クック時にデコレーターのエラーに遭遇した話

忙しさにかまけて放置してしまっていたが、久々に更新。

デコレーターでクックエラー

突然だが、UE4 のビヘイビアツリーには、デコレーター(Decorator)というものがある。

Behavior Tree ノードのリファレンス:Decorators
Behavior Tree Decorator ノードの説明です。

このデコレーターのアセットを作った上で、パッケージングをするためにクックを走らせると、
以下のようなエラーが出て止まるということがあった。

LogOutputDevice: Error: === Handled ensure: ===
LogOutputDevice: Error: Ensure condition failed: ObservedKeyNames.Num() > 0 [File:D:\Build\++UE4+Release-4.19+Compile\Sync\Engine\Source\Runtime\AIModule\Private\BehaviorTree\Decorators\BTDecorator_BlueprintBase.cpp] [Line: 67]

// 以下略

UE4 の ensure アサーションに捕まっている。
とりあえず、上記に書かれているソースコードを読んで見る。

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

BTDecorator_BlueprintBase はブループリントでデコレーターを作る際には必ず継承する必要があるクラス。

このクラスの PostLoad つまりロード直後のイベント内でアサートしている。
具体的には、GetFlowAbortMode() != EBTFlowAbortMode::None かつ bIsObservingBB のとき、
自分のクラスに、 FBlackboardKeySelector のメンバ変数があるかどうか探して、なければアサーション

(BlueprintNodeHelpers::CollectBlackboardSelectorsFBlackboardKeySelector のプロパティを検索してその名前をリスト化して返す)

EBTFlowAbortMode はデコレーターが元々持っているもので、ビヘイビアツリーの中断発生時の振る舞いを決めるものだけど、
一般的に考えて EBTFlowAbortMode::None にする運用はあまりないといっていい。

なので、bIsObservingBB が true になっていることが問題?と一瞬思うのだが、

void UBTDecorator_BlueprintBase::InitializeProperties()
{
	if (HasAnyFlags(RF_ClassDefaultObject))
	{
		UClass* StopAtClass = UBTDecorator_BlueprintBase::StaticClass();
		BlueprintNodeHelpers::CollectPropertyData(this, StopAtClass, PropertyData);

		bIsObservingBB = BlueprintNodeHelpers::HasAnyBlackboardSelectors(this, StopAtClass);
	}
}

void UBTDecorator_BlueprintBase::PostInitProperties()
{
	Super::PostInitProperties();
		
	InitializeProperties();
	
	if (PerformConditionCheckImplementations || bIsObservingBB)
	{
		bNotifyBecomeRelevant = true;
		bNotifyCeaseRelevant = true;
	}
}

とあるように、PostInitProperties 時に、
BlueprintNodeHelpers::HasAnyBlackboardSelectors で ‘FBlackboardKeySelector’ を事前検索しているだけだった。

どういうことなの・・・

もうわからんので、ぐぐってみる。
AnserHub にもとりあげられていた。ああ、まさにこれこれ。

ensure ObservedKeyNames.Num() > 0 failed in UBTDecorator_BlueprintBase::PostLoad() - UE4 AnswerHub

ここにもあるけど、結局のところ結論から言えば、

アセットを作り直せ。

である。ええ・・・。

どうも、デコレーター編集をしている際、一時的に BlackboardKeySelector を使用していて、
その後、削除するような操作をしたとき、起きる場合があるのだと思われる(検証はしてないが)

作り直すのはいいんだけどさ

ここで1つ問題がある。

そもそも、どのデコレーターアセットのエラーなのかわからない

ということ。
エラーログを見る限り、全くわからない状態。
いやー、困ったね。

どのアセットが問題か探す

探し方として、以下のような方法がとれる。

1. デコレーターアセットを別プロジェクトに Migrate して該当するアセットが含まれているか確認

今制作しているプロジェクトへ影響を与えず、かつ、問題の箇所を明確にするため、デコレーターアセットを Migrate して別プロジェクトにインポートして、エラーが起きるか確認する。
アセット自体を分けてしまうことで、問題の切り分けもはっきりするし、1回のクック時間も短く済む。

2. 各デコレーターアセットの Observer Abouts を1つずつ None にしてクックエラーが出ないか確認

上のソースコード読めばわかるように、

GetFlowAbortMode() != EBTFlowAbortMode::None

でなければ、そもそも、このエラーにはならない。
1つずつ None しながら、クックエラーが起きないかチェックすることで、どのデコレーターアセットで問題が起きているか、
特定することができる。

該当アセットの修正

該当アセットを削除して作り直す・・・わけだが、ある程度作った後だった場合は、ちょっと大変な作業になる。
回避策としては、今のところ、早めにクックをして、問題が発生していないか確認しておくぐらいしかなさそうだ。

なお、「BlackboardKeySelector のプロパティを1つ持たせておく」ことで通すことができないか検証したが、それだけではうまく行かなかった。
また Duplicate してもエラーは解決しない。
きちんとまっさらなものから作り直す必要がある。