总览
Mass Framework 解决的问题不是“怎样再写一种 Actor”,而是“当世界里有几百、几千甚至更多相似对象时,怎样用数据批处理维持行为、表现和同步”。在 UE5.8 源码里,底层核心在 MassEntity,它提供 Entity、Fragment、Archetype、Query、Processor、Observer 和 CommandBuffer;应用层在 MassGameplay 和 MassAI,它们提供 Trait、Spawner、Movement、LOD、Representation、SmartObject、StateTree、Replication 等项目会直接使用的模块。
把它理解成 ECS 会更准确:FMassEntityHandle 是实体句柄,真正的数据在各种 FMassFragment、FMassTag、Shared Fragment 和 Chunk Fragment 里;FMassEntityQuery 负责筛出拥有某些数据的实体;UMassProcessor 在阶段调度里批量处理这些实体。Actor 可以参与 Mass,但更适合作为近处表现、高精度交互或桥接外部系统,而不是每个 Mass 实体的本体。
什么时候该用 Mass
适合 Mass 的对象通常有四个特征:数量大、行为模式相似、状态能拆成数据列、表现可以根据距离或重要性降级。城市路人、交通流、野外动物群、战场小兵、观众席、资源采集单位都很适合。反过来,只有十几个强交互角色、每个都要复杂蓝图 Tick、需要大量物理组件和个性化动画实例的对象,不应该为了“架构先进”硬塞进 Mass。
| 场景 | 推荐做法 | 原因 |
|---|---|---|
| 500 个路人巡游 | Mass Entity + ZoneGraph + Representation | 行为和移动可批量化,近处再换 Actor |
| 50 辆可驾驶车 | 交通背景用 Mass,玩家车用 Actor | 可驾驶实体需要完整交互和网络权威 |
| 1000 个观众 | Mass + ISM 或 Skinned Mesh Instance | 表现远大于行为,LOD 收益高 |
| 20 个 Boss 级 AI | BehaviorTree/StateTree + Actor | 个体差异和调试复杂度更重要 |
专题分篇目录
- Entity、Fragment、Tag、Archetype 与 Chunk:建立 Mass 的数据模型。
- EntityManager、CommandBuffer 与结构变更:解释实体生命周期和安全修改。
- Query、Requirements 与 ExecutionContext:写 Processor 的核心技能。
- Processor、Phase、Dependency 与调度:让大批量逻辑可并行、可控顺序。
- Trait、Entity Config、Template 与 Spawner:把 C++ 数据组合交给内容侧配置。
- Observer、Signal 与事件响应:处理状态进入、退出、延迟唤醒。
- Movement、Steering、Avoidance 与导航:从意图到路径、速度和 Transform。
- LOD、Representation、Actor 与 ISM 表现层:控制画面预算和 Actor 数量。
- Mass AI、StateTree 与 SmartObject:搭可维护的人群行为。
- Replication、Client Bubble 与联网应用:只同步相关实体和必要数据。
- 调试、Insights、性能与生产落地:定位问题和建立上线检查清单。
- 城市场景人群系统案例:把所有部分串成一个实施路线。
架构分析
Mass 可以分成三层。第一层是数据层:Fragment/Tag/Shared Fragment 描述实体状态,Archetype/Chunk 负责把相同组成的数据连续存储。第二层是执行层:Query 声明需要哪些数据,Processor 在某个 Phase 执行,依赖求解器根据读写需求和手动顺序安排执行图。第三层是项目层:Trait 和 Config 把模板做成资产,Spawner 批量生成,Movement/Representation/Replication/AI 模块把模板变成可见、可动、可同步的游戏对象。
这个拆法的关键收益是“热数据批处理”。如果一个 Processor 只读 FTransformFragment 和 FMassVelocityFragment,它不需要知道实体是不是路人、车辆、动物,也不需要碰 Actor 组件树。CPU 看到的是一批连续数组,调度系统看到的是明确的读写声明,内容侧看到的是可复用 Trait。Mass 的难点也在这里:你必须主动设计数据边界,否则很容易把 Actor 思维迁移过来,最后只得到一堆难调试的全局表。
使用案例
一个最小城市人群原型可以这样拆:
| 模块 | 项目资产/代码 | 作用 |
|---|---|---|
| 数据 | FCrowdMoodFragment、FCrowdDestinationFragment |
保存心情、目的地、排队目标 |
| 配置 | DA_CrowdOfficeWorkerConfig |
组合 Transform、Movement、ZoneGraph、Representation、StateTree |
| 生成 | AMassSpawner + ZoneGraph Spawn Points |
在人行道或区域内生成实体 |
| 决策 | Mass StateTree | 选择去办公楼、排队、休息、离场 |
| 移动 | ZoneGraph Navigation + Steering | 沿路网移动并避障 |
| 表现 | Representation LOD | 近处 Actor/Skinned,远处 ISM/None |
| 调试 | Mass Debugger + Insights | 看实体组成、Processor 耗时、LOD 分布 |
源码依据
UE5.8 里 FMassArchetypeCompositionDescriptor 已经围绕 FMassElementBitSet 组织 Fragment、Tag、Chunk Fragment、Shared Fragment 和 Const Shared Fragment。FMassEntityManager 注释明确说它负责托管实体和 Archetype,实体数据存于 chunked array,并且有效实体会被分配到对应 Archetype。FMassEntityQuery 会缓存匹配的 Archetype,ForEachEntityChunk 再按 Chunk 执行。UMassProcessor 通过 ConfigureQueries、Execute、ExecutionOrder、EMassProcessingPhase 和 dependency solver 进入调度。
项目落地
建议用三步试点。第一步只做“可见可动”:一个 Config、一个 Spawner、一条 ZoneGraph 路线和一个 Representation。第二步加行为:把目的地选择、排队、使用 SmartObject 写进 StateTree 或 Processor,建立调试面板。第三步才做规模化:加 LOD、变量 Tick、Replication 或 MassCrowd。每一步都要留一个 Actor 版本作为参照,避免 Mass 出问题时连业务逻辑是否正确都无法判断。
常见坑
不要一开始就给实体加几十个 Fragment。Mass 的性能来自简单明确的 Query,Fragment 过碎会导致结构变更和 Archetype 数量膨胀。不要在 Processor 遍历中直接做会改变组成的操作,优先用 Context.Defer() 或 EntityManager 的 Defer 命令。不要把表现层 Actor 当实体本体,Actor 是可切换的 representation,不是 Mass 数据的唯一事实来源。
源码路径索引
Engine/Source/Runtime/MassEntity/Public/MassEntityManager.hEngine/Source/Runtime/MassEntity/Public/MassEntityTypes.hEngine/Source/Runtime/MassEntity/Public/MassProcessor.hEngine/Plugins/Runtime/MassGameplay/Source/MassSpawner/Public/MassEntityTraitBase.h