iT邦幫忙

2026 iThome 鐵人賽

DAY 2
1
Claude AI

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

Day 1|第一天先設計防範規則:跟 AI 合作的一些鐵律

  • 分享至 

  • xImage
  •  

Whisper Isle v0.1.3 遊戲連結(PC Only):whisperisle.app

昨天在 Day 0 提到,我找了 Claude Code 當主力 CTO,打算在瀏覽器裡 vibe 出一個 MMO ARPG。

很多人看社群分享 AI 寫程式,畫面通常很美好:給一句 prompt,AI 吐出幾百行代碼,在瀏覽器裡跑起來好像有模有樣。但實務上,如果你真的拿它來做一個即時多人連線系統,而且放任它「自由發揮」,那可能難以長期支撐。

AI 寫 code 有個天性:它永遠傾向用阻力最小、最扁平的方式解決眼前的問題(簡單說就是偷懶,或者說節省算力)。你叫它做點擊移動,它最順手的寫法就是在前端把人物座標改掉,發個封包告訴伺服器「我在 (x, y)」;你叫它做血條,它就會在 UI 元件裡宣告一個 let currentHp,你叫它做聊天室,它就把 DOM 按鈕事件直接綁在遊戲渲染引擎的 Sprite 物件上。

前三天看起來神速,第七天功能一多,你的整個 codebase 就會變成一盤誰也動不了的義大利麵條(Spaghetti code)。當上下文越來越大、依賴越來越亂,AI 就會開始出現幻覺、改 A 壞 B,最後重構的成本可能高到無以為繼。

所以在專案開工的第一個 commit 裡,除了 package.json 就是 CLAUDE.md,為了確保整個施工流程盡可能順暢(畢竟你要 vibe 速度還是很快的,只能盡量變做邊修正方向),施工規則還是要先制定下來。

1. Client 端只送「意圖」,不送「結果」

這是做 MMO 最核心的骨架,也是 AI 最容易偷懶破功的地方。

AI 天生缺乏分散式系統與防作弊的直覺,它的預設思維往往是「單機模式」:前端算好位移,再同步給後端。如果放任它這樣做,後面只要加上怪物的攻擊判定、地圖碰撞,整個架構就會直接崩塌。因此第一天在協定層(packages/shared)就把訊息定義鎖死:

// packages/shared/src/index.ts

/** 玩家移動速度(邏輯 px/s;server 權威模擬唯一使用點,client 不得自算位移) */
export const PLAYER_SPEED = 220;

/** server 固定模擬 tick(Hz) */
export const SIMULATION_TICK_RATE = 20;

/**
 * Client → Server 訊息名。
 * 鐵律:client 只送意圖(「我想去 (x,y)」),不送結果(「我在 (x,y)」)。
 */
export const ClientMessage = {
  MoveTo: "moveTo",
} as const;

/** MoveTo payload=目的地世界座標(意圖;server 端 clamp 進世界邊界) */
export type MoveToPayload = Vec2;

客戶端永遠只能發送「意圖」——例如 moveTo(x, y) 代表「我想走向這裡」。至於走不走得到、移動路徑有沒有被障礙物擋住、每一步走多快,全部由伺服器每秒 20 次(20Hz)的 fixedTick 說了算:

// server/src/rooms/WorldRoom.ts

// 接收意圖:只記錄目標點,不改座標
messages = {
  [ClientMessage.MoveTo]: (client: Client, payload: MoveToPayload) => {
    const player = this.state.players.get(client.sessionId);
    if (!player) return;
    if (!Number.isFinite(payload?.x) || !Number.isFinite(payload?.y)) return;
    player.target = {
      x: clamp(payload.x, 0, WORLD_WIDTH),
      y: clamp(payload.y, 0, WORLD_HEIGHT),
    };
  },
};

// 伺服器唯一寫入點:固定 tick 等速推進
private fixedTick(dtMs: number) {
  const step = (PLAYER_SPEED * dtMs) / 1000;
  this.state.players.forEach((player) => {
    if (!player.target) return;
    const dx = player.target.x - player.x;
    const dy = player.target.y - player.y;
    const dist = Math.hypot(dx, dy);
    if (dist <= step) {
      player.x = player.target.x;
      player.y = player.target.y;
      player.target = null;
      return;
    }
    player.x += (dx / dist) * step;
    player.y += (dy / dist) * step;
  });
}

2. 狀態單向(Colyseus State 是唯一事實來源)

AI 寫前端時最愛做一件事:為了讓畫面反應更靈敏,在各個組件裡面宣告本地變數當快取(例如在 UI 層存一份 let myHp = 100、在場景層存一份怪獸清單)。

這在單機應用或許無傷大雅,但在即時多人連線裡就是災難的源頭。一旦發生網路抖動或丟包,前端的本地變數跟後端的真實狀態就會產生漂移。最後玩家就會看到怪物明明血條空了卻打不死、或是自己血條滿的卻突然暴斃。

所以立下的第二條防線:Colyseus State 是唯一的事實來源(Single Source of Truth)。

  • UI 與遊戲場景永遠只是後端狀態的投影(View)。
  • Client 端禁止自存任何遊戲狀態的副本。

3. UI 代碼與遊戲世界徹底隔離

在 Web 遊戲開發中,畫面通常是由兩個完全不同的世界拼起來的:

底下的 Canvas/WebGL 遊戲世界(地圖、角色、怪物、粒子特效)。
上面的 DOM 介面(聊天框、背包格、血條、系統選單)。
如果沒有立規則,AI 最喜歡抄捷徑:在 HTML 按鈕的 click handler 裡直接伸手去摸 Pixi.js 的 Sprite 改屬性;或是反過來,把遊戲世界的怪物物件整包傳給 UI 元件當 props。這兩者一旦產生雙向依賴,未來只要你想微調 UI 佈局,整個遊戲的底層渲染就會跟著被污染。client/src/ui/ 與 client/src/world/ 嚴禁互相 import 內部模組。

  ┌─────────────────── 客戶端 (Client) ───────────────────┐
  │                                                        │
  │   [ DOM UI 殼 ]                   [ Pixi Canvas 世界 ] │
  │   (血條 / 快捷列 / 聊天)           (地圖 / 角色 / 怪物)   │
  │         │                               │              │
  │         └─── 嚴禁互相 import 內部模組 ───┘              │
  │                         │                              │
  │                         ▼                              │
  │           只送意圖:ClientMessage.MoveTo               │
  └─────────────────────────┼──────────────────────────────┘
                            │ WebSocket
  ┌─────────────────────────┼──────────────────────────────┐
  │                         ▼                              │
  │      [ Server: WorldRoom 權威模擬 (20Hz fixedTick) ]   │
  │      - 檢查邊界與障礙物                                   │
  │      - 等速推進座標                                      │
  │                         │                              │
  │                         ▼                              │
  │           單向廣播:WorldState (唯一事實來源)             │
  └─────────────────── 伺服器 (Server) ───────────────────┘

https://ithelp.ithome.com.tw/upload/images/20260906/201069478KISfHqamw.png


上一篇
Day 0|夢回 2000 年,用 AI 做一個 MMO 吧!
下一篇
Day 2|技術選型:對 AI 友善的架構更能長遠走下去
系列文
血汗同事:我和 Claude Code 僱了一隊 AI,做出 Web MMO ARPG12
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言