最近项目打包时遇到一个非常奇怪的错误:
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; }
Collapse
在蓝图中测试了一下我们的 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 {}
v1.4.14