最近项目打包时遇到一个非常奇怪的错误:
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] 
首先先来看一下报错的源码位置:
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对象中获取所有的FBlackboardKeySelectorName列表。
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并完全拷贝了原有逻辑,在行为树中替换之后就打包成功了。解决这个问题的办法 只能是新建一个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