mcpField
U
ue-mass-entity

ue-mass-entity

github.com/quodsoler开发4191.1k 次安装4 周前更新
代码仓库

关于 ue-mass-entity

面向 UE 5.8 开发者的 Mass Entity 参考技能,讲解用于大规模 Agent 模拟的 ECS 数据、处理器和查询。内容覆盖实体生命周期、并行迭代及游戏玩法扩展模块。

主要功能

  1. 01Fragment、Tag 与 Archetype
  2. 02实体管理器与实体构建
  3. 03Processor 和查询配置
  4. 04分块迭代、并行处理与限流
  5. 05迭代期间的延迟结构变更

适用场景

  1. 01构建大量 Agent 的模拟逻辑
  2. 02编写按 Fragment 筛选实体的处理器
  3. 03在迭代中安全地添加或移除 Tag
  4. 04配置分块并行处理或跨帧限流

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/quodsoler/unreal-engine-skills --skill ue-mass-entity

安装后在 Claude Code 中输入/ue-mass-entity确认已加载。

附带文件references/mass-entity-patterns.mdreferences/mass-fragment-reference.md

详细介绍

UE Mass Entity

目标引擎:UE 5.8。以下 API 均已根据 5.8 头文件验证;旧版形式列在“已弃用 — 请勿使用”部分。

Mass 是一种基于原型的实体组件系统,使用缓存友好的数据布局和多线程处理器模拟数千个 Agent。核心位于始终可用的运行时模块 MassEntity 和 MassCore(以及 MassSignals、MassEngine);面向游戏的部分(生成、表示、移动、LOD、复制、Actor 桥接)来自 MassGameplay(在 5.8 中为 Experimental) 插件,导航和行为则来自 MassAI(在 5.8 中为 Experimental) 和 MassCrowd(在 5.8 中为 Experimental)。

上下文

如果 .agents/ue-project-context.md 存在,请阅读它(模块名称、约定、已启用的插件、GAS/网络设置)。如果该文件不存在,也请继续。

根据请求和代码库确定对应领域。仅当两种合理解读会导致不同代码时才提问。

请求涉及……请参阅
Build.cs、包含项、“启用插件”模块和 Build.cs
声明片段、标签、共享数据/块数据片段、标签和原型
创建/销毁实体、直接修改FMassEntityManager
编写处理器、调度、阶段UMassProcessor
选择实体、需求、筛选FMassEntityQuery
在循环中读取/写入片段数据迭代和 FMassExecutionContext
迭代期间添加/移除片段延迟命令
对片段/标签的添加或移除作出响应UMassObserverProcessor
在处理器外访问单个实体、流畅构建单实体访问和 FEntityBuilder
父子链接、原型分组关系和原型组
Traits、配置资源、AMassSpawnerTraits、配置资源和生成
ISM 渲染、LOD、提升为 Actor表示和 LOD
人群、车道、实体 AI人群、导航和行为

更完整的模板见 mass-entity-patterns.md;内置类型和字段见 mass-fragment-reference.md。

模块和 Build.cs

无需启用 MassEntity 插件。MassEntity 和 MassCore 是随引擎提供的运行时模块——将它们添加到 Build.cs 后即可链接。只有游戏功能层属于插件,并且必须在 .uproject 中启用。

PublicDependencyModuleNames.AddRange(new string[] {
    "Core", "CoreUObject", "Engine",
    "MassEntity",          // manager, processors, queries, commands, relations
    "MassCore",            // FMassFragment/FMassTag bases, FTransformFragment, traits
    "MassSignals",         // UMassSignalSubsystem, UMassSignalProcessorBase
    "MassCommon",          // UE::Mass::ProcessorGroupNames      (MassGameplay)
    "MassSpawner",         // UMassEntityTraitBase, AMassSpawner  (MassGameplay)
    "MassMovement",        // FMassVelocityFragment               (MassGameplay)
    "MassRepresentation",  // ISM visualization + LOD             (MassGameplay)
    "MassActors",          // UMassAgentComponent, actor bridging (MassGameplay)
    "MassNavigation",      // FMassMoveTargetFragment             (MassAI)
});

已移入 MassCore 的包含路径现在以 Mass/ 为命名空间:Mass/EntityElementTypes.h、Mass/EntityFragments.h、Mass/EntityHandle.h、Mass/ExternalSubsystemTraits.h、Mass/ArchetypeGroup.h。MassEntityTypes.h 仍会传递引入元素基类;建议使用显式的 Mass/ 路径。

片段、标签与原型

概念基类 / 类型用途
实体FMassEntityHandle由索引 + 序列号构成的身份句柄
片段FMassFragment每个实体的可变数据
稀疏片段FMassSparseFragment存储在原型之外的每个实体数据(添加时实体不会移动)
标签FMassTag用于筛选的零大小标记
稀疏标签FMassSparseTag无需更改原型即可添加或移除的标记
块片段FMassChunkFragment每个内存块的一个实例
共享片段FMassSharedFragment引用它的实体共享的可变值
常量共享片段FMassConstSharedFragment不可变的共享配置
原型FMassArchetypeHandle唯一的片段 / 标签组合

以上类型均派生自 FMassElement,并在 Mass/EntityElementTypes.h 中声明。

// MyMassTypes.h
#pragma once
#include "Mass/EntityElementTypes.h"
#include "MyMassTypes.generated.h"

USTRUCT()
struct FMyHealthFragment : public FMassFragment
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, Category = "Mass")
    float Current = 100.f;

    UPROPERTY(EditAnywhere, Category = "Mass")
    float Max = 100.f;
};

USTRUCT()
struct FMyAliveTag : public FMassTag { GENERATED_BODY() };

USTRUCT()
struct FMyDeadTag : public FMassTag { GENERATED_BODY() };

USTRUCT()
struct FMySquadSharedFragment : public FMassSharedFragment
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, Category = "Mass")
    int32 SquadID = 0;
};

组成相同的实体共享一个原型,每种片段类型都按块连续存储。添加或移除片段或标签会将实体移至另一个原型——这就是结构性更改会延迟到迭代结束后执行的原因。稀疏片段和稀疏标签正是为避免此类移动而存在的。

FMassEntityManager

FMassEntityManager 不是 UObject;它是一个派生自 TSharedFromThis<FMassEntityManager> 和 FGCObject 的结构体。可通过 UMassEntitySubsystem(一个 UMassSubsystemBase,其自身又是 UWorldSubsystem)或 UE::Mass::Utils 访问它。

#include "MassEntitySubsystem.h"
#include "MassEntityManager.h"

UMassEntitySubsystem* MassSubsystem = GetWorld()->GetSubsystem<UMassEntitySubsystem>();
FMassEntityManager& EntityManager = MassSubsystem->GetMutableEntityManager();
// const access: MassSubsystem->GetEntityManager()
// free function: UE::Mass::Utils::GetEntityManagerChecked(*GetWorld())
FMassEntityHandle Entity = EntityManager.CreateEntity(ArchetypeHandle);

FMassEntityHandle Reserved = EntityManager.ReserveEntity();
EntityManager.BuildEntity(Reserved, ArchetypeHandle);

// Batch creation: hold the returned context until observers should fire.
TArray<FMassEntityHandle> Entities;
TSharedRef<FMassEntityManager::FEntityCreationContext> CreationContext =
    EntityManager.BatchCreateEntities(ArchetypeHandle, 5000, Entities);

EntityManager.DestroyEntity(Entity);
EntityManager.BatchDestroyEntities(Entities);

FMassEntityHandle::IsSet()(及其别名 IsValid())只会报告索引和序列号是否非零。要查询实体是否确实存在,请使用管理器:

EntityManager.IsEntityValid(Handle);   // handle refers to a live entity
EntityManager.IsEntityBuilt(Handle);   // reserved handle has been built
EntityManager.IsEntityActive(Handle);  // valid and built

在处理器迭代之外,可以直接进行结构性修改。添加 / 标签 / 交换调用接受 TNotNull<const UScriptStruct*>(RemoveFragmentFromEntity 仍接受普通的 const UScriptStruct*,MassEntityManager.h:430),因此请传入 T::StaticStruct(),绝不要传入可能为空的指针。

EntityManager.AddFragmentToEntity(Handle, FMyHealthFragment::StaticStruct());
EntityManager.RemoveFragmentFromEntity(Handle, FMyHealthFragment::StaticStruct());
EntityManager.AddTagToEntity(Handle, FMyDeadTag::StaticStruct());
EntityManager.RemoveTagFromEntity(Handle, FMyDeadTag::StaticStruct());
EntityManager.SwapTagsForEntity(Handle, FMyAliveTag::StaticStruct(), FMyDeadTag::StaticStruct());

实体存储支持并发:FMassEntityManager::Initialize(const FMassEntityManagerStorageInitParams&) 接受 FMassEntityManager_InitParams_Concurrent(MaxEntityCount、MaxEntitiesPerPage),并构建 FConcurrentEntityStorage。子系统会为你完成此操作;只有独立管理器需要调用它。

UMassProcessor

继承 UMassProcessor,使用 *this 构建查询,以便注册自身,并严格按照声明覆盖两个虚函数。

// MyMovementProcessor.h
#pragma once
#include "MassProcessor.h"
#include "MassEntityQuery.h"
#include "MyMovementProcessor.generated.h"

UCLASS()
class MYGAME_API UMyMovementProcessor : public UMassProcessor
{
    GENERATED_BODY()
public:
    UMyMovementProcessor();
protected:
    virtual void ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager) override;
    virtual void Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) override;
private:
    FMassEntityQuery MovementQuery;
};
// MyMovementProcessor.cpp
#include "MyMovementProcessor.h"
#include "MassCommonTypes.h"        // UE::Mass::ProcessorGroupNames
#include "MassExecutionContext.h"
#include "MassMovementFragments.h"   // FMassVelocityFragment, FMassMovementParameters
#include "MassSignalSubsystem.h"
#include "Mass/EntityFragments.h"    // FTransformFragment
#include "MyMassTypes.h"             // FMyHealthFragment, FMyDeadTag, FMySquadSharedFragment

UMyMovementProcessor::UMyMovementProcessor()
    : MovementQuery(*this)
{
    ProcessingPhase = EMassProcessingPhase::PrePhysics;
    ExecutionFlags = static_cast<int32>(EProcessorExecutionFlags::AllNetModes);
    ExecutionOrder.ExecuteInGroup = UE::Mass::ProcessorGroupNames::Movement;
    ExecutionOrder.ExecuteAfter.Add(TEXT("MassApplyMovementProcessor")); // class FName: no U prefix
    bRequiresGameThreadExecution = false;
}

EMassProcessingPhase:PrePhysics、StartPhysics、DuringPhysics、EndPhysics、PostPhysics、FrameEnd、MAX。

EProcessorExecutionFlags:None、Standalone、Server、Client、Editor、EditorWorld、AllNetModes(Standalone|Server|Client)、AllWorldModes(AllNetModes|EditorWorld)、All。

FMassProcessorExecutionOrder 包含 ExecuteInGroup(一个 FName),以及由处理器或组名称构成的 ExecuteBefore 和 ExecuteAfter 数组。处理器的名称是其类的 FName,不带 U 前缀(MassProcessorDependencySolver.cpp:560)——建议使用 UOther::StaticClass()->GetFName();拼写错误的名称只会变成一个虚拟节点,并输出一条 Log 级别的消息(:705),因此排序会悄然失效。命名组来自 UE::Mass::ProcessorGroupNames:UpdateWorldFromMass、SyncWorldToMass、Behavior、Tasks、Avoidance、ApplyForces、Movement。

FMassEntityQuery

在 ConfigureQueries 中声明需求,绝不要在构造函数中声明,因为查询会在 ConfigureQueries 运行前与实体管理器绑定。通过 FMassEntityQuery(UMassProcessor& Owner) 构建的查询已完成注册;默认构造的成员仍需调用 RegisterQuery(Query)。

void UMyMovementProcessor::ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager)
{
    MovementQuery.AddRequirement<FTransformFragment>(
        EMassFragmentAccess::ReadWrite, EMassFragmentPresence::All);
    MovementQuery.AddRequirement<FMassVelocityFragment>(
        EMassFragmentAccess::ReadOnly, EMassFragmentPresence::All);
    MovementQuery.AddRequirement<FMyHealthFragment>(
        EMassFragmentAccess::ReadOnly, EMassFragmentPresence::Optional);
    MovementQuery.AddTagRequirement<FMyDeadTag>(EMassFragmentPresence::None);
    MovementQuery.AddSharedRequirement<FMySquadSharedFragment>(
        EMassFragmentAccess::ReadOnly, EMassFragmentPresence::All);
    MovementQuery.AddConstSharedRequirement<FMassMovementParameters>(
        EMassFragmentPresence::All);
    MovementQuery.AddSubsystemRequirement<UMassSignalSubsystem>(
        EMassFragmentAccess::ReadWrite);
}
EMassFragmentAccess含义
None仅筛选,不绑定数据
ReadOnly通过 GetFragmentView<T>()(TConstArrayView)读取
ReadWrite通过 GetMutableFragmentView<T>()(TArrayView)读写
EMassFragmentPresence含义
All必须存在列出的每个元素
Any至少必须存在一个标记为 Any 的元素
None元素必须不存在
Optional存在时绑定,不存在时跳过

AddChunkRequirement<T>(Access, Presence) 会绑定一个 FMassChunkFragment。SetChunkFilter(const FMassChunkConditionFunction&) 接收一个 TFunction<bool(const FMassExecutionContext&)>,每个块只会对其求值一次,因此谓词可以读取块片段、共享片段和常量数据,但不能读取逐实体片段。

迭代和 FMassExecutionContext

void UMyMovementProcessor::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context)
{
    MovementQuery.ForEachEntityChunk(Context, [](FMassExecutionContext& Context)
    {
        const int32 NumEntities = Context.GetNumEntities();
        const TArrayView<FTransformFragment> Transforms = Context.GetMutableFragmentView<FTransformFragment>();
        const TConstArrayView<FMassVelocityFragment> Velocities = Context.GetFragmentView<FMassVelocityFragment>();
        const float DeltaTime = Context.GetDeltaTimeSeconds();

        for (int32 Index = 0; Index < NumEntities; ++Index)
        {
            Transforms[Index].GetMutableTransform().AddToTranslation(Velocities[Index].Value * DeltaTime);
        }
    });
}

Lambda 仅接收 FMassExecutionContext&;没有实体管理器参数。其他访问器:GetEntities()(TConstArrayView<FMassEntityHandle>)、GetFragmentView<T>()、GetMutableFragmentView<T>()、GetChunkFragment<T>()、GetConstSharedFragment<T>()、GetMutableSharedFragment<T>()、GetSubsystem<T>()、GetMutableSubsystem<T>() 和 Defer()。

并行: ParallelForEachEntityChunk(Context, Lambda, FMassEntityQuery::EParallelExecutionFlags::Default);嵌套枚举的标志包括 Default、Force 和 AutoBalance(MassEntityQuery.h:67)。

分时处理: ForEachEntityChunk(Context, Limiter, Lambda) 接收一个由实体数量构造的 UE::Mass::FExecutionLimiter&,在访问指定数量的实体后停止(始终完成当前块),并在下次调用时从已存储的位置继续。将 limiter 作为处理器成员跨帧保留——新建的 limiter 会从头开始,而且 limiter 不会察觉两次调用之间创建或销毁的实体。

延迟命令

在 ForEachEntityChunk 内,绝不要通过实体管理器更改实体的组成:实体会移动到其他原型,当前持有的视图也会失效。应通过 Context.Defer() 将更改排入队列。

MovementQuery.ForEachEntityChunk(Context, [](FMassExecutionContext& Context)
{
    const TConstArrayView<FMassEntityHandle> Entities = Context.GetEntities();
    const TConstArrayView<FMyHealthFragment> Healths = Context.GetFragmentView<FMyHealthFragment>();

    for (int32 Index = 0; Index < Context.GetNumEntities(); ++Index)
    {
        if (Healths[Index].Current <= 0.f)
        {
            Context.Defer().SwapTags<FMyAliveTag, FMyDeadTag>(Entities[Index]);
            Context.Defer().RemoveFragment<FMyHealthFragment>(Entities[Index]);
        }
    }
});

FMassCommandBuffer 上的便捷调用:AddFragment<T>、RemoveFragment<T>、AddTag<T>、RemoveTag<T>、SwapTags<TOld, TNew>、AddElements<T...>、RemoveElements<T...>、DestroyEntity、DestroyEntities。添加和移除辅助函数会转发到 FMassCommandAddElements<T...> / FMassCommandRemoveElements<T...>,后者可在单次实体移动中处理任意组合的片段和标签(SwapTags 使用 FMassCommandSwapTagsInternal,MassCommandBuffer.h:264)。

若要添加片段并设置其值,直接推入命令类型:

Context.Defer().PushCommand<FMassCommandAddFragmentInstances<FMyHealthFragment>>(
    Entities[Index], FMyHealthFragment{});

若要执行任意延迟工作,可通过 FMassDeferredCommand<OpType> 推入 Lambda:Context.Defer().PushCommand<FMassDeferredSetCommand>([Entity](FMassEntityManager& Manager) { ... });(别名包括 FMassDeferredCreate/Add/Remove/ChangeComposition/Set/DestroyCommand,MassCommands.h:1686-1744)。若要使用可复用的类型化命令,可继承 FMassBatchedCommand,重写 Run(FMassEntityManager&)(const Execute 已标记为 UE_DEPRECATED(5.7),MassCommands.h:202),并调用 PushUniqueCommand(TUniquePtr<FMassBatchedCommand>&&)。

命令按 EMassCommandOperationType 分桶刷新,而不是按枚举顺序:Create → Add → ChangeComposition → Set → Remove 和 Destroy(共享一个分桶,按推入顺序执行)→ 最后处理未分类的 None(MassCommandBuffer.cpp:110-119)。此顺序可确保实体在被修改前已经存在,片段在写入值之前已经存在。

UMassObserverProcessor

观察器会在被观察的片段或标签添加或移除时运行。填写 ObservedTypes(一个数组——单个观察器可以监视多个类型)和 ObservedOperations。

// MyHealthObserver.cpp
UMyHealthObserver::UMyHealthObserver()
    : ObserverQuery(*this)
{
    ObservedTypes.Add(FMyHealthFragment::StaticStruct());
    ObservedOperations = EMassObservedOperationFlags::Add;
}

EMassObservedOperationFlags:None、AddElement、RemoveElement、CreateEntity、DestroyEntity,以及组合标志 Add(=AddElement | CreateEntity)、Remove(=RemoveElement | DestroyEntity)和 All。除非你有意忽略实体创建,否则请使用 Add/Remove——实体创建时就带有片段的情况下,单独使用 AddElement 不会触发。

重写 Register() 可以自定义注册的类型,并使用 GetObservedTypes() 或 GetSingleObservedTypeChecked() 读取这些类型。完整的观察器模板见 mass-entity-patterns.md。

单实体访问与 FEntityBuilder

FMassEntityView 提供对单个实体的类型化访问。它是临时对象——原型内存会发生重定位,因此每次都要构建新的视图,绝不要缓存视图。

if (EntityManager.IsEntityValid(Handle))
{
    FMassEntityView View(EntityManager, Handle);
    if (FMyHealthFragment* Health = View.GetFragmentDataPtr<FMyHealthFragment>())
    {
        Health->Current -= Damage;
    }
    const bool bDead = View.HasTag<FMyDeadTag>();
}

UE::Mass::FEntityBuilder(MassEntityBuilder.h)以流畅的方式组合实体并一次性提交,比反复调用 AddFragmentToEntity 更高效。

#include "MassEntityBuilder.h"

UE::Mass::FEntityBuilder Builder(EntityManager);
Builder.Add<FTransformFragment>(SpawnTransform);
Builder.Add<FMyHealthFragment>();
Builder.Add<FMyAliveTag>();
FMyHealthFragment& Health = Builder.GetOrCreate<FMyHealthFragment>();
Health.Current = 50.f;
const FMassEntityHandle NewEntity = Builder.Commit();

SetForceDeferredCommit(true) 会通过命令缓冲区提交;CommitAndReprepare() 可以复用构建器来处理下一个实体。

关系与原型组

关系用于表示实体之间的类型化链接。关系类型派生自 FMassRelation(其本身也是 FMassTag);引擎在 Relations/MassChildOf.h 中提供了 FMassChildOfRelation 和 FMassChildOfFragment。通过 UE::Mass::FRelationManager 创建和查询关系实例,可使用 EntityManager.GetRelationManager() 获取它。

#include "MassRelationManager.h"
#include "Relations/MassChildOf.h"

UE::Mass::FRelationManager& Relations = EntityManager.GetRelationManager();
Relations.CreateRelationInstance<FMassChildOfRelation>(ChildEntity, ParentEntity);
TArray<FMassEntityHandle> Children = Relations.GetRelationSubjects(
    FMassChildOfRelation::StaticStruct(), ParentEntity);

延迟执行的等效写法:Context.Defer().PushCommand<FMassCommandMakeRelation<FMassChildOfRelation>>(ChildEntity, ParentEntity)。构建器支持 AddRelation<T>(OtherEntity)。

原型组可以让查询按受控顺序处理原型。EntityManager.FindOrAddArchetypeGroupType(GroupName) 返回一个 UE::Mass::FArchetypeGroupType;EntityManager.BatchGroupEntities(UE::Mass::FArchetypeGroupHandle(GroupType, UE::Mass::FArchetypeGroupID(N)), Entities) 会为实体分配分组(Mass/ArchetypeGroup.h:100);Query.GroupBy(GroupType)(可选地使用排序谓词)会让迭代按组进行。

特征、配置资产与生成

UMassEntityConfigAsset(一个 UDataAsset)保存一组特征,用于构建实体模板。特征继承 UMassEntityTraitBase 并实现 BuildTemplate;两个虚函数都是 const,且 ValidateTemplate 接受三个参数。

// MyHealthTrait.h
#pragma once
#include "MassEntityTraitBase.h"
#include "MyHealthTrait.generated.h"

UCLASS(meta = (DisplayName = "My Health"))
class MYGAME_API UMyHealthTrait : public UMassEntityTraitBase
{
    GENERATED_BODY()
public:
    UPROPERTY(EditAnywhere, Category = "Health")
    float DefaultHealth = 100.f;

protected:
    virtual void BuildTemplate(FMassEntityTemplateBuildContext& BuildContext,
                               const UWorld& World) const override;
    virtual bool ValidateTemplate(const FMassEntityTemplateBuildContext& BuildContext,
                                  const UWorld& World,
                                  FAdditionalTraitRequirements& OutTraitRequirements) const override;
};

FMassEntityTemplateBuildContext(声明于 MassEntityTemplateRegistry.h)提供 AddFragment<T>()、AddFragment_GetRef<T>()、AddTag<T>()、AddChunkFragment<T>()、AddSharedFragment(const FSharedStruct&)、AddConstSharedFragment(const FConstSharedStruct&) 和 AddTranslator<T>()。

UMassAssortedFragmentsTrait(MassAssortedFragmentsTrait.h)可以添加在编辑器中配置的片段和标签列表,无需编写任何 C++。AMassSpawner 通过 EntityTypes 引用配置资产,并驱动 Count、DoSpawning() 和 DoDespawning()。

表示与 LOD

UMassRepresentationSubsystem 会池化 Instanced Static Mesh 组件和生成的 Actor,使数千个实体无需各自对应一个 Actor 也能完成渲染。

EMassRepresentationType用途
HighResSpawnedActor完整 Actor,用于最近的实体
LowResSpawnedActor用于中距离的轻量 Actor
SkinnedMeshInstance实例化蒙皮网格
StaticMeshInstanceISM,用于大部分人群
None不渲染

EMassLOD::Type 为 High、Medium、Low、Off、Max,存储为 TEnumAsByte<EMassLOD::Type>;可见性由 EMassVisibility(CanBeSeen、CulledByFrustum、CulledByDistance、Max)表示。UMassMovableVisualizationTrait 和 UMassStationaryVisualizationTrait 用于配置网格、Actor 类和 LOD 距离。

查询会触及的所有内容,其线程安全性都通过 Mass/ExternalSubsystemTraits.h 中的 Trait 声明。TMassExternalSubsystemTraits 默认 GameThreadOnly = true;TMassSharedFragmentTraits 默认 GameThreadOnly = false。TMassFragmentTraits 没有线程标记,只有 AuthorAcceptsItsNotTriviallyCopyable = false(Mass/ExternalSubsystemTraits.h:57-66)。仅在默认值不适用时才进行特化:

template<>
struct TMassExternalSubsystemTraits<UMyMassSubsystem> final
{
    enum
    {
        GameThreadOnly = false,
        ThreadSafeWrite = false
    };
};

人群、导航与行为

UMassCrowdSubsystem(MassCrowd,在 5.8 中为实验性功能)跟踪 ZoneGraph 车道占用、密度和等待位置。实体包含 FMassCrowdTag 和 FMassCrowdLaneTrackingFragment;UMassCrowdMemberTrait 会将它们关联起来。该子系统将 TMassExternalSubsystemTraits<UMassCrowdSubsystem> 特化为 GameThreadOnly = false,因此并行处理器可通过 AddSubsystemRequirement 声明对此子系统的依赖。

移动目标存储在 FMassMoveTargetFragment 中(MassNavigationFragments.h,模块 MassNavigation)。ZoneGraph 本身和 Smart Objects 的内容请参阅 ue-ai-navigation。

实体行为通过 State Trees 运行:UMassStateTreeTrait 会附加资源,UMassStateTreeProcessor 派生自 UMassSignalProcessorBase,仅对收到信号的实体进行 Tick;FMassStateTreeSharedFragment 是 const 共享片段。使用 UMassSignalSubsystem::SignalEntity(FName SignalName, FMassEntityHandle Entity) 向实体发送信号——信号名称在前。任务和求值器的编写请参阅 ue-state-trees。

已弃用 — 请勿使用

不要使用在 5.8 中使用来源
ObservedType = T::StaticStruct();ObservedTypes.Add(T::StaticStruct());MassObserverProcessor.h 中的 UE_DEPRECATED(5.8)
Operation = EMassObservedOperation::AddElement;ObservedOperations = EMassObservedOperationFlags::Add;MassObserverProcessor.h 中的 UE_DEPRECATED(5.7)
GetObservedTypeChecked()GetSingleObservedTypeChecked()MassObserverProcessor.h 中的 UE_DEPRECATED(5.8)
virtual void ConfigureQueries()ConfigureQueries(const TSharedRef<FMassEntityManager>&)MassProcessor.h 中的 UE_DEPRECATED(5.6)
virtual void Initialize(UObject&)InitializeInternal(UObject&, const TSharedRef<FMassEntityManager>&)MassProcessor.h 中的 UE_DEPRECATED(5.6)
ForEachEntityChunk(EntityManager, Context, Fn)ForEachEntityChunk(Context, Fn)MassEntityQuery.h 中的 UE_DEPRECATED(5.6)
ParallelForEachEntityChunk(EntityManager, …)、EParallelForModeParallelForEachEntityChunk(Context, Fn, EParallelExecutionFlags)MassEntityQuery.h 中的 UE_DEPRECATED(5.6)
FMassEntityQuery Query{ FMyFragment::StaticStruct() };FMassEntityQuery Query{*this}; + 在 ConfigureQueries 中声明需求MassEntityQuery.h 中的 UE_DEPRECATED(5.6)
Defer().AddTag_RuntimeCheck<T>() 和其他 *_RuntimeCheck 调用Defer().AddTag<T>()、AddFragment<T>()、RemoveFragment<T>()、RemoveTag<T>()、SwapTags<A,B>()MassCommandBuffer.h 中的 UE_DEPRECATED(5.8)
FMassCommandAddTagsInternal、FMassCommandAddFragmentsInternalFMassCommandAddElements<T...>MassCommands.h 中的 UE_DEPRECATED(5.8)
FMassCommandRemoveTagsInternal、FMassCommandRemoveFragmentsInternalFMassCommandRemoveElements<T...>MassCommands.h 中的 UE_DEPRECATED(5.8)
#include "MassExternalSubsystemTraits.h"、"MassEntityElementTypes.h"、"MassEntityFragments.h"、"MassEntityHandle.h"Mass/ExternalSubsystemTraits.h、Mass/EntityElementTypes.h、Mass/EntityFragments.h、Mass/EntityHandle.h各 shim 中的 UE_DEPRECATED_HEADER(5.8)
FMassEntityManager::Initialize()Initialize(FMassEntityManagerStorageInitParams),并使用 FMassEntityManager_InitParams_ConcurrentMassEntityManager.h 中的 UE_DEPRECATED(5.8)
UMassRepresentationSubsystem::GetOrSpawnActorFromTemplateGetOrRequestSpawnActorFromTemplateMassRepresentationSubsystem.h:246 中的 UE_DEPRECATED(5.8)
FMassLookAtTargetTagFMassLookAtTargetFragmentMassLookAtFragments.h 中的 UE_DEPRECATED(5.6)

常见错误

把 Mass 当作插件:MassEntity 和 MassCore 是运行时模块,没有 .uplugin。在 Build.cs 中添加 "MassEntity" 即可;只有 MassGameplay、MassAI 和 MassCrowd 需要在 .uproject 中启用。

**FTransformFragment 的头文件错误:**它位于 Mass/EntityFragments.h(模块 MassCore)中。MassCommonFragments.h 只是包含了它;MassCommon 和 MassEntity 将 MassCore 列为公共依赖,因此可以传递解析,但应包含 Mass/EntityFragments.h 并显式列出 MassCore,而不是依赖这种传递关系。

**在迭代期间进行结构性更改:**在 ForEachEntityChunk 中调用 EntityManager.AddFragmentToEntity(...) 会移动实体,并使所有正在使用的视图失效。请使用 Context.Defer().AddFragment<T>(Entity)。

**在构造函数中声明需求:**查询会在 ConfigureQueries 运行前不久绑定到其实体管理器,因此在构造函数中调用 AddRequirement 会触发“Modifying requirements before initialization”检查。只在 ConfigureQueries 中声明需求。

忘记注册默认构造的查询:FMassEntityQuery MyQuery; 在调用 RegisterQuery(MyQuery) 前不会匹配任何内容,且不会给出提示。建议在构造函数初始化列表中使用 MyQuery(*this)。

**缓存 FMassEntityView:**视图指向可能会移动的原型内存。每次使用时都应根据句柄重新构建视图。

**用 IsSet() 检查是否存在:**指向已销毁实体的句柄仍会报告 IsSet()/IsValid() 为 true。请调用 EntityManager.IsEntityValid(Handle)。

**访问方式不匹配:**请求 ReadOnly 后调用 GetMutableFragmentView<T>() 会触发断言。请确保视图与声明的 EMassFragmentAccess 匹配。

**只监听 AddElement:**创建时就已包含该片段的实体会触发 CreateEntity,而非 AddElement。请使用 EMassObservedOperationFlags::Add。

**从并行处理器访问 UObject:**设置 bRequiresGameThreadExecution = true,或者通过 AddSubsystemRequirement 声明子系统,并为其提供如实声明特性的 TMassExternalSubsystemTraits 特化。

相关技能

  • ue-state-trees — State Tree 资产、任务、评估器和 Mass State Tree 架构
  • ue-ai-navigation — ZoneGraph、Smart Objects、NavMesh 和感知
  • ue-async-threading — 任务图、ParallelFor 和线程安全基础
  • ue-actor-component-architecture — UMassAgentComponent 另一侧的 Actor 和组件
  • ue-cpp-foundations — USTRUCT/UCLASS 反射、子系统、模块布局
  • ue-animation-system — 将实体提升为 Actor 或蒙皮实例后的动画
  • ue-procedural-generation — 为 Mass 生成提供数据的 PCG 和 ISM 编写
  • ue-mover — Mover 插件:移动模式、分层移动和回滚网络

评论

支持 Markdown