跳转至

行为树、GOAP 与 Utility AI

常见现象

做 NPC 时最容易出现两个问题:敌人站着不动,或者目标一变就来回切换。前者通常是条件写漏了,后者是决策频率和优先级没处理好。

行为树、GOAP 和 Utility AI 都能解决“下一步做什么”,但它们的控制方式不一样。先看项目更需要可预测性,还是需要组合能力。1

行为树

行为树用节点描述执行顺序,常见节点只有三种状态:

public enum Status
{
    Failure,
    Success,
    Running
}

Selector 依次尝试子节点,有一个成功就返回;Sequence 依次执行,全部成功才返回成功。条件节点、冷却和超时通常放在装饰器里。

Selector
├─ Sequence: 有敌人
│  ├─ 选择武器
│  ├─ 移动到掩体
│  └─ 开火
└─ Sequence: 巡逻
   ├─ 选择路径点
   └─ 移动

行为树适合战斗、巡逻、Boss 阶段这种层级明确的逻辑。设计师能直接看出执行顺序,运行时也容易可视化。Unity 也提供了 Behavior 包,可以直接搭可视化行为树。3

缺点是条件多起来后,分支会迅速膨胀。尤其是“有敌人但没弹药”“有掩体但队友占了”这类组合,最后容易改出一棵很难维护的树。2

GOAP

GOAP 把行为拆成带前置条件和效果的动作,再由规划器搜索一条能达成目标的序列。

public readonly struct AgentAction
{
    public readonly string Name;
    public readonly float Cost;
    public readonly Func<WorldState, bool> CanRun;
    public readonly Func<WorldState, WorldState> Apply;
}

例如“装弹”的前置条件是 HasAmmo == false,效果是把 HasAmmo 设为 true。规划器从当前状态出发,尝试动作,累计成本,直到满足目标。

它适合战术、生存和资源管理类 AI,因为动作可以组合出设计者没有手写过的序列。代价是搜索空间会随动作数量增长,必须限制深度、节点数和单帧预算。

一个常见的优化是分层:先决定“进攻/撤退/补给”这种高层目标,再在局部动作里做小规模规划。不要让每个 NPC 每帧都从零开始搜索。

Utility AI

Utility AI 给每个候选行为打分,选当前分数最高的一个:

public static float ScoreAttack(
    float distance,
    float healthRatio,
    float ammoRatio,
    float threat)
{
    var range = 1f - math.saturate(distance / 20f);
    var sustain = math.saturate(ammoRatio * 0.7f + healthRatio * 0.3f);
    return range * 0.45f + sustain * 0.2f + threat * 0.35f;
}

这种写法很适合混合多个因素,比如距离、血量、弹药和威胁。行为过渡也比较自然。

问题是权重要反复调,而且分数接近时容易抖动。可以加一点滞回:

if (newScore > currentScore + 0.1f)
    SwitchBehavior();

这样只有新行为明显更好时才切换,避免 NPC 在开火和找掩体之间来回跳。

怎么选

方案 可预测性 组合能力 适合场景
行为树 高 中 战斗、巡逻、Boss
GOAP 中 高 战术、生存、资源管理
Utility AI 中 中 开放世界、群体行为
HTN 高 中高 潜入、基地、战役

HTN 介于行为树和 GOAP 之间,由设计者定义任务如何分解,执行结果更可控。这里不展开,实际项目可以把它理解为“带规划的层级任务”。

决策频率

NPC 不需要每个渲染帧都重新思考。比较常见的分配是:

  • 感知更新:5–15 Hz,分帧执行。
  • 目标选择:2–10 Hz,或者事件触发。
  • 路径重算:目标或地形变化时执行。
  • 动画切换:由行为事件驱动。

把 AI 分成不同更新组,错开每帧的峰值,往往比单纯优化评分函数更有效。

让决策过程能被看见

做一个运行时调试面板,显示当前行为、候选分数、黑板关键字段和最近一次决策原因。很多“AI 看起来傻”的问题,不是算法不对,而是某个条件一直为真,或者目标 ID 在对象池复用时没有清掉。

调试信息最好和随机种子一起记录。固定种子重放同一场战斗,能很快复现问题。