UE5.8 StateTree 状态树专题系列

UE5.8 StateTree 专题(十一):StateTree 和 Mass,批量 AI 里怎么用

用新手能懂的方式解释 UMassStateTreeTrait、FMassStateTreeInstanceFragment、MassStateTreeSubsystem、MassStateTreeExecutionContext 与普通 Actor StateTree 的差异。

总览

Mass 里的 StateTree 是最容易把新手劝退的一块。普通 Actor StateTree 是“一个 Actor 上挂一个组件,然后组件 Tick 它”。Mass 不是这样。Mass 关心的是成百上千个 Entity,不能给每个 Entity 都挂 Actor 组件,于是它用 Fragment 存状态树实例句柄,再由 Mass Processor 批量推进。

先别背类名,先记住一句话:普通 StateTree 像“每个 NPC 自己带一本任务清单”,Mass StateTree 像“调度员拿着一批人的任务清单,一批一批推进”。

UE5.8 StateTree 专题(十一):StateTree 和 Mass,批量 AI 里怎么用 配图
Mass 里的 StateTree 不是每个实体挂组件,而是用 Fragment 保存实例句柄,由 Mass Processor 批量推进。

普通 Actor 和 Mass 的差异

问题 Actor StateTree Mass StateTree
运行入口 UStateTreeComponent / UStateTreeAIComponent UMassStateTreeProcessor
实例数据放哪 组件里的 FStateTreeInstanceData FMassStateTreeInstanceFragment 持有句柄
外部数据 Actor、AIController、Component Fragment、Subsystem、Entity View
Tick 粒度 一个组件一个组件 Tick 按 Chunk/Query 批量 Tick
适合对象 少量重要 NPC 大量群众、车辆、简单 AI

所以 Mass StateTree 不是“给 Mass Entity 挂了一个 StateTreeComponent”。它没有那个组件。它走的是 Mass 的 ECS 数据通道。

源码依据

UMassStateTreeTrait 是把 StateTree 加进 Mass Entity 配置的入口,属性上要求使用 MassStateTreeSchemaFMassStateTreeInstanceFragment 保存 FMassStateTreeInstanceHandle 和上次更新时间。FMassStateTreeSharedFragment 保存共享的 UStateTree 资产。FMassStateTreeExecutionContext 继承 FStateTreeExecutionContext,额外提供 Mass Entity、Mass ExecutionContext 和 EntityManager。UMassStateTreeProcessor 才是真正批量 Tick StateTree 的处理器。

架构分析

Mass StateTree 有四个关键角色:

角色 作用 新手理解
Trait 在 Entity Config 里声明“这类实体有 StateTree 行为” 配方
Shared Fragment 多个实体共享同一个 StateTree 资产 公共剧本
Instance Fragment 每个实体自己的运行状态句柄 当前演到哪一页
Processor 每帧或被 Signal 唤醒后推进一批实体 调度员

为什么要这么绕?因为 Mass 的核心目标是批量。共享数据只存一份,个体差异放 Fragment,处理器按 Query 遍历 Chunk。这样 1000 个行人可以共享一套行为资产,但每个人仍然有自己的“当前状态”和“实例数据”。

使用案例

案例:城市里有 500 个路人。你想让他们做三件事:沿路走、发现座椅就坐下、坐一会儿继续走。

不要给 500 个路人都生成完整 AIController。可以用 Mass:

  1. Entity Config 加移动相关 Trait。
  2. 加 SmartObject 相关 Trait。
  3. Mass StateTree Trait
  4. StateTree 使用 Mass Schema。
  5. 状态设计为 WalkFindSeatUseSeatResumeWalk

状态表:

State 输入数据 Task Transition
Walk 当前位置、ZoneGraph lane 沿 ZoneGraph 前进 看到可用座椅 -> FindSeat
FindSeat SmartObject 查询结果 Claim SmartObject 成功 -> UseSeat,失败 -> Walk
UseSeat Claim Handle、等待时间 Use SmartObject / 等待 完成 -> ResumeWalk
ResumeWalk 目标 lane 释放交互并继续移动 成功 -> Walk

数据怎么喂给 StateTree

普通 AI 里,你可能会从 Pawn 上读 HealthComponent、从 AIController 上读黑板。Mass 里要换脑子:数据应当来自 Fragment 或 Subsystem。

需求 Mass 推荐数据来源
当前位置 Transform Fragment
速度/移动意图 Movement Fragment
可交互目标 SmartObject Fragment 或 Subsystem 查询
当前行为状态 StateTree Instance Fragment
个体配置 Shared Fragment / Const Shared Fragment

这也是为什么 Mass StateTree 的 Task 不能随便假设自己能拿到 Actor。低 LOD 群众可能根本没有 Actor 表现体。

代码演示

Mass Processor 内部大致会做这样的事:拿到每个实体的 StateTree 实例数据,构造 FMassStateTreeExecutionContext,设置当前 Entity,然后 Tick。

UE::MassBehavior::ForEachEntityInChunk(Context, MassStateTreeSubsystem,
    [TimeInSeconds](FMassStateTreeExecutionContext& StateTreeContext,
                    FMassStateTreeInstanceFragment& StateTreeFragment)
    {
        const float DeltaTime = FloatCastChecked<float>(
            TimeInSeconds - StateTreeFragment.LastUpdateTimeInSeconds,
            /* Precision */ 1.0 / 256.0);
        StateTreeFragment.LastUpdateTimeInSeconds = TimeInSeconds;
        StateTreeContext.Tick(DeltaTime);
    });

这段不是让你照抄到项目里,而是帮助理解:Mass StateTree 的执行上下文里同时有 StateTree 的上下文,也有 Mass 的 Entity 上下文。写 Mass Task 时要从这个上下文取 Fragment 或 Subsystem,而不是去找组件。

项目落地

哪些逻辑适合 Mass StateTree:

  1. 大量单位共享相似行为,例如行人、车辆、动物群、战场小兵。
  2. 行为可以拆成简单状态,不依赖复杂蓝图 Actor。
  3. 低 LOD 时也能运行,不需要每个实体都有 SkeletalMesh Actor。
  4. 数据可以 Fragment 化,比如目标位置、速度、等待时间、交互句柄。

哪些逻辑不适合一开始就上 Mass StateTree:

  1. Boss AI。
  2. 强依赖复杂动画蓝图、Montage、剧情事件的角色。
  3. 每个单位有大量独特蓝图组件逻辑。
  4. 需要频繁和 UI、任务系统、背包系统强耦合的少量角色。

常见坑

不要把 Actor StateTree 的 Task 原封不动搬到 Mass。它可能依赖 AIController、Pawn、Component,而 Mass Entity 不一定有这些。不要在 Mass Task 里强制 Spawn Actor 做逻辑主体,表现层可以 Actor 化,但决策层要尽量留在 Fragment。不要忽视 Signal 和 Tick 预算,大量实体每帧全量 Tick 会把 Mass 的优势吃掉。不要把所有个体差异都塞进 StateTree 参数,能放 Fragment 的长期状态就放 Fragment。

源码路径索引

  • MassAI/Source/MassAIBehavior/Public/MassStateTreeTrait.h
  • MassAI/Source/MassAIBehavior/Public/MassStateTreeFragments.h
  • MassAI/Source/MassAIBehavior/Public/MassStateTreeExecutionContext.h
  • MassAI/Source/MassAIBehavior/Public/MassStateTreeSchema.h