目前版本5.3.2,本文章仅讨论MASS在与UE自身系统结合时遇到的问题,并不探讨ECS框架的哲学问题,如有疏忽错误,欢迎评论区指出
不要在编辑器下复制EntityConfigAsset(UE5.3.2存在,UE5.4修了,但没完全修)
因为内部是使用一个guid来判断唯一性的,但是复制的时候会顺带把guid也复制了(WTF?)就会导致同时使用这俩的时候只会指向其中一个。这个问题会在不经意间触发,尤其是当我们想配置两个差不多的Entity的时候我们会本能的复制一份,从而触发。

不要复制带有AgentComponent的Actor,问题跟上面那个guid一模一样….(5.4仍然存在)
Processor自动注册到游戏阶段具体是怎么个流程?
首先大多数流程在MassProcessingPhaseManager.h文件夹下,主要控制由FMassProcessingPhaseManager完成,而FMassProcessingPhaseManager则是放在了USimulationSubsystem下,OnWorldBeginPlay的时候自动启动相关逻辑。
简单来讲,每一帧(主线程Tick,下同)分为很多个Phase,就是下面这些

每一个Phase都会有一个对应的FMassProcessingPhase,每一个FMassProcessingPhase都只会对应一个UMassCompositeProcessor(重要),UMassCompositeProcessor看名字也知道是一个由很多个Processor组合起来的Processor,而这个FMassProcessingPhase继承自FTickFunction,所以它也就拥有了Tick的能力,他们是由上述的FMassProcessingPhaseManager注册到主循环中的。
然后每一个Phase要做的事情就是,开始的时候检查一些修改,比如有没有新的Archetypes什么的,然后根据这些来判断是否需要重新构建对应的UMassCompositeProcessor,构建时收集所有的UMassProcessor子类,然后根据Processor其下OwnedQueries中依赖的Fragment(重要),配置好的执行依赖(FMassProcessorExecutionOrder配置)计算出一个依赖图,如下图这个

,一切前置任务都搞定之后,再把Processor包成Task丢给TaskGraph去执行,等待他们执行完了,这一Phase才会结束。
看似很美好,但是其中包含了无数的小细节需要注意,接下里我一个个讲
更新CompositeProcessor依赖的时候不检查ConstSharedFragment(UE5.3.2)
当你想在FMassEntityQuery中只配置一个ConstSharedFragment,你会发现即使目前有对应的Entity,但是仍然无法触发对应的Processor,原因就在此。
修改Entity对应的Archetype的时候需要延迟修改,那么延迟到什么时候?
无论是否开启并行模式,都会在每一个Phase的结尾进行这个操作,包括AddRemoveFragment,销毁Entity等,这个操作是在游戏线程下的,在其注释中有提到,在Processor运行时,不应该对Archetype进行修改,所以在EntityManager中有这样一个函数

ProcessingScopeCount就是一个原子变量,在每一次有Processor进行操作时都会对这个变量+1,从而检查当前是否有正在进行的操作,来避免多线程bug。
但是这会引入另一个问题,那就是无法在同一帧内通过添加或删除一些片段来协同其他Processor做部分逻辑,如果需要在同一帧内完成的操作只能延迟处理或者加一个Fragment,并在其中存入对应的上下文信息然后下一帧解决,或者通过Subsystem去做,增加不少代码量。
不经意的耦合会触发多线程bug吗?
很多时候会有Entity耦合其他Entity的情况,比如在官方的MassAvoidanceProcessors中,需要检查周围Entity的Velocity,在这种时候,官方给了一个FMassEntityView来解决对应的需求,如下

但是,它不是线程安全的,并且提供的API允许写入,也就意味着,是有可能会出现多线程bug的,如果有时候发生了莫名的bug,可以在这个方向稍微看一下。
Fragment的初始化流程是怎样的,以及有什么暗坑需要注意?
官方的资料中,推荐了UMassObserverProcessor来初始化Fragment,但是在阅读源码的时候我们会发现与UE自身的UObject耦合时还有另一个东西可以用来初始化,比如这个

上图中的Initializers会在UMassObserverProcessor之后调用,记住这个结论就行了,源码在UMassAgentSubsystem::HandlePendingInitialization(),其UMassObserverProcessor的调用时机是SpawnerSystem->SpawnEntities(EntityTemplate, NewEntityCount, Entities);这一行结束的时候,由FEntityCreationContext的析构函数触发
关于SpawnEntities也有一些可以说道的东西,比如下图这个函数可以以一种更加高级的方式控制Entity生成

这里还有一个隐形坑,如果你实现了更高级的Processor来处理Entity,注意不要在这个你自定义的Processor中增删Fragment,因为这样会导致Archetype的变更,也就是Entity不在原来的地方了,而FEntityCreationContext又是存储的Archetype中的位置,不是EntityHandle,就会导致UMassObserverProcessor失效
如果在ConstShareFragment中放置软引用,在构建模板时要记得加载再构建
在构建entity模板的时候会获取结构体然后计算一个hash值来存入模板中,用于判断模板是否相同,但是由于软指针在未加载的时候为空,但实际上我们的意图是有实际指向的对象,与此同时又忘记了加载,就会导致存入错误的模板
ECS,很奇妙吧
原文发表于知乎。