最近项目打包时遇到一个非常奇怪的错误:
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
挂上这个 Decorator
并指定一个Pawn使用这个行为树。
然后从引擎启动开始断点调试(使用VS调式可以使用这个方法:UE4和VR开发技术笔记#VS调试独立运行的UE项目 )。 最终发现这个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的类型是Loading还是Writing的,可以通过FArchive
的IsLoading
接口进行判断:
1 bool FArchive::IsLoading () const {}