DYNAMIC EXPLAINER / ARTICLE FLOW

把章节变成可单步观察的过程

01 / 11
STEP 01 / CODE / LOGIC

先说结论

FSM 直接定义状态怎样切换,行为树组织每帧决策流程,效用 AI 给候选行为打分,GOAP 根据前置条件和效果搜索行动序列。它们不是从低级到高级的替代关系,而是用不同方式回答“现在做什么”。

世界状态:看到玩家、门已锁、持有钥匙、弹药 0
FSM:从 Chase 切到 OpenDoor 或 SearchAmmo
行为树:先尝试开门分支,失败后进入找弹药分支
效用 AI:OpenDoor=80,SearchAmmo=65,选择开门
GOAP:规划 UseKey -> OpenDoor -> Chase

分类:游戏 AI 与逻辑建模

先说结论

FSM 直接定义状态怎样切换,行为树组织每帧决策流程,效用 AI 给候选行为打分,GOAP 根据前置条件和效果搜索行动序列。它们不是从低级到高级的替代关系,而是用不同方式回答“现在做什么”。

同一个敌人遇到锁门

世界状态:看到玩家、门已锁、持有钥匙、弹药 0
FSM:从 Chase 切到 OpenDoor 或 SearchAmmo
行为树:先尝试开门分支,失败后进入找弹药分支
效用 AI:OpenDoor=80,SearchAmmo=65,选择开门
GOAP:规划 UseKey -> OpenDoor -> Chase

同一玩法可以组合使用,例如 GOAP 选出计划,行为树执行单个动作,FSM 管理动画状态。关键是明确每一层拥有哪种状态和失败反馈。

核心问题

复杂 AI 或流程逻辑不是只能靠一堆 if else

常见建模工具包括:

普通状态机
分层状态机
并行状态机
行为树
效用 AI
GOAP

它们不是谁完全替代谁,而是解决不同复杂度、不同类型的问题。

最重要的判断是:

这是流程问题,还是决策问题,还是规划问题?

普通状态机 FSM

核心模型:

当前状态 + 条件 = 下一个状态

例子:

Idle -> 发现玩家 -> Chase
Chase -> 进入攻击距离 -> Attack
Attack -> 攻击结束 -> Cooldown
Cooldown -> 冷却结束 -> Chase
任意状态 -> 死亡 -> Dead

C# 常见写法:

public enum MonsterState
{
    Idle,
    Chase,
    Attack,
    Cooldown,
    Dead
}

private MonsterState state;

void Update()
{
    switch (state)
    {
        case MonsterState.Idle:
            UpdateIdle();
            break;
        case MonsterState.Chase:
            UpdateChase();
            break;
        case MonsterState.Attack:
            UpdateAttack();
            break;
    }
}

适合:

状态少
流程明确
状态互斥
切换规则简单

优点:

简单直观
容易 Debug
当前状态一眼能看懂

缺点:

状态多了会膨胀
多个维度同时存在时不好处理

分层状态机 Hierarchical FSM

分层状态机把状态分层:

Alive
  Idle
  Chase
  Attack
  Stunned

Dead

父状态管公共规则,子状态管具体行为。

例如所有 Alive 状态都要检查死亡:

Alive.Update:
    if hp <= 0 -> Dead

子状态就不用每个都重复写死亡检查。

适合:

状态明显有层级
很多状态共享公共逻辑
普通 FSM 已经开始变乱

优点:

减少重复判断
状态结构更清楚
适合中大型流程

缺点:

实现比普通状态机复杂
父子状态进入/退出顺序要设计清楚

常见进入顺序:

Enter 父状态 -> Enter 子状态

常见退出顺序:

Exit 子状态 -> Exit 父状态

并行状态机 Parallel FSM

并行状态机解决多个状态维度同时存在的问题。

一个角色可能同时有:

移动状态:Idle / Walk / Run / Jump
战斗状态:Ready / Casting / Cooldown
控制状态:Normal / Stunned / Frozen
表情状态:Normal / Hurt / Angry

如果硬写成一个巨大枚举,会出现组合爆炸:

RunCastingNormal
RunCastingStunned
WalkCastingNormal
JumpCooldownFrozen

更好的方式是拆成多个状态机:

MovementState movementState;
CombatState combatState;
ControlState controlState;

适合:

多个状态维度可以同时存在
组合数量很多
不想写巨大 enum

优点:

避免组合爆炸
模块职责更清晰
更接近真实游戏角色结构

缺点:

多个状态机之间可能冲突
需要优先级或协调规则

例如:

ControlState = Stunned

此时移动状态机和战斗状态机都应该受到限制。

行为树 Behavior Tree

行为树不是当前状态切来切去,而是每次从树根开始做决策。

它更像:

按优先级检查行为,谁能执行就执行谁。

例子:

Selector
  IsDead? -> Dead
  IsStunned? -> Stunned
  CanAttack? -> Attack
  CanSeePlayer? -> Chase
  Patrol

常见节点:

Selector:选择一个成功的子节点
Sequence:一组步骤都成功才成功
Condition:条件判断
Action:执行行为
Decorator:修饰节点,比如冷却、取反、循环

适合:

AI 决策复杂
行为优先级明显
行为可以组合复用
策划需要可视化编辑

优点:

扩展 AI 行为比较自然
行为节点可复用
优先级清楚
很适合怪物 AI

缺点:

每帧从树根 tick,节点多了要注意性能
复杂树也会变难维护
长流程行为需要额外状态保存

一句话:

状态机更像流程图。
行为树更像决策树。

效用 AI Utility AI

效用 AI 不按固定优先级选行为,而是给每个行为打分。

例子:

攻击分 = 技能可用 + 距离合适 + 目标血量低
逃跑分 = 自己血量低 + 敌人太近
追击分 = 看见目标 + 距离较远
巡逻分 = 没有目标

然后:

谁分最高,执行谁。

适合:

行为不是绝对优先级
需要多个因素权衡
希望 AI 更柔性自然

优点:

行为更自然
容易调权重
适合多因素决策

缺点:

调参成本高
分数设计不好会出现奇怪行为
Debug 时要能看到各项分数

关键不是代码难,而是:

怎么设计评分公式。

GOAP 目标导向行动规划

GOAP 的思路是:

AI 有目标。
AI 自己规划一串行动达成目标。

每个行动有:

前置条件
执行效果
成本

例子:

行动:开火
前置条件:有武器、有弹药、目标在范围内
效果:玩家受伤
成本:2

如果目标是消灭玩家,AI 可能规划:

没有武器 -> 找武器 -> 靠近玩家 -> 开火

适合:

沙盒 AI
复杂 NPC
行动组合非常多
希望 AI 看起来会“想办法”

优点:

AI 很灵活
不用手写所有行为顺序
能根据环境变化重新规划

缺点:

实现复杂
性能更贵
Debug 更难
对普通怪物可能太重

选择建议

普通状态机:
适合简单互斥流程。

分层状态机:
适合有父子层级的流程。

并行状态机:
适合多个状态维度同时存在。

行为树:
适合优先级式 AI 决策。

效用 AI:
适合多因素评分决策。

GOAP:
适合目标导向的自动规划。

实际系统中经常组合使用:

角色动作流程:状态机 / 分层状态机
角色移动、战斗、控制:并行状态机
怪物行为选择:行为树
技能选择或目标选择:效用评分
复杂 NPC 规划:GOAP
Buff、异常状态:标签 / 独立状态模块

常见误解

  • 行为树不是“更高级的状态机”,两者表达状态和转换的方式不同。
  • 效用分最高只代表当前评分模型更偏好它,不代表行动一定能成功。
  • GOAP 生成计划后仍需要执行器处理动画、移动、失败和中断。

最重要的收获

流程问题:状态机。
决策问题:行为树 / 效用 AI。
规划问题:GOAP。