NPC行为演进
概述
游戏NPC(Non-Player Character)的行为控制技术经历了从简单状态机到LLM驱动的革命性演进。本文梳理这一演进路径,比较各技术方案的优劣。
有限状态机 (FSM)
有限状态机是最早和最简单的NPC行为控制方法。
基本结构
\[\text{FSM} = (S, s_0, \Sigma, \delta, F)\]
- \(S\): 状态集合(如 Idle, Patrol, Chase, Attack)
- \(s_0\): 初始状态
- \(\Sigma\): 输入事件集合(如 see_enemy, lose_sight, health_low)
- \(\delta: S \times \Sigma \rightarrow S\): 状态转移函数
- \(F\): 终止状态集合
stateDiagram-v2
[*] --> Idle
Idle --> Patrol: start_patrol
Patrol --> Chase: see_enemy
Patrol --> Idle: patrol_complete
Chase --> Attack: in_range
Chase --> Patrol: lose_sight
Attack --> Chase: out_of_range
Attack --> Flee: health_low
Flee --> Idle: safe_distance
实现示例
class FSM_NPC:
def __init__(self):
self.state = "idle"
self.health = 100
def update(self, perception):
if self.state == "idle":
if perception.patrol_timer_expired:
self.state = "patrol"
elif self.state == "patrol":
if perception.enemy_visible:
self.state = "chase"
self.target = perception.nearest_enemy
elif perception.patrol_complete:
self.state = "idle"
elif self.state == "chase":
if self.distance_to(self.target) < self.attack_range:
self.state = "attack"
elif not perception.enemy_visible:
self.state = "patrol"
elif self.state == "attack":
if self.health < 20:
self.state = "flee"
elif self.distance_to(self.target) > self.attack_range:
self.state = "chase"
FSM的局限性
| 问题 | 说明 |
|---|---|
| 状态爆炸 | 复杂行为需要大量状态,$ |
| 刚性转移 | 缺乏灵活性,行为模式固定 |
| 难以扩展 | 新增行为需修改大量转移逻辑 |
| 缺乏上下文 | 无法考虑历史和上下文信息 |
分层有限状态机(HFSM)通过状态嵌套部分缓解了状态爆炸问题,但根本局限仍在。
行为树 (Behavior Tree)
行为树是游戏AI的工业标准,被广泛用于AAA游戏(如Halo、Unreal Engine内置)。
核心节点类型
graph TD
Root[Root<br/>→] --> Sel1[Selector<br/>?]
Sel1 --> Seq1[Sequence<br/>→ Combat]
Sel1 --> Seq2[Sequence<br/>→ Patrol]
Sel1 --> Act1[Action<br/>Idle]
Seq1 --> Cond1[Condition<br/>Enemy Visible?]
Seq1 --> Sel2[Selector<br/>? Attack/Flee]
Sel2 --> Seq3[Sequence<br/>→ Attack]
Sel2 --> Act2[Action<br/>Flee]
Seq3 --> Cond2[Condition<br/>Health > 20?]
Seq3 --> Act3[Action<br/>Attack Enemy]
Seq2 --> Act4[Action<br/>Move to Waypoint]
Seq2 --> Cond3[Condition<br/>At Waypoint?]
Seq2 --> Act5[Action<br/>Wait]
节点类型详解
| 节点类型 | 符号 | 行为 | 子节点失败时 | 子节点成功时 |
|---|---|---|---|---|
| Sequence | → | 依次执行所有子节点 | 立即返回失败 | 继续下一个 |
| Selector | ? | 尝试直到一个成功 | 尝试下一个 | 立即返回成功 |
| Decorator | ◇ | 修饰子节点行为 | 依装饰器而定 | 依装饰器而定 |
| Condition | ▢ | 检查条件 | - | - |
| Action | ● | 执行动作 | - | - |
Tick机制
行为树通过 tick 驱动,每帧从根节点开始遍历:
\[\text{tick}(node) \rightarrow \{\text{SUCCESS}, \text{FAILURE}, \text{RUNNING}\}\]
- SUCCESS: 节点执行成功
- FAILURE: 节点执行失败
- RUNNING: 节点仍在执行中(如移动到目标点的过程中)
class BehaviorTree:
def tick(self, node, context):
if isinstance(node, Sequence):
for child in node.children:
result = self.tick(child, context)
if result != Status.SUCCESS:
return result
return Status.SUCCESS
elif isinstance(node, Selector):
for child in node.children:
result = self.tick(child, context)
if result != Status.FAILURE:
return result
return Status.FAILURE
elif isinstance(node, Decorator):
child_result = self.tick(node.child, context)
return node.modify(child_result)
elif isinstance(node, Action):
return node.execute(context)
elif isinstance(node, Condition):
return Status.SUCCESS if node.check(context) else Status.FAILURE
常用Decorator
- Inverter: 反转子节点结果
- Repeater: 重复执行子节点N次
- Timeout: 超时返回失败
- Cooldown: 冷却期内不执行
- ForceSuccess: 始终返回成功
目标导向行动规划 (GOAP)
GOAP(Goal-Oriented Action Planning)受STRIPS规划器启发,让NPC自主规划达到目标的行动序列。
核心概念
\[\text{GOAP} = (\mathcal{S}, \mathcal{A}, \mathcal{G})\]
- \(\mathcal{S}\): 世界状态(键值对集合)
- \(\mathcal{A}\): 行动集合,每个行动包含前置条件和效果
- \(\mathcal{G}\): 目标集合,每个目标包含期望的世界状态
class GOAPAction:
def __init__(self, name, cost, preconditions, effects):
self.name = name
self.cost = cost # 行动代价
self.preconditions = preconditions # 前置条件 {key: value}
self.effects = effects # 效果 {key: value}
# 定义行动
actions = [
GOAPAction("get_axe", cost=2,
preconditions={"has_axe": False},
effects={"has_axe": True}),
GOAPAction("chop_tree", cost=4,
preconditions={"has_axe": True},
effects={"has_wood": True}),
GOAPAction("build_fire", cost=3,
preconditions={"has_wood": True},
effects={"is_warm": True}),
GOAPAction("find_shelter", cost=6,
preconditions={},
effects={"is_warm": True}),
]
# 目标
goal = {"is_warm": True}
# A*规划器会找到: get_axe -> chop_tree -> build_fire (cost=9)
# 而非: find_shelter (cost=6) — 取决于当前状态
A*规划
GOAP使用反向A*搜索从目标状态回溯到当前状态:
\[f(n) = g(n) + h(n)\]
- \(g(n)\): 从起始到当前节点的实际代价
- \(h(n)\): 启发式估计(通常为未满足条件数量)
graph RL
G["目标: is_warm=True"] --> A3["build_fire<br/>cost=3"]
A3 --> A2["chop_tree<br/>cost=4"]
A2 --> A1["get_axe<br/>cost=2"]
A1 --> S["当前状态<br/>has_axe=False"]
G --> A4["find_shelter<br/>cost=6"]
A4 --> S2["当前状态<br/>(无前置条件)"]
GOAP的优势与局限
优势:
- 行动解耦:新增行动无需修改其他逻辑
- 自动规划:NPC自主发现行动序列
- 动态适应:环境变化时自动重新规划
局限:
- 状态空间需预定义
- 启发式函数设计影响性能
- 不擅处理连续动作和模糊目标
效用AI (Utility AI)
效用AI通过评分函数为每个可能的行动计算效用值,选择效用最高的行动:
\[U(a) = \sum_{i=1}^{n} w_i \cdot f_i(a)\]
其中: - \(a\) 是候选行动 - \(f_i(a)\) 是第 \(i\) 个评估因子对行动 \(a\) 的评分 - \(w_i\) 是对应权重
评分曲线类型
常用的评分函数 \(f_i(a)\) 形式:
- 线性: \(f(x) = mx + b\)
- 二次: \(f(x) = ax^2 + bx + c\)
- Logistic: \(f(x) = \frac{1}{1 + e^{-k(x - x_0)}}\)
- 指数衰减: \(f(x) = e^{-\lambda x}\)
示例
class UtilityAI:
def select_action(self, npc, context):
actions = {
"eat": self.score_eat(npc),
"sleep": self.score_sleep(npc),
"fight": self.score_fight(npc, context),
"flee": self.score_flee(npc, context),
"socialize": self.score_socialize(npc, context),
}
return max(actions, key=actions.get)
def score_eat(self, npc):
hunger = npc.hunger / 100.0 # 归一化到[0,1]
# Logistic曲线:饥饿度高时急剧上升
return 1.0 / (1.0 + math.exp(-10 * (hunger - 0.6)))
def score_fight(self, npc, context):
if not context.enemy_visible:
return 0.0
health_factor = npc.health / 100.0
weapon_factor = 1.0 if npc.has_weapon else 0.3
enemy_weakness = 1.0 - context.enemy_health / 100.0
return 0.4 * health_factor + 0.3 * weapon_factor + 0.3 * enemy_weakness
def score_flee(self, npc, context):
if not context.enemy_visible:
return 0.0
danger = 1.0 - npc.health / 100.0
return danger * 0.8 # 血量低时逃跑倾向高
LLM驱动的NPC
LLM的出现彻底改变了NPC设计范式,从预定义行为转向生成式行为。
核心优势
- 自由对话: 不再局限于预设对话树
- 动态目标: 根据上下文生成合理的目标
- 个性化: 通过人设prompt实现独特人格
- 涌现行为: 多NPC交互产生意料之外的行为
架构
class LLM_NPC:
def __init__(self, name, personality, backstory):
self.name = name
self.personality = personality
self.backstory = backstory
self.memory_stream = MemoryStream()
self.current_plan = []
def generate_response(self, player_input, context):
"""生成对话回复"""
relevant_memories = self.memory_stream.retrieve(player_input)
prompt = f"""You are {self.name}. {self.personality}
Backstory: {self.backstory}
Relevant memories:
{format_memories(relevant_memories)}
Current situation: {context}
{player_input}
Respond in character:"""
response = call_llm(prompt)
self.memory_stream.add(f"Player said: {player_input}")
self.memory_stream.add(f"I responded: {response}")
return response
def decide_action(self, perception):
"""决定下一步行动"""
memories = self.memory_stream.retrieve(str(perception))
prompt = f"""You are {self.name}. Given your personality,
memories, and current situation, what should you do next?
Available actions: {perception.available_actions}
Current observation: {perception.description}
Relevant memories: {format_memories(memories)}
Choose one action and explain briefly:"""
return call_llm(prompt)
技术对比总表
| 特性 | FSM | 行为树 | GOAP | 效用AI | LLM NPC |
|---|---|---|---|---|---|
| 可预测性 | 极高 | 高 | 中 | 中 | 低 |
| 灵活性 | 低 | 中 | 高 | 高 | 极高 |
| 可扩展性 | 差 | 好 | 好 | 好 | 好 |
| 对话能力 | 无 | 无 | 无 | 无 | 原生 |
| 开发成本 | 低 | 中 | 中 | 中 | 低(调用) |
| 运行成本 | 极低 | 低 | 中 | 低 | 高(API) |
| 调试难度 | 易 | 中 | 中 | 中 | 难 |
| 涌现行为 | 无 | 无 | 有限 | 有限 | 丰富 |
| 适用场景 | 简单NPC | AAA游戏 | 策略游戏 | 模拟游戏 | 开放世界 |
| 代表案例 | 早期游戏 | Halo, UE | F.E.A.R | Sims | Smallville |
演进趋势
graph LR
A[FSM<br/>1990s] --> B[行为树<br/>2000s]
B --> C[GOAP<br/>2004+]
B --> D[效用AI<br/>2010s]
C --> E[混合架构<br/>2015+]
D --> E
E --> F[LLM驱动<br/>2023+]
style F fill:#f9f,stroke:#333,stroke-width:2px
混合架构趋势
现代NPC系统越来越多地采用混合架构:
- LLM + 行为树: LLM处理对话和高层决策,行为树执行具体动作
- LLM + GOAP: LLM生成目标,GOAP规划行动序列
- LLM + 效用AI: LLM评估情境,效用AI选择行动
- 分层架构: 上层LLM(战略),中层GOAP/效用AI(战术),底层FSM/行为树(执行)
\[\text{Decision} = \text{LLM}_{\text{strategic}}(\text{context}) \rightarrow \text{GOAP}_{\text{tactical}}(\text{goal}) \rightarrow \text{BT}_{\text{execution}}(\text{plan})\]
总结
NPC行为控制从确定性的FSM演进到生成式的LLM,每一代技术都解决了前一代的核心局限,同时引入新的挑战。未来的方向是混合架构——利用LLM的灵活性和传统方法的可控性,构建既智能又可靠的NPC系统。