iT邦幫忙

2026 iThome 鐵人賽

DAY 5
0
Claude AI

血汗同事:我和 Claude Code 僱了一隊 AI,做出 Web MMO ARPG系列 第 5

Day 4|怪物、戰鬥、升級:核心數值進表格

  • 分享至 

  • xImage
  •  

Whisper Isle 遊戲連結(PC Only):whisperisle.app(註:專案每日推進,撰文當下線上已推進至 v0.2.2)

昨天在 Day 3 解決了部署問題,雲端有了一個活著的公網環境,但一片空畫布終究不是遊戲。做一個連線 RPG,必須先把最底層的最小原子核心迴圈(Core Loop)打通:

發現怪物 → 戰鬥互動 → 擊殺獲得經驗與掉落 → 升級成長 → 怪物重生循環。

而上述這一點,在開工的第一個週末(2026-07-10 深夜到 07-11 凌晨),我和 Fable 用了約 3 個多小時完成(表格驅動內容 Data-Driven Design)。

邏輯寫進程式碼,數值則進表格

當你放任 AI 實作一隻怪物時,它的偷懶直覺通常長這樣:

它會在後端邏輯的深處(例如 WorldRoom.ts)寫下幾行常數:

const wolfHp = 30; const wolfDamage = 6;;

或者在怪物實體的建構式裡用一堆 if-else 硬寫:if (type === 'wolf') { this.speed = 90; }

在一人加上 AI 的協作中,這種寫法會帶來致命的誤傷半徑(Blast Radius):

當你實測後覺得「野狼血量太厚了,調低 5 點、反咬間隔拉長 0.2 秒」時,AI 就必須去翻閱幾百行包含物理運算與狀態同步的邏輯代碼。而在改動這些數值的同時,AI 極容易順手改壞旁邊的 tick 迴圈、或是漏掉某個欄位的廣播。改個數值,整個戰鬥系統可能就默默掛掉。

因此嚴格要求:程式碼只負責「讀表並套用通用規則」,所有具體數值一律抽離到 JSON 資料表。我們在 packages/shared/src/data/monsters.json 建立了第一隻怪物「野狼」的資料表:

// packages/shared/src/data/monsters.json
// packages/shared/src/data/monsters.json
{
  "wolf": {
    "level": 2,
    "name": "野狼",
    "maxHp": xxxx,
    "moveSpeed": xxx,
    "wanderRadius": xxx,
    "wanderPauseMs": x,
    "respawnMs": x,
    "attackDamage": x,
    "attackRange": x,
    "attackCooldownMs": xxx,
    "leashRadius": x,
    "xpReward": 12x,
    "physicalDefense": 5xx,
    "magicDefense": 3xx,
    "dropQuantityWeights": [
      { "quantity": 1x, "weight": xx },
      { "quantity": 3x, "weight": 2x }
    ],
    "behavior": "xxxx",
    "socialRadius": xxx,
    "socialMaxResponders": 3,
    "packKey": "wolf",
    "drops": [
      { "itemKey": "wolf_pelt", "weight": 75 },
      { "itemKey": "wolf_fang", "weight": 25 }
    ]
  }
}

這個表成了前後端唯一的怪物數值事實來源(SSOT):

  • 前後端共用:前端只讀表取得中文顯示名(name)與血條上限;後端讀取攻擊距離、冷卻、防禦與經驗。
  • 邊界清晰:怪物表只負責定義「怪物的範本屬性(Stats Template)」。怪物的生成數量與座標不塞在怪表,而是屬於地圖物件層(packages/shared/src/data/maps/*/ 的 map.zones)。
  • 掉落機制:掉落物不是簡單的獨立浮點數機率,而是**權重單選池(Weighted Pool)**配合數量權重表(dropQuantityWeights)。
  • 調整遊戲平衡時,AI 只需要改 JSON,不需要碰任何一行 TypeScript,誤傷半徑被物理隔離為零。

怪物生命週期:隨機漫遊

有了一張表,伺服器在房間建立時就能依表生成指定數量(spawnCount: 6)的野狼。但怪物不能像木樁一樣死站在原地。
隨機漫遊 AI
如果所有野狼用固定頻率移動,在畫面上看起來會極其僵硬機械。在 20Hz 的 fixedTick 裡,每隻怪物在其出生地(Home)的 wanderRadius: 220 半徑內隨機挑點等速前進。走到了停下,停頓時間帶有 0.5 ~ 1.5 的隨機浮動係數(wanderPauseMs * random),自然打破了怪物群體的同步感。

戰鬥操作:意圖分離、職業表與單一計算入口

在戰鬥操作與手感上,我們執行 Client 只送意圖,不送結果:

  • 攻擊意圖:玩家滑鼠點擊怪物,前端只發送「我想攻擊這隻怪」的意圖訊息。
  • 自動推進走位:如果玩家與怪物距離大於武器射程,伺服器會自動先推進玩家走向怪物;一旦進入射程,立即在攻擊冷卻允許時揮刀。
  • 移動打斷(意圖互斥):戰鬥中只要點擊任何空地發送移動指令(MoveTo),伺服器立即切斷攻擊狀態機,轉為移動。最重要的是:玩家的攻擊數值同樣禁止寫死在邏輯代碼裡。

我們在 packages/shared/src/data/classes.json 建立了職業表。初期以「騎士(Knight)」為範本:

{
  "knight": {
    "name": "騎士",
    "baseHp": 100,
    "hpPerLevel": 12,
    "baseDamage": 10,
    "damagePerLevel": 3,
    "baseMp": 30,
    "mpPerLevel": 6,
    "attackRange": 48,
    "attackCooldownMs": 900,
    "moveSpeed": 220,
    "weaponTypes": ["sword", "greatsword"]
  }
}

同時在 packages/shared/src/progression.ts 訂立了單一計算入口:

export function playerAttackDamageAtLevel(classKey: ClassKey, level: number, weaponBonus = 0): number {
  const definition = CLASS_TABLE[classKey];
  return definition.baseDamage + (level - 1) * definition.damagePerLevel + weaponBonus;
}

這個單一入口直接對應規定:跨系統數值必走單一計算入口,禁止在任何 tick 內散落 +bonus。日後無論是騎士、祕法師還是獵手,無論何時加入裝備或 Buff,伺服器計算攻擊力永遠只有這一條路徑,絕不會發生各處公式不一致的慘劇。Client 端嚴格遵循「不自算傷害、不預扣血量」。怪物被打時,由伺服器固定 tick 計算傷害並廣播 CombatHit 事件,前端收到後才彈出傷害浮動文字(Floating Combat Text)並扣除血條。

https://ithelp.ithome.com.tw/upload/images/20260909/20106947vOrAcCL8eW.png

升級與掉落

  1. 經驗值與升級(Level Progression)
    擊殺者立即獲得 xpReward: 12 經驗值。在 shared 定義的 PLAYER_XP_TABLE 門檻表裡,前 3 級設計為單人農怪約 2 分鐘即可達成的節奏:
export const PLAYER_XP_TABLE: readonly number[] = [
  0, 36, 96, 180, 300, 456, 660, 912, 1224, 1608, ...
];
  1. 掉落物與拾取(Loot & Pickup)
    野狼死後,伺服器依照怪表內的 drops 權重擲骰。骰中時,地面上生成一個帶有 60 秒存活倒數(GROUND_ITEM_TTL_MS: 60000)的地面物品實體。玩家點擊地面物品送出 PickUp 意圖,角色自動走近到 48px 範圍內完成拾取,物品進入背包,地面物品同步從所有人的畫面上消失。
  2. 怪物重生排程
    野狼被擊殺後,伺服器排程一個 8 秒(respawnMs: 8000)的重生計時器,倒數結束後在出生區重新生成一隻全新的野狼,讓整個生態圈永續運轉。

https://ithelp.ithome.com.tw/upload/images/20260909/20106947ZMzlcViHXU.png

結語

建立骨架,數值進表格,架構設定在模組,後續 AI 就能更有效擴充職業與技能,甚至怪物的種類遞增。


上一篇
Day 3|部署上線:避免 AI 本地開發幻覺
下一篇
Day 5|自作聰明的 AI
系列文
血汗同事:我和 Claude Code 僱了一隊 AI,做出 Web MMO ARPG12
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言