← 返回部落格
·10 min 閱讀·專案紀錄

用 LLM 打造 AI 地城主:多人 TRPG 遊戲引擎的架構設計

深入解析一套由大型語言模型驅動的多人桌遊系統架構,涵蓋 NPC 語意記憶(pgvector)、雙層行動驗證引擎、劇本自動生成 Pipeline、即時 WebSocket 多人互動,以及 LLM 並發控制策略。

LLM遊戲開發NestJSpgvectorWebSocket
GP Wang
GP Wang
GWP4 STUDIO 創辦人 · 軟體 / 資料工程師

專案動機

桌遊(TRPG)最大的門檻是「找到一個好的 DM(地城主)」。一個好的 DM 需要即興反應能力、對規則的深入理解、以及創造沉浸感的說故事技巧。

如果 LLM 能扮演 DM 的角色呢?

這個專案的目標是打造一套完整的 AI 驅動 TRPG 遊戲引擎。玩家透過類終端機的 Web 介面進行遊戲,AI 負責:NPC 對話、場景描述、行動判定、戰鬥旁白、甚至整個劇本世界的自動生成。

系統全貌

[Next.js Frontend] ←WebSocket→ [NestJS Backend]
       │                              │
       │                     ┌────────┼────────┐
       │                     │        │        │
       │                     ▼        ▼        ▼
       │               [PostgreSQL] [Redis] [Ollama/LLM]
       │                 + pgvector
       │
  CLI 風格 UI
  打字機效果
  骰子動畫

技術棧

| 層級 | 技術 | |------|------| | Monorepo | pnpm workspaces + Turborepo | | Frontend | Next.js 14 (App Router) | | Backend | NestJS (TypeScript) | | Database | PostgreSQL + pgvector | | Cache/PubSub | Redis | | WebSocket | Socket.IO | | LLM | Ollama (qwen2.5:7b-instruct) | | ORM | Drizzle ORM | | Auth | JWT + Google OAuth |

LLM 整合:五個關鍵系統

LLM 不是一個簡單的「生成文字」功能,而是深入到遊戲的多個核心系統中。

1. NPC 對話與人格

每個 NPC 有結構化的人格定義:

interface NpcPersonality {
  traits: string[];          // 性格特點
  speaking_style: string;    // 說話風格
  background: string;        // 背景故事
  knowledge: string[];       // 知道的事情
  restrictions: string[];    // 不會透露的祕密
}

對話時,系統將人格轉換成中文 system prompt,注入記憶和場景上下文:

function buildNpcConversationPrompt(npc: Npc, memories: Memory[], playerName: string) {
  return `你是「${npc.name}」,${npc.personality.background}

性格特點:${npc.personality.traits.join('、')}
說話風格:${npc.personality.speaking_style}

你記得的事情:
${memories.map(m => `- ${m.content}`).join('\n')}

限制:你絕對不會主動提及以下資訊:
${npc.personality.restrictions.join('\n')}

現在「${playerName}」正在跟你說話。請以角色的身份回應。`;
}

2. NPC 語意記憶(pgvector)

NPC 不只是「每次對話都從零開始」。他們有持久記憶——而且是語意搜索的記憶。

當一段對話結束,系統會:

  1. 用 LLM 生成 768 維的 embedding vector
  2. 存入 npc_memories 表(帶有 importance 權重 1-10)

下次對話時,用玩家的當前語句做語意搜索:

SELECT content, importance,
  1 - (embedding <=> $queryVec::vector) AS similarity
FROM npc_memories
WHERE npc_id = $npcId
  AND embedding_status = 'completed'
  AND 1 - (embedding <=> $queryVec::vector) >= 0.7
ORDER BY similarity * (importance::float / 10.0) DESC
LIMIT 5

排序公式是 similarity × importance——確保重要記憶(如「玩家曾經救過我的命」)不會被瑣碎但語意相似的記憶蓋過。

為什麼用 pgvector 而不是 Pinecone/Qdrant?

  • 不需要額外的服務(已經有 PostgreSQL)
  • NPC 記憶的規模不大(幾千條而非幾百萬條)
  • Drizzle ORM 用 customType 就能支援

3. 雙層行動驗證引擎

玩家在遊戲中可以嘗試做任何事情。系統需要判斷「這個行動在這個世界中是否合理」。

第一層:規則引擎(快速、確定性)

function validateByRules(action: string, worldRules: WorldRules): ValidationResult {
  // 關鍵字檢查:中世紀世界不能用手機
  if (worldRules.era === 'medieval' && containsModernTech(action)) {
    return { valid: false, reason: '這個世界沒有這種科技' };
  }

  // 物理規則:沒有飛行能力不能飛
  if (isFlightAction(action) && !playerHasFlightAbility()) {
    return { valid: false, reason: '你沒有飛行的能力' };
  }

  // 簡單移動:直接通過
  if (isSimpleMovement(action)) {
    return { valid: true, determined: true };
  }

  // 無法確定 → 交給 LLM
  return { determined: false };
}

第二層:LLM 判定(複雜情境)

規則引擎無法判斷時(例如「我嘗試用巧言說服守衛放我進去」),送給 LLM 判斷:

const llmPrompt = `世界設定:${worldSetting}
物理規則:${physicsRules}
魔法系統:${magicSystem}
玩家狀態:位於${location},擁有${inventory}

玩家嘗試的行動:${action}

請判斷這個行動是否合理,回傳 JSON:
{ "valid": bool, "result": "行動的結果描述", "reason": "判斷理由", "side_effects": [] }`;

雙層設計的好處:

  • 簡單情況不浪費 LLM 算力(規則引擎毫秒級回應)
  • 複雜情況才動用 LLM(保持靈活性)
  • LLM 失敗時 fallback 為「允許但警告」(遊戲不會卡住)

4. 劇本世界自動生成

最強大的功能:輸入一個劇本概念,LLM 自動生成整個可玩的世界。

生成是鏈式的——每一步都參考前面的結果:

Locations → NPCs → Items → Quests
    │          │       │
    │          ▼       │
    │     (放在哪個地點)│
    │                  ▼
    └─────────────(出現在哪裡)
async runGeneration(scenario: Scenario) {
  // Step 1: 生成地點
  const locations = await this.generateWithLlm(
    buildLocationsPrompt(scenario)
  );

  // Step 2: 生成 NPC(知道有哪些地點)
  const npcs = await this.generateWithLlm(
    buildNpcsPrompt(scenario, { generatedLocations: locations })
  );

  // Step 3: 生成物品
  const items = await this.generateWithLlm(
    buildItemsPrompt(scenario, { generatedLocations: locations })
  );

  // Step 4: 生成任務(知道地點和 NPC)
  const quests = await this.generateWithLlm(
    buildQuestsPrompt(scenario, {
      generatedLocations: locations,
      generatedNpcs: npcs,
    })
  );
}

用 JSON Schema 約束 LLM 輸出格式 + Zod 後驗證,確保結果結構正確。失敗則以 temperature=0.3 重試一次。

5. LLM 並發控制

多人同時遊戲時,LLM 請求可能瞬間暴增。用 Semaphore + Priority Queue 控制:

class LlmService {
  private semaphore = new Semaphore(MAX_CONCURRENCY); // 預設 3
  private queue = new PriorityQueue();

  async complete(prompt: string, priority: 'high' | 'low') {
    if (priority === 'low' && this.queue.isFull()) {
      return null; // 低優先級在佇列滿時直接丟棄
    }

    await this.semaphore.acquire(priority);
    try {
      return await this.callLlm(prompt);
    } finally {
      this.semaphore.release();
    }
  }
}

優先級設計:

  • high:玩家即時操作(NPC 對話、行動判定)→ 一定要排到
  • low:背景任務(embedding 計算、劇本生成)→ 佇列滿就丟棄

這確保了玩家體驗不會因為背景任務而卡頓。

遊戲系統支援

系統不只支援 D&D,而是一個通用的 TRPG 引擎:

  • D&D 5e:六大屬性、技能檢定、HP/AC/先攻
  • 克蘇魯的呼喚 7e:SAN 值、理智檢定、暫時/永久瘋狂

角色創建是一個 6 步驟的精靈介面:遊戲系統 → 基本資料 → 種族/職業 → 屬性點數 → 技能分配 → 確認。

前端:CLI 風格的沉浸體驗

整個遊戲介面模擬終端機風格:

  • 打字機效果:AI 的回應逐字顯示,模擬 DM 說話
  • 命令輸入框:玩家輸入行動(不是選擇題)
  • 骰子動畫:技能檢定時顯示擲骰結果
  • 經驗值進度條:角色成長視覺化
  • ASCII Art 標題:增加復古感

多人即時互動

透過 Socket.IO 實現多人同世界互動:

  • 玩家加入同一個 world instance
  • 一個玩家的行動結果會廣播給其他玩家
  • NPC 的回應所有人都看得到
  • 戰鬥時按先攻順序輪流行動

學到的經驗

LLM 輸出的不確定性是最大挑戰

即使用 JSON Schema 約束,LLM 偶爾還是會輸出無法解析的結果。必須在每一層都做 fallback:

  • 格式錯誤 → 重試一次(低 temperature)
  • 重試失敗 → graceful degradation(預設行為)
  • 永遠不讓遊戲卡在「LLM 失敗」上

7B 模型的能力邊界

Qwen 2.5:7b 能處理大多數簡單的 NPC 對話和行動判定,但在複雜的劇本生成和多步推理上偶爾會出問題。未來可能需要:

  • 簡單任務用 7B
  • 複雜任務(劇本生成)用更大的模型或雲端 API

pgvector 的實用性

對於「每個 NPC 幾百條記憶」這個規模,pgvector 完全夠用。不需要專門的向量資料庫。但 Drizzle ORM 不原生支援 vector 型別,需要用 customType 手動處理序列化。

結語

這個專案展示了 LLM 在遊戲領域的一個有趣應用:不是用 AI「取代」人類 DM,而是讓更多人能在沒有 DM 的情況下也能享受 TRPG 的樂趣。

技術上最有價值的模式是「雙層驗證」(規則引擎 + LLM fallback)和「語意記憶」(pgvector embedding + importance 加權)。這兩個模式在 TRPG 之外的場景也有廣泛的應用價值——任何需要「在規則框架內做創造性判斷」的系統都可以參考。

關於作者

GP Wang
GP Wang

GWP4 STUDIO 創辦人,超過 8 年軟體開發經驗,專精資料工程、全端開發與系統架構。 持續在部落格分享專案實作經驗與技術心得。

了解更多 →