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;
}
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.

我猜测认为是蓝图里这个的 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
文章禁止全文转载,摘要转发请保留原文链接及作者信息,谢谢!
您的捐赠将鼓励我继续创作!
Powered By Valine
v1.4.14