最近项目打包时遇到一个非常奇怪的错误:
1 2 // package log Ensure condition failed: ObservedKeyNames.Num() > 0 [File:D:\Build\++UE4+Release-4.18+Compile\Sync\Engine\Source\Runtime\AIModule\Private\BehaviorTree\Decorators\BTDecorator_BlueprintBase.cpp] [Line: 67]
我调试了一下UE4的代码,分析了一下原因和解决过程。
首先先来看一下报错的源码位置:
1 2 3 4 5 6 7 8 9 10 11 12 13 void UBTDecorator_BlueprintBase::PostLoad () { Super::PostLoad(); if (GetFlowAbortMode() != EBTFlowAbortMode::None && bIsObservingBB) { ObservedKeyNames.Reset(); UClass* StopAtClass = UBTDecorator_BlueprintBase::StaticClass(); BlueprintNodeHelpers::CollectBlackboardSelectors(this , StopAtClass, ObservedKeyNames); ensure(ObservedKeyNames.Num() > 0 ); } }
决定是否进入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 2 3 4 5 6 7 8 9 10 11 void UBTDecorator_BlueprintBase::InitializeProperties () { if (HasAnyFlags(RF_ClassDefaultObject)) { UClass* StopAtClass = UBTDecorator_BlueprintBase::StaticClass(); BlueprintNodeHelpers::CollectPropertyData(this , StopAtClass, PropertyData); bIsObservingBB = BlueprintNodeHelpers::HasAnyBlackboardSelectors(this , StopAtClass); } }
该函数的作用是通过调用BlueprintNodeHelpers::HasAnyBlackboardSelectors
来判断当前的Decorator
内是否具有FBlackboardSelectors
的属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 bool BlueprintNodeHelpers::HasAnyBlackboardSelectors (const UObject* Ob, const UClass* StopAtClass) { bool bResult = false ; for (UProperty* TestProperty = Ob->GetClass()->PropertyLink; TestProperty; TestProperty = TestProperty->PropertyLinkNext) { if (TestProperty->GetOuter() == StopAtClass) { break ; } if (TestProperty->HasAnyPropertyFlags(CPF_Transient) || TestProperty->HasAnyPropertyFlags(CPF_DisableEditOnInstance)) { continue ; } const UStructProperty* StructProp = Cast<const UStructProperty>(TestProperty); if (StructProp && StructProp->GetCPPType(NULL , CPPF_None).Contains(GET_STRUCT_NAME_CHECKED(FBlackboardKeySelector))) { bResult = true ; break ; } } return bResult;}
即 :如果Decorator
内有FBlackboardKeySelector
属性,bIsObservingBB
就为true.
那么进入条件内的处理就很简单明了了:
1 2 3 4 5 6 7 8 9 10 11 12 13 void UBTDecorator_BlueprintBase::PostLoad () { Super::PostLoad(); if (GetFlowAbortMode() != EBTFlowAbortMode::None && bIsObservingBB) { ObservedKeyNames.Reset(); UClass* StopAtClass = UBTDecorator_BlueprintBase::StaticClass(); BlueprintNodeHelpers::CollectBlackboardSelectors(this , StopAtClass, ObservedKeyNames); ensure(ObservedKeyNames.Num() > 0 ); } }
调用BlueprintNodeHelpers::CollectBlackboardSelectors
从当前Decorator
对象中获取所有的FBlackboardKeySelector
Name列表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 void BlueprintNodeHelpers::CollectBlackboardSelectors (const UObject* Ob, const UClass* StopAtClass, TArray<FName>& KeyNames) { for (UProperty* TestProperty = Ob->GetClass()->PropertyLink; TestProperty; TestProperty = TestProperty->PropertyLinkNext) { if (TestProperty->GetOuter() == StopAtClass) { break ; } if (TestProperty->HasAnyPropertyFlags(CPF_Transient) || TestProperty->HasAnyPropertyFlags(CPF_DisableEditOnInstance)) { continue ; } const UStructProperty* StructProp = Cast<const UStructProperty>(TestProperty); if (StructProp && StructProp->GetCPPType(NULL , CPPF_None).Contains(GET_STRUCT_NAME_CHECKED(FBlackboardKeySelector))) { const FBlackboardKeySelector* PropData = TestProperty->ContainerPtrToValuePtr<FBlackboardKeySelector>(Ob); KeyNames.AddUnique(PropData->SelectedKeyName); } } }
因为上面bIsObservingBB
为true,所以这里预期肯定能够获取到一个非空的列表,所以在ObservedKeyNames.Num()
肯定是>0
的,如果这里没有获取到,就会出发断言。 最开始猜测这个问题的出现是因为在UBTDecorator_BlueprintBase::InitializeProperties
和UBTDecorator_BlueprintBase::PostLoad
出现了获取属性不一致的情况,因为我查到bIsObservingBB
这个变量只在UBTDecorator_BlueprintBase::InitializeProperties
被设置了。
所以我新建了一个函数库来模仿BlueprintNodeHelpers::CollectBlackboardSelectors
暴露给蓝图来进行检测,作用是获取传进来的Decorator
是否具有BlackboardKey
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #pragma once #include "CoreMinimal.h" #include "Kismet/KismetSystemLibrary.h" #include "Kismet/KismetStringLibrary.h" #include "Kismet/BlueprintFunctionLibrary.h" #include "BehaviorTree/Decorators/BTDecorator_BlueprintBase.h" #include "AIModuleFlib.generated.h" UCLASS() class UAIModuleFlib : public UBlueprintFunctionLibrary{ GENERATED_BODY() public : UFUNCTION(BlueprintCallable) static TArray<FName> Z_CollectBlackboardSelectors (UObject* Ob, bool & resault) ; };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include "AIModuleFlib.h" #include "AIController.h" #include "BehaviorTree/BlackboardComponent.h" #include "BehaviorTree/BTFunctionLibrary.h" #include "BlueprintNodeHelpers.h" #include "BehaviorTree/BehaviorTree.h" #include "BehaviorTree/BehaviorTreeTypes.h" #define GET_STRUCT_NAME_CHECKED(StructName) \ ((void )sizeof (StructName), TEXT(#StructName)) TArray<FName> UAIModuleFlib::Z_CollectBlackboardSelectors (UObject* Ob, bool & resault) { resault=false ; TArray<FName> KeyNames; KeyNames.Empty(); for (UProperty* TestProperty = Ob->GetClass()->PropertyLink; TestProperty; TestProperty = TestProperty->PropertyLinkNext) { if (TestProperty->HasAnyPropertyFlags(CPF_Transient) || TestProperty->HasAnyPropertyFlags(CPF_DisableEditOnInstance)) { continue ; } const UStructProperty* StructProp = Cast<const UStructProperty>(TestProperty); if (StructProp && StructProp->GetCPPType(NULL , CPPF_None).Contains(GET_STRUCT_NAME_CHECKED(FBlackboardKeySelector))) { const FBlackboardKeySelector* PropData = TestProperty->ContainerPtrToValuePtr<FBlackboardKeySelector>(Ob); KeyNames.AddUnique(PropData->SelectedKeyName); } } resault=KeyNames.Num()>0 ; return KeyNames; }
在蓝图中测试了一下我们的Decorator
(以BTD_Check_AttackTargetDistance.uasset 为例,这个也是我检测出的有问题的Decorator
,有它就打包不过):
但是在这样的测试中发现我们的`Decorator`中没有`BlackboardKey`,这就很奇怪了,因为我查到的`bIsObservingBB`这个变量只在`UBTDecorator_BlueprintBase::InitializeProperties`有设置,所以一定是在其他地方这个变量被设置了。
知道了这个问题,我新建了一个空白工程,在空BehaviorTree中添加一个`selector`或者`sequence`挂上[这个](BTD_Check_AttackTargetDistance.uasset)`Decorator`并指定一个Pawn使用这个行为树。
然后从引擎启动开始断点调试(使用VS调式可以使用这个方法:[UE4和VR开发技术笔记#VS调试独立运行的UE项目](https://imzlp.me/posts/3380/#VS%E8%B0%83%E8%AF%95%E7%8B%AC%E7%AB%8B%E8%BF%90%E8%A1%8C%E7%9A%84UE%E9%A1%B9%E7%9B%AE))。
最终发现这个`Decorator`在序列化时出现了问题:
调用栈如下:

获取数据:

可以看到序列化的属性`bIsObservingBB`被设置成了`true`.
我猜测认为是蓝图里这个的Decorator
的uasset
资源有问题,我们从BehaviorTree完全新建了Decorator
并完全拷贝了原有逻辑,在行为树中替换之后就打包成功了。 但是目前我尚不清楚序列化出来的属性不一致的原因(我猜测是uasset里面的序列化的属性出问题了),所以解决这个问题的办法 只能是新建一个Decorator
并拷贝其实现了。 有兴趣分析这个问题的可以下载这个Decorator
(BTD_Check_AttackTargetDistance.uasset )进行调试。
而且,UE的蓝图也是资源,方便倒是方便就是出现类似的序列化问题就很蛋疼了。 另外,不知道UE的这个序列化是怎么设计的,设计思想是什么,但是看到了这样的代码:
1 2 3 4 5 6 7 8 9 10 11 FArchive& operator <<( FArchive& Ar, FPropertyTag& Tag ) { Ar << Tag.Name; if ((Tag.Name == NAME_None) || !Tag.Name.IsValid()) { return Ar; } }
这个FArchive& operator<<
操作的行为居然向参数Tag
里写入数据…我个人觉得这种方式不直观不大好(我还以为是Tag.Name
写入到Ar
的)。 再立个Flag:有时间再研究一下UE的序列化吧,
本文标题: UE Package Error:ObservedKeyNames.Num()>0
文章作者: 查利鹏
发布时间: 2019年01月17日 09时50分
本文字数: 本文一共有2.2k字
原始链接: https://imzlp.me/posts/359/
许可协议: CC BY-NC-SA 4.0
捐赠BTC: 1CbUgUDkMdy6YRmjPJyq1hzfcpf2n36avm
转载请保留原文链接及作者信息,谢谢!