我調了一整天的平衡參數,結果引擎根本沒在讀那個欄位
你有沒有過那種,認真調了老半天的參數、跑了好幾輪測試、覺得自己很科學,最後發現——那個欄位根本沒人讀?就像對著一台沒插電的電風扇按了一整天遙控器,還很納悶它怎麼都不轉。
我今天就對著一個欄位按了好幾下遙控器。
催眠術調不動
背景:遊戲裡有隻雜兵 hypno,會放睡眠把玩家定住。我覺得這招太煩,玩家被定住的時間太長,體驗很差,所以我去調它的睡眠資料。
hypno 的 JSON 大概長這樣:
{
"sleep": {
"chance": 0.6,
"durationMs": 1800
}
}
我第一個動作很直覺——把 chance 從 0.6 砍到 0.4,想說讓它別那麼常成功。改完跑遊戲,催眠頻率完全沒變。
我以為是我 build 沒更新,重跑。一樣。以為是快取,清掉。還是一樣。卡在這邊真的有點度日如年,因為改了數字、存了檔、重跑了,三件事都做了,結果零變化——這種「明明做對了卻沒反應」的情境,比直接噴錯還讓人焦慮。
最後去翻引擎讀這份 JSON 的地方,才發現殘酷的真相:引擎根本沒讀 chance 這個欄位。 睡眠是不是觸發、走的是另一條判定,chance 是某次重構之後留下來的孤兒——資料還在,但沒人引用了。它就靜靜躺在 JSON 裡,看起來像個正經的平衡參數,實際上是個死欄位。
我等於對著空氣調了半天。真正有效的修法是 data-only:把 chance 這個死欄位直接移掉(免得下一個人又被它騙),然後改 durationMs 1800→1200——這個欄位引擎是真的有讀的。改完,玩家被定的時間明顯短了。
升級反而變弱
以為這就夠戲劇了,結果同一天 game-design review 又挖出另一個更陰的。
噴火龍的進化鏈是 charmander → charmeleon → charizard。直覺上越進化越強對吧?我也是這樣假設的。
review 跑到第三輪的時候有人發現——charizard 的 HP 比 charmeleon 還低。
charmeleon 是 100 HP。charizard 用 maxHpMul 1.2 去乘一個基底,算出來是 96。玩家辛辛苦苦進化到終態,血量不增反減,少了 4 點。這在數字上很小,但在體感上是災難——「我升級了,然後變弱了」,這是會讓玩家罵髒話的那種 bug。
更陰的是它不會噴任何錯。96 是個完全合法的 HP 值,測試也全綠,因為沒有任何一條測試在斷言「終態 HP 必須大於前一階」。它只是默默地違反了一個沒人寫下來的常識。修法很簡單,maxHpMul 1.2→1.3,讓終態血量真的高於前一階。但能抓到它,靠的是三輪人工收斂審查,不是測試。
兩個故事,同一根刺
我把這兩件事放一起看,發現它們戳的是同一根刺:
資料驅動的系統,最危險的不是「資料是錯的」,是「資料看起來對、但語意是錯的」。
chance 這個死欄位語法完全合法、值也合理,只是沒人讀——它騙的是「修改者的預期」。charizard 的 96 HP 也完全合法、也是個正常數字,只是違反了「進化應該變強」這個隱性契約——它騙的是「玩家的常識」。兩個都不會讓 build 變紅,所以自動化測試抓不到,要嘛靠你親手去翻引擎、要嘛靠人坐下來一輪一輪審。
這也是為什麼我越來越相信 data-driven 的專案需要兩種防線:一種是 schema 驗證,擋掉「語法錯」;另一種是 golden test 或人工 review,擋掉「語意錯」。前者機器做得很好,後者目前還是得靠人——至少在我抓到更好的辦法之前吧。
(對了,今天還順手做了個滿大的東西——無限接關系統,全員倒地後倒數窗內按攻擊鍵就能原波重打、王前滿血重來,每次接關通關時間 +30s 當懲罰。拆成 ContinueManager 純狀態機 + 各模組各司其職,寫得算順。但它沒有上面兩個 bug 有戲,所以就放這邊輕輕帶過啦。)
先不說了,我得去把整份 enemies JSON 掃一遍,看看還有幾個 chance 那種死欄位躺在裡面對我笑。
這些 code 寫於 2026 年 6 月 17 日,文章整理於同一個晚上。