UE Package Error:ObservedKeyNames.Num()>0

最近项目打包时遇到一个非常奇怪的错误:

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
// Runtime\AIModule\Private\BehaviorTree\Decorators\BTDecorator_BlueprintBase.cpp [Line: 67]
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);的条件是:

  1. GetFlowAbortMode() != EBTFlowAbortMode::NoneGetFlowAbortMode()获取到的是BehaviorTree中Decorator节点FlowControl分类下的Observe abort值。

  2. bIsObservingBB is true;
    bIsObservingBB的默认值是false,后续是通过UBTDecorator_BlueprintBase::PostInitProperties()->UBTDecorator_BlueprintBase::InitializeProperties();来获取的。

1
2
3
4
5
6
7
8
9
10
11
// Runtime\AIModule\Private\BehaviorTree\Decorators\BTDecorator_BlueprintBase.cpp
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
// Runtime\AIModule\Private\BehaviorTree\BlueprintNodeHelpers.cpp
bool BlueprintNodeHelpers::HasAnyBlackboardSelectors(const UObject* Ob, const UClass* StopAtClass)
{
bool bResult = false;

for (UProperty* TestProperty = Ob->GetClass()->PropertyLink; TestProperty; TestProperty = TestProperty->PropertyLinkNext)
{
// stop when reaching base class
if (TestProperty->GetOuter() == StopAtClass)
{
break;
}

// skip properties without any setup data
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
// Runtime\AIModule\Private\BehaviorTree\Decorators\BTDecorator_BlueprintBase.cpp [Line: 67]
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
// Runtime\AIModule\Private\BehaviorTree\BlueprintNodeHelpers.cpp
void BlueprintNodeHelpers::CollectBlackboardSelectors(const UObject* Ob, const UClass* StopAtClass, TArray<FName>& KeyNames)
{
for (UProperty* TestProperty = Ob->GetClass()->PropertyLink; TestProperty; TestProperty = TestProperty->PropertyLinkNext)
{
// stop when reaching base class
if (TestProperty->GetOuter() == StopAtClass)
{
break;
}

// skip properties without any setup data
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::InitializePropertiesUBTDecorator_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
// AIModuleFlib.h
// Fill out your copyright notice in the Description page of Project Settings.

#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
// AIModuleFlib.cpp
// Fill out your copyright notice in the Description page of Project Settings.

#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)
{

// skip properties without any setup data
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.

我猜测认为是蓝图里这个的Decoratoruasset资源有问题,我们从BehaviorTree完全新建了Decorator并完全拷贝了原有逻辑,在行为树中替换之后就打包成功了。
但是目前我尚不清楚序列化出来的属性不一致的原因(我猜测是uasset里面的序列化的属性出问题了),所以解决这个问题的办法只能是新建一个Decorator并拷贝其实现了。
有兴趣分析这个问题的可以下载这个Decorator(BTD_Check_AttackTargetDistance.uasset)进行调试。

而且,UE的蓝图也是资源,方便倒是方便就是出现类似的序列化问题就很蛋疼了。

另外,UE的序列化在设计上是通过同一个接口来实现的序列化与反序列化:

1
2
3
4
5
6
7
8
9
10
11
// Serializer.
FArchive& operator<<( FArchive& Ar, FPropertyTag& Tag )
{
// Name.
Ar << Tag.Name;
if ((Tag.Name == NAME_None) || !Tag.Name.IsValid())
{
return Ar;
}
// ...
}

走的是完全相同的逻辑,而区别在于FArchive的类型是Loading还是Writing的,可以通过FArchiveIsLoading接口进行判断:

1
bool FArchive::IsLoading() const{}
全文完,若有不足之处请评论指正。

微信扫描二维码,关注我的公众号。

本文标题:UE Package Error:ObservedKeyNames.Num()>0
文章作者:查利鹏
发布时间:2019年01月17日 09时50分
本文字数:本文一共有1.9k字
原始链接:https://imzlp.com/posts/359/
许可协议: CC BY-NC-SA 4.0
文章禁止全文转载,摘要转发请保留原文链接及作者信息,谢谢!
您的捐赠将鼓励我继续创作!