引言:AI系統概述
RimWorld 的 NPC AI 系統是一個多層次、模塊化的智能決策框架。與傳統的單一 AI 系統不同,RimWorld 將 AI 功能分解為五個相互協作的層次,每一層負責不同的職責,共同實現 NPC 的複雜行為。
系統架構概覽
┌─────────────────────────────────────────────────────────┐
│ 第五層:戰鬥決策層 (AttackTargetFinder) │
│ - 目標評分與選擇 │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 第四層:羣體協調層 (Lord System) │
│ - 狀態機驅動的羣體行為 │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 第三層:工作執行層 (Job System) │
│ - Job/Toil 任務執行框架 │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 第二層:路徑導航層 (PathFinder) │
│ - A* 路徑查找算法 │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 第一層:思考決策層 (ThinkTree) │
│ - 決策樹驅動的行為選擇 │
└─────────────────────────────────────────────────────────┘
每一層都有其獨特的職責,但它們並非孤立工作,而是通過精心設計的接口相互協作,形成一個完整的 AI 決策和執行系統。
核心設計理念
- 分層解耦:每一層專注於特定功能,降低系統複雜度
- 優先級驅動:決策基於優先級,確保重要行為優先執行
- 狀態管理:通過狀態機管理複雜的行為轉換
- 性能優化:使用啓發式算法和緩存機制提升性能
第一層:思考決策層(ThinkTree 系統)
思考決策層是 AI 系統的"大腦",負責決定 NPC 應該做什麼。它採用決策樹(Decision Tree)結構,通過遍歷節點來找到合適的行動。
核心概念
ThinkNode(思考節點)
ThinkNode 是決策樹的基本單元,每個節點代表一個決策點或行為生成器。節點可以包含子節點,形成樹狀結構。
節點類型:
- ThinkNode_Priority:優先級節點,按順序嘗試子節點,返回第一個有效結果
- ThinkNode_JobGiver:工作生成器,負責創建具體的 Job
- ThinkNode_Conditional:條件節點,根據條件決定是否執行子節點
- ThinkNode_Subtree:子樹節點,引用另一個思考樹
ThinkTree(思考樹)
ThinkTree 定義了 NPC 的完整決策邏輯。每個 NPC 擁有兩個思考樹:
- MainThinkTree:主要思考樹,處理常規行為決策
- ConstantThinkTree:常量思考樹,處理需要持續檢查的行為(如緊急情況)
決策流程
思考樹的遍歷遵循以下邏輯:
function TryIssueJobPackage(pawn, jobParams):
for each childNode in subNodes:
result = childNode.TryIssueJobPackage(pawn, jobParams)
if result.IsValid:
return result
return NoJob
關鍵特點:
- 優先級順序:子節點按順序評估,高優先級節點在前
- 短路機制:一旦找到有效結果,立即返回,不再評估後續節點
- 異常處理:單個節點異常不會影響整個決策流程
實際示例
假設一個 NPC 需要決定下一步行動,思考樹可能是這樣的結構:
ThinkNode_Priority (根節點)
├── ThinkNode_Conditional (檢查是否有敵人)
│ └── ThinkNode_JobGiver_Attack (攻擊敵人)
├── ThinkNode_Conditional (檢查是否飢餓)
│ └── ThinkNode_JobGiver_Food (尋找食物)
├── ThinkNode_Conditional (檢查是否需要休息)
│ └── ThinkNode_JobGiver_Rest (去休息)
└── ThinkNode_JobGiver_Wander (閒逛)
NPC 會從上到下依次檢查:
- 如果有敵人 → 攻擊
- 如果飢餓 → 找食物
- 如果需要休息 → 去休息
- 否則 → 閒逛
JobGiver 機制
JobGiver 是連接思考層和執行層的橋樑。它負責:
- 評估當前情況
- 決定是否應該生成某個 Job
- 創建並返回 Job 對象
class JobGiver_Attack:
function TryGiveJob(pawn):
target = FindBestTarget(pawn)
if target == null:
return null
return CreateAttackJob(pawn, target)
第二層:路徑導航層(PathFinder 系統)
路徑導航層負責計算 NPC 從當前位置到目標位置的移動路徑。RimWorld 使用改進的 A* 算法來實現高效的路徑查找。
A* 算法基礎
A* 算法結合了 Dijkstra 算法的準確性(考慮實際成本)和貪心算法的效率(使用啓發式估計)。
核心公式:
f(n) = g(n) + h(n)
g(n):從起點到節點 n 的實際成本h(n):從節點 n 到終點的啓發式估計成本f(n):節點的總評估成本
RimWorld 的路徑查找實現
成本計算
路徑查找會考慮多種因素來計算移動成本:
function CalculateCellCost(cell, pawn):
baseCost = GetTerrainCost(cell) // 地形基礎成本
buildingCost = GetBuildingCost(cell) // 建築成本(門、牆等)
avoidCost = GetAvoidGridCost(cell) // 避讓區域成本
areaCost = GetAreaRestrictionCost(cell) // 區域限制成本
collisionCost = GetPawnCollisionCost(cell) // NPC碰撞成本
return baseCost + buildingCost + avoidCost + areaCost + collisionCost
特殊處理:
- 門:根據是否可以打開、是否需要破壞來計算成本
- 牆:如果允許破壞,成本 = 基礎成本 + 牆體血量 × 係數
- 避讓網格:避免危險區域(如炮火覆蓋區)
啓發式函數
RimWorld 使用八方向距離(Octile Distance)作為啓發式:
function OctileDistance(dx, dz, cardinalCost, diagonalCost):
// dx, dz: x和z方向的差值
// cardinalCost: 直線移動成本
// diagonalCost: 斜線移動成本
if dx > dz:
return diagonalCost * dz + cardinalCost * (dx - dz)
else:
return diagonalCost * dx + cardinalCost * (dz - dx)
啓發式強度:
- 動物:固定係數 1.75
- 人類:根據距離動態調整(距離越遠,係數越大,最高 2.8)
區域路徑優化
當搜索節點數超過閾值時(殖民者 100,000,非殖民者 2,000),系統會切換到區域級路徑查找:
if nodesOpened > threshold:
// 切換到區域級路徑查找
regionCost = CalculateRegionPathCost(currentRegion, targetRegion)
heuristic = regionCost * regionWeight
這種優化可以顯著減少大距離路徑查找的計算量。
路徑執行
找到路徑後,Pawn_PathFollower 負責執行移動:
function PatherTick():
if WillCollideWithPawn(nextCell):
if CanFindAlternatePath():
RecalculatePath()
else:
WaitForPawn()
if nextCellCostLeft > 0:
nextCellCostLeft -= CostToPayThisTick()
else:
MoveToNextCell()
SetupNextCell()
移動速度控制:
- Amble(漫步):成本 × 3,最小 60 ticks
- Walk(步行):成本 × 2,最小 50 ticks
- Jog(慢跑):成本 × 1
- Sprint(衝刺):成本 × 0.75
第三層:工作執行層(Job 系統)
工作執行層將抽象的"做什麼"轉化為具體的"怎麼做"。它通過 Job、JobDriver 和 Toil 三個層次來實現任務的執行。
核心組件
Job(工作)
Job 是任務的抽象描述,包含:
- JobDef:工作類型定義(如攻擊、建造、搬運)
- TargetA/B/C:工作目標(位置、物體、NPC等)
- expiryInterval:過期時間
- playerForced:是否玩家強制
JobDriver(工作驅動)
JobDriver 負責執行 Job,它包含一系列 Toil(工作步驟):
class JobDriver_AttackMelee:
function MakeNewToils():
toils = []
// Toil 1: 移動到目標附近
toils.Add(Toils_Goto.Goto(TargetIndex.A, PathEndMode.Touch))
// Toil 2: 等待直到可以攻擊
toils.Add(Toils_Combat.WaitUntilSuitableToAttack(TargetIndex.A))
// Toil 3: 執行近戰攻擊
toils.Add(Toils_Combat.MeleeAttack(TargetIndex.A))
// Toil 4: 跳回 Toil 2(循環攻擊)
toils.Add(Toils_Jump.JumpIf(TargetIndex.A, IsValid, toils[1]))
return toils
Toil(工作步驟)
Toil 是最小的執行單元,每個 Toil 代表一個原子操作:
Toil {
initAction: // 初始化時執行
tickAction: // 每 tick 執行
endConditions: // 結束條件檢查
defaultDuration: // 默認持續時間
defaultCompleteMode: // 完成模式
}
完成模式:
- Instant:立即完成
- Delay:延遲指定時間後完成
- PatherArrival:到達路徑終點時完成
- FinishedBusy:忙碌狀態結束時完成
工作執行流程
function JobTrackerTick():
// 每 30 ticks 檢查常量思考樹
if IsHashIntervalTick(30):
constantJob = DetermineConstantThinkTreeJob()
if constantJob.IsValid:
StartJob(constantJob)
// 執行當前工作
if curDriver != null:
curDriver.DriverTick()
// 檢查工作是否過期
if curJob.IsExpired():
EndCurrentJob(JobCondition.Succeeded)
// 如果沒有工作,尋找新工作
if curJob == null:
TryFindAndStartJob()
工作隊列
NPC 可以維護一個工作隊列,支持:
- EnqueueFirst:插入隊列頭部(高優先級)
- EnqueueLast:插入隊列尾部(低優先級)
- Dequeue:取出下一個工作
這允許玩家為 NPC 安排多個連續任務。
工作中斷與恢復
工作可能因為以下原因中斷:
- 更高優先級的工作:思考樹生成新工作
- 工作條件失效:目標消失、不可達等
- 玩家強制:玩家手動分配新工作
中斷的工作可以:
- 完全取消:釋放所有資源
- 暫停並排隊:如果工作可暫停,加入隊列等待恢復
第四層:羣體協調層(Lord 系統)
羣體協調層管理多個 NPC 的協同行為,如襲擊、商隊、防禦等。它使用狀態機(State Machine)來協調羣體行為。
核心概念
Lord(領主)
Lord 代表一個羣體,管理:
- ownedPawns:屬於該羣體的 NPC 列表
- ownedBuildings:屬於該羣體的建築列表
- curLordToil:當前狀態
- graph:狀態圖
LordJob(領主工作)
LordJob 定義羣體的整體目標,如:
- LordJob_DefendPoint:防禦某個點
- LordJob_Travel:旅行
- LordJob_ExitMap:離開地圖
LordToil(領主狀態)
LordToil 是狀態機中的狀態節點,定義羣體在該狀態下應該做什麼:
class LordToil_DefendPoint:
function UpdateAllDuties():
for each pawn in ownedPawns:
duty = CreateDefendDuty(pawn, defendPoint)
pawn.mindState.duty = duty
StateGraph(狀態圖)
StateGraph 定義狀態之間的轉換關係:
StateGraph {
lordToils: [Toil1, Toil2, Toil3]
transitions: [
Transition(Toil1 -> Toil2, [Trigger1, Trigger2]),
Transition(Toil2 -> Toil3, [Trigger3])
]
}
Trigger(觸發器)
Trigger 定義狀態轉換的條件:
class Trigger_FractionPawnsLost:
threshold: 0.5 // 50% 的 NPC 失去戰鬥力
function CheckSignal(lord, signal):
if signal.type == PawnLost:
lostFraction = lord.numPawnsLostViolently / lord.numPawnsEverGained
return lostFraction >= threshold
狀態轉換流程
function LordTick():
curJob.LordJobTick()
curLordToil.LordToilTick()
// 檢查所有從當前狀態出發的轉換
for each transition in graph.transitions:
if transition.sources.Contains(curLordToil):
if transition.CheckSignal(this, currentSignal):
// 執行轉換前的動作
transition.preActions.Execute()
// 轉換到新狀態
GotoToil(transition.target)
break
實際示例:襲擊事件
一個典型的襲擊事件狀態機:
初始狀態: LordToil_AssaultColony
├── Trigger: 50% NPC 失去戰鬥力
│ └── 轉換到: LordToil_PanicFlee
│
├── Trigger: 所有目標建築被摧毀
│ └── 轉換到: LordToil_ExitMap
│
└── Trigger: 玩家投降
└── 轉換到: LordToil_ExitMap
每個狀態會為所有 NPC 設置相應的 Duty(職責),NPC 的思考樹會根據 Duty 調整行為優先級。
第五層:戰鬥決策層(AttackTargetFinder)
戰鬥決策層負責在戰鬥中選擇最佳攻擊目標。它使用評分系統來評估每個潛在目標。
目標選擇流程
function BestAttackTarget(searcher, flags, validator):
// 1. 獲取所有潛在目標
candidates = GetPotentialTargets(searcher)
// 2. 過濾不符合條件的目標
validTargets = FilterTargets(candidates, flags, validator)
// 3. 計算每個目標的評分
scoredTargets = []
for each target in validTargets:
score = CalculateTargetScore(searcher, target)
scoredTargets.Add((target, score))
// 4. 根據評分選擇目標
return SelectBestTarget(scoredTargets)
目標過濾條件
目標必須滿足以下條件才能被考慮:
- 敵對關係:必須是敵對目標
- 距離範圍:在攻擊範圍內
- 視線:如果需要,必須有視線
- 威脅狀態:目標必須是活躍威脅
- 可達性:如果要求,目標必須可達
評分系統
評分考慮多個因素:
function CalculateTargetScore(searcher, target):
score = 0
// 基礎威脅值
score += target.ThreatValue
// 距離因子(越近分數越高)
distance = Distance(searcher, target)
score += 1.0 / (distance + 1.0) * distanceWeight
// 目標類型偏好
if target.IsPawn:
score += pawnTargetBonus
if target.IsBuilding:
score += buildingTargetBonus
// 友軍誤傷懲罰
friendlyFireRisk = CalculateFriendlyFireRisk(searcher, target)
score -= friendlyFireRisk * friendlyFirePenalty
// 目標當前狀態
if target.IsDowned:
score -= downedPenalty
if target.IsBurning:
score -= burningPenalty
return score
友軍誤傷計算:
- 人類/機械:每個 +18 分懲罰
- 動物:每個 +7 分懲罰
- 非 NPC 物體:每個 +10 分懲罰
- 自己:+40 分懲罰
遠程 vs 近戰
系統會根據攻擊類型採用不同的選擇策略:
遠程攻擊:
- 優先選擇可以立即射擊的目標
- 考慮射擊位置(CastPosition)
- 評估友軍誤傷風險
近戰攻擊:
- 優先選擇距離最近的目標
- 考慮移動成本
- 評估近戰威脅等級
層次間協作機制
五個層次並非獨立工作,而是通過精心設計的接口相互協作。下面詳細説明它們如何協同工作。
思考層 → 工作層
思考層通過 JobGiver 生成 Job,傳遞給工作層:
ThinkTree 遍歷
↓
JobGiver.TryGiveJob()
↓
創建 Job 對象
↓
JobTracker.StartJob()
↓
創建 JobDriver
↓
執行 Toil 序列
示例:NPC 決定攻擊敵人
- ThinkTree 遍歷到
ThinkNode_JobGiver_Attack - JobGiver 調用
AttackTargetFinder.BestAttackTarget()找到目標 - 創建
Job_AttackMelee,目標設置為找到的敵人 - JobTracker 創建
JobDriver_AttackMelee - JobDriver 開始執行攻擊 Toil
工作層 → 路徑層
當工作需要移動時,會調用路徑查找:
JobDriver.Toil_Goto
↓
pawn.pather.StartPath(destination)
↓
PathFinder.FindPath()
↓
返回 PawnPath
↓
Pawn_PathFollower 執行移動
示例:NPC 執行"去攻擊"工作
- JobDriver 的第一個 Toil 是
Toils_Goto.Goto(target) - Toil 調用
pawn.pather.StartPath(target.Position) - PathFinder 計算路徑
- PathFollower 每 tick 移動一步
羣體層 → 思考層
羣體層通過 Duty 影響思考層的決策:
Lord 設置 Duty
↓
pawn.mindState.duty = newDuty
↓
ThinkTree 中的條件節點檢查 Duty
↓
根據 Duty 調整行為優先級
示例:襲擊事件中的 NPC
- Lord 處於
LordToil_AssaultColony狀態 - 為所有 NPC 設置
PawnDuty,duty.focus = 玩家基地 - NPC 的思考樹中有
ThinkNode_Conditional_HasDuty - 該節點激活,NPC 優先執行與 Duty 相關的行為(攻擊、破壞等)
戰鬥層 → 工作層
戰鬥層為工作層提供目標選擇服務:
JobGiver 需要攻擊目標
↓
調用 AttackTargetFinder.BestAttackTarget()
↓
返回最佳目標
↓
創建攻擊 Job
完整協作流程示例
假設一個 NPC 參與襲擊事件,需要攻擊玩家基地:
1. 羣體層(Lord):
- Lord 處於 "AssaultColony" 狀態
- 為 NPC 設置 Duty:攻擊玩家基地
2. 思考層(ThinkTree):
- 檢查 Duty,發現需要攻擊
- JobGiver_Attack 被激活
3. 戰鬥層(AttackTargetFinder):
- JobGiver 調用 BestAttackTarget()
- 找到最佳攻擊目標(玩家 NPC)
4. 工作層(Job):
- 創建 Job_AttackMelee
- JobDriver 開始執行
5. 路徑層(PathFinder):
- JobDriver 的第一個 Toil 需要移動到目標
- 調用 PathFinder 計算路徑
- PathFollower 執行移動
6. 工作層繼續:
- 到達目標後,執行攻擊 Toil
- 循環攻擊直到目標死亡或工作被中斷
7. 羣體層監控:
- 如果 NPC 死亡,Lord 收到通知
- 檢查是否觸發狀態轉換(如撤退)
實際案例:完整行為流程
讓我們通過一個完整的遊戲場景來理解整個 AI 系統如何協同工作。
場景:NPC 襲擊玩家基地
階段 1:襲擊開始
羣體層初始化:
Lord 創建
├── LordJob = LordJob_AssaultColony
├── 初始狀態 = LordToil_AssaultColony
└── 添加所有襲擊者 NPC 到 ownedPawns
為每個 NPC 設置 Duty:
for each pawn in ownedPawns:
duty = new PawnDuty {
def = DutyDefOf.AssaultColony,
focus = playerBaseCenter,
radius = 50
}
pawn.mindState.duty = duty
階段 2:NPC 決策
思考樹遍歷(以襲擊者 NPC 為例):
ThinkNode_Priority (根節點)
├── ThinkNode_Conditional_HasDuty
│ ├── 檢查:pawn.mindState.duty != null ✓
│ └── ThinkNode_SubtreesByTag
│ └── 根據 Duty 類型加載相應子樹
│ └── ThinkNode_JobGiver_Attack
│ ├── 調用 AttackTargetFinder.BestAttackTarget()
│ ├── 找到目標:玩家 NPC "Alice"
│ └── 返回 Job_AttackMelee(Alice)
階段 3:目標選擇
AttackTargetFinder 工作流程:
1. 獲取所有潛在目標
candidates = [玩家NPC1, 玩家NPC2, 炮塔1, 建築1, ...]
2. 過濾目標
- 檢查敵對關係 ✓
- 檢查距離(在武器射程內)✓
- 檢查視線 ✓
- 檢查威脅狀態 ✓
validTargets = [玩家NPC1, 玩家NPC2, 炮塔1]
3. 計算評分
玩家NPC1: 威脅值=50, 距離=20, 誤傷風險=0 → 總分=70
玩家NPC2: 威脅值=45, 距離=25, 誤傷風險=18 → 總分=52
炮塔1: 威脅值=60, 距離=30, 誤傷風險=0 → 總分=60
4. 選擇最佳目標
返回:玩家NPC1(分數最高)
階段 4:工作執行
Job 創建與啓動:
Job job = new Job_AttackMelee {
targetA = 玩家NPC1,
maxNumMeleeAttacks = -1, // 無限攻擊
expiryInterval = -1
}
JobTracker.StartJob(job)
JobDriver 執行 Toil 序列:
Toil 1: Toils_Goto.Goto(targetA, PathEndMode.Touch)
├── 調用 pawn.pather.StartPath(玩家NPC1.Position)
├── PathFinder 計算路徑
│ ├── 起點:襲擊者位置 (100, 0, 50)
│ ├── 終點:玩家NPC1位置 (150, 0, 80)
│ ├── 路徑:[(100,0,50) → (110,0,55) → ... → (150,0,80)]
│ └── 總成本:450 ticks
└── PathFollower 開始移動
Toil 2: Toils_Combat.WaitUntilSuitableToAttack(targetA)
├── 檢查:是否到達目標附近?
├── 檢查:目標是否仍然有效?
└── 等待直到條件滿足
Toil 3: Toils_Combat.MeleeAttack(targetA)
├── 執行近戰攻擊動畫
├── 計算傷害
│ ├── 基礎傷害值(根據武器和 NPC 屬性)
│ ├── 隨機因子(0.8 - 1.2 倍)
│ ├── 護甲穿透值
│ └── 傷害類型(切割、鈍擊、刺擊等)
├── 應用傷害到目標
│ ├── 檢查是否命中(考慮命中率)
│ ├── 檢查是否被閃避
│ ├── 如果命中:調用 target.TakeDamage(damageInfo)
│ ├── 更新戰鬥日誌
│ └── 播放音效和視覺效果
└── 跳回 Toil 2(循環攻擊)
階段 5:狀態更新與反饋
工作完成檢查:
每 tick 檢查:
├── 目標是否死亡? → 結束工作
├── 目標是否離開地圖? → 結束工作
├── 工作是否過期? → 結束工作
└── 否則繼續攻擊循環
羣體層狀態更新:
Lord 監控:
├── 統計傷亡情況
├── 檢查觸發條件
│ ├── 如果 50% NPC 失去戰鬥力 → 觸發撤退
│ ├── 如果目標完成 → 觸發離開地圖
│ └── 否則繼續當前狀態
└── 更新所有 NPC 的 Duty
總結
RimWorld 的 NPC AI 系統通過五個層次的精心設計,實現了複雜而高效的行為決策和執行機制。每個層次都有其獨特的職責,同時通過清晰的接口相互協作。
系統特點回顧
-
分層架構:
- 思考決策層:決定"做什麼"
- 路徑導航層:解決"怎麼走"
- 工作執行層:實現"怎麼做"
- 羣體協調層:協調"一起做"
- 戰鬥決策層:選擇"打哪個"
-
優先級驅動:
- 思考樹按優先級順序評估節點
- 高優先級行為(如戰鬥)會中斷低優先級行為(如工作)
- 確保 NPC 能夠及時響應緊急情況
-
狀態管理:
- Lord 系統使用狀態機管理羣體行為
- 通過 Trigger 實現狀態轉換
- 支持複雜的事件流程(如襲擊、商隊等)
-
性能優化:
- A* 算法使用啓發式函數加速路徑查找
- 區域級路徑優化減少大距離路徑的計算量
- 思考樹使用短路機制避免不必要的評估
關鍵概念速查
| 概念 | 層級 | 作用 |
|---|---|---|
| ThinkNode | 第一層 | 決策樹節點,決定行為選擇 |
| ThinkTree | 第一層 | 完整的決策邏輯樹 |
| JobGiver | 第一層 | 連接思考層和工作層的橋樑 |
| PathFinder | 第二層 | A* 路徑查找算法實現 |
| Job | 第三層 | 任務的抽象描述 |
| JobDriver | 第三層 | 任務的執行驅動 |
| Toil | 第三層 | 最小的執行單元 |
| Lord | 第四層 | 羣體管理器 |
| LordToil | 第四層 | 羣體狀態節點 |
| StateGraph | 第四層 | 狀態轉換圖 |
| AttackTargetFinder | 第五層 | 戰鬥目標選擇器 |
設計優勢
- 模塊化:每個層次可以獨立理解、測試和修改
- 可擴展:新行為可以通過添加新的 ThinkNode 或 JobDriver 實現
- 可維護:清晰的層次劃分使代碼易於維護
- 性能:優化的算法和緩存機制確保遊戲流暢運行
實際應用
理解這個 AI 系統對於:
- Mod 開發者:可以創建自定義行為、工作類型和羣體事件
- 遊戲設計師:可以調整 AI 參數來平衡遊戲難度
- 玩家:可以更好地理解 NPC 行為,制定策略
進一步探索
如果你想深入瞭解某個特定方面,可以:
- 查看源代碼中的具體實現細節
- 通過遊戲日誌觀察 AI 決策過程
- 使用開發工具(如 Dev Mode)測試不同場景
- 參考 RimWorld 的 Mod 開發文檔