最近项目打包时遇到一个非常奇怪的错误:
1 | // package log |
我调试了一下UE4的代码,分析了一下原因和解决过程。
首先先来看一下报错的源码位置:
1 | // Runtime\AIModule\Private\BehaviorTree\Decorators\BTDecorator_BlueprintBase.cpp [Line: 67] |
决定是否进入ensure(ObservedKeyNames.Num() > 0);
的条件是:
GetFlowAbortMode() != EBTFlowAbortMode::None
;GetFlowAbortMode()
获取到的是BehaviorTree中Decorator节点FlowControl分类下的Observe abort
值。bIsObservingBB
is true;bIsObservingBB
的默认值是false
,后续是通过UBTDecorator_BlueprintBase::PostInitProperties()
->UBTDecorator_BlueprintBase::InitializeProperties();
来获取的。
1 | // Runtime\AIModule\Private\BehaviorTree\Decorators\BTDecorator_BlueprintBase.cpp |
该函数的作用是通过调用BlueprintNodeHelpers::HasAnyBlackboardSelectors
来判断当前的Decorator
内是否具有FBlackboardSelectors
的属性:
1 | // Runtime\AIModule\Private\BehaviorTree\BlueprintNodeHelpers.cpp |
即:如果Decorator
内有FBlackboardKeySelector
属性,bIsObservingBB
就为true.
那么进入条件内的处理就很简单明了了:
1 | // Runtime\AIModule\Private\BehaviorTree\Decorators\BTDecorator_BlueprintBase.cpp [Line: 67] |
调用BlueprintNodeHelpers::CollectBlackboardSelectors
从当前Decorator
对象中获取所有的FBlackboardKeySelector
Name列表。
1 | // Runtime\AIModule\Private\BehaviorTree\BlueprintNodeHelpers.cpp |
因为上面bIsObservingBB
为true,所以这里预期肯定能够获取到一个非空的列表,所以在ObservedKeyNames.Num()
肯定是>0
的,如果这里没有获取到,就会出发断言。
最开始猜测这个问题的出现是因为在UBTDecorator_BlueprintBase::InitializeProperties
和UBTDecorator_BlueprintBase::PostLoad
出现了获取属性不一致的情况,因为我查到bIsObservingBB
这个变量只在UBTDecorator_BlueprintBase::InitializeProperties
被设置了。
所以我新建了一个函数库来模仿BlueprintNodeHelpers::CollectBlackboardSelectors
暴露给蓝图来进行检测,作用是获取传进来的Decorator
是否具有BlackboardKey
:
1 | // AIModuleFlib.h |
1 | // AIModuleFlib.cpp |
在蓝图中测试了一下我们的Decorator
(以BTD_Check_AttackTargetDistance.uasset为例,这个也是我检测出的有问题的Decorator
,有它就打包不过):
我猜测认为是蓝图里这个的Decorator
的uasset
资源有问题,我们从BehaviorTree完全新建了Decorator
并完全拷贝了原有逻辑,在行为树中替换之后就打包成功了。
但是目前我尚不清楚序列化出来的属性不一致的原因(我猜测是uasset里面的序列化的属性出问题了),所以解决这个问题的办法只能是新建一个Decorator
并拷贝其实现了。
有兴趣分析这个问题的可以下载这个Decorator
(BTD_Check_AttackTargetDistance.uasset)进行调试。
而且,UE的蓝图也是资源,方便倒是方便就是出现类似的序列化问题就很蛋疼了。
另外,不知道UE的这个序列化是怎么设计的,设计思想是什么,但是看到了这样的代码:
1 | // Serializer. |
这个FArchive& operator<<
操作的行为居然向参数Tag
里写入数据…我个人觉得这种方式不直观不大好(我还以为是Tag.Name
写入到Ar
的)。
再立个Flag:有时间再研究一下UE的序列化吧,