わからないまま使っている?
UE4 の AI の基本的なこと
らりほま
2018/02/25 Unreal Engine Meetup Nagoya #6 in 名古屋城
自己紹介
 らりほま (Twitter : @rarihoma)
 株式会社ヒストリアのエンジニア
 UE4 歴 3 年
 エンジニア歴 1 年
 Gray ちゃん 3D モデルデータ配布中
 http://rarihoma.xvs.jp/products/graychan
講演のテーマ『AI』
 注意
 やたらと地味で細かい内容です
 間違っている部分があればご指摘お願いします
 Unreal Engine 4.18.3 で検証しています
 今日話さないこと
 すごく賢い AI の作り方 (← むしろ自分が知りたい)
 Navigation
 Sensing
 EQS
UE4 の AI の資料
 公式ドキュメント
 ビヘイビアツリーのクイックスタート ガイド | Unreal Engine
 https://docs.unrealengine.com/latest/JPN/Engine/AI/BehaviorTrees/QuickStart/ind
ex.html
 動画
 はじめてのAI~ 愛のあるAIを作ろう - YouTube
 https://www.youtube.com/watch?v=gT8uuc0DxWk&t=1225s
 スクウェア・エニックスにおける UNREAL ENGINE 4 を用いた人工知能技術の開発
事例 - YouTube
 https://www.youtube.com/watch?v=BV2GTGbSjq8
 書籍
 Unreal Engine 4 で極めるゲーム開発
 作れる!学べる!Unreal Engine 4 ゲーム開発入門
目次
 UE4 の AI の雑な解説
 UserBlackboard() と RunBehaviorTree() の動作
 Blackboard の Key の初期値
 Behavior Tree Task の通常版と AI 版の Event の違い
UE4 の AI の雑な解説
UE4 の AI の雑な解説
 BehaviorTree
 挙動を定義
 中断処理を
組みやすい
UE4 の AI の雑な解説
 Blackboard
 変数を定義
 変数のことを
Key と呼ぶ
UE4 の AI の雑な解説
 AIController で UseBlackboard() と RunBehaviorTree() を実行
→ AIController が所有 (Possess) している Pawn が
BehaviorTree に従って動作する
UseBlackboard() と
RunBehaviorTree() の動作
UseBlackboard() と
RunBehaviorTree() に関する疑問
 2 つの Node は具体的に何を行っているのか?
 2 つの Node の ReturnValue は何を返しているのか?
UseBlackboard() と
RunBehaviorTree() の動作
Blueprint で検証
検証 :
AIController の Component を見る
 UseBlackboard() は BlackboardComponent を返しているので、
RunBehaviorTree() も何らかの Component を付加しているのでは?
→ AIController に付加された Component を
各 Node の実行前・実行後で比較する
検証 :
AIController の Component を見る
 AIController に最初から付加されている Component
検証 :
AIController の Component を見る
 UseBlackboard() 実行後
 BlackboardComponent が付加される
検証 :
AIController の Component を見る
 UseBlackboard() → RunBehaviorTree() 実行後
 BTComponent が付加される
検証 :
AIController の Component を見る
 UseBlackboard() は実行せずに
RunBehaviorTree() だけ実行後
 !?!?!?
 BTComponent だけでなく
BlackboardComponent も付加される
UseBlackboard() と
RunBehaviorTree() の動作
エンジンソースを読む
UseBlackboard() のソースを読む
 (/Runtime/AIModule/Private/AIController.cpp line:995)
UseBlackboard() のソースを読む
 ソースからわかること
 UBlackboardComponent がなければ AAIController に追加する
 UBlackboardComponent が既にある場合は、
追加せずに既存のものを初期化する
 UseBlackboard() を何度も呼び出しても
UBlackboardComponent は 1 つだけしか付加されない
 UBlackboardData (Blackboard の Asset) が異なる場合、
新しく指定された方で初期化される
 引数 BlackboardAsset が nullptr のときのみ false を返す
 return される変数 bSuccess が
初期値 true から変更される機会が実は存在しない
RunBehaviorTree() のソースを読む
 (/Runtime/AIModule/Private/AIController.cpp line:889)
RunBehaviorTree() のソースを読む
 ソースからわかること
 UBehaviorTreeComponent がなければ AAIController に追加する
 RunBehaviorTree() を何度も呼び出しても
UBehaviorTreeComponent は 1 つだけしか付加されない
RunBehaviorTree() のソースを読む
 ソースからわかること
 引数 BTAsset に Blackboard Asset が指定されていて、
かつ UBlacokboardComponent が存在しないか
存在していても互換性がない場合、
UseBlackboard() が実行される
 UseBlackboard() Node を実行しなくても
UBlackboardComponent が追加されていたのはこのため
 既存の Blackboard の Asset が
BTAsset が指定する Blackboard Asset の親である場合、
または両者の Key がすべて同一であれば、
互換性があると見なされる
 要するに、これから実行する BehaviorTree 内で使われる可能性のある Key が
既に存在する UBlackboardComponent 内に無いのは困るので、
その場合は UBlackboardComponent を作り直してしまえ、ということ
RunBehaviorTree() のソースを読む
 ソースからわかること
 引数 BTAsset が NULL のときのみ false を返す
 bSuccess = UseBlackboard(~); という行は
UseBlackboard() が常に true を返す状況でしか呼ばれないため、
return される変数 bSuccess は初期値 true から変更されない
実例 1
 UseBlackboard() は
呼び出さずに
RunBehaviorTree() だけ
呼び出した場合
 BehaviorTree に
BlackboardAsset が
設定されているので、
内部で UseBlackboard() が走って
UBlackboardComponent が
生成され、問題なく動作する
実例 2
 UseBlackboard() は
呼び出さずに
RunBehaviorTree() だけ
呼び出した場合
 BehaviorTree に
BlackboardAsset が
設定されていないので、
内部で UseBlackboard() が走らず
UBlackboardComponent が
生成されないので、
アクセス違反を起こす可能性がある
実例 3
 RunBehaviorTree() で実行される BehaviorTree と
UseBlackboard() で既に生成された Blackboard とで
Blackboard Asset の互換性がない場合
 UBlackboardComponent が初期化され、データが消し飛ぶ
互換性なし
実例 4
 RunBehaviorTree() で実行される BehaviorTree と
UseBlackboard() で既に生成された Blackboard とで
Blackboard Asset の互換性がある場合
 UBlackboardComponent のデータは維持される
 さらに、子の Blackboard にしか存在しない Key も使える
互換性あり
(子 - 親 の関係)
実例 5
 上と下の Node 群はほぼ同義
まとめ
 2 つの Node は具体的に何を行っているのか?
 UseBlackboard() は UBlackboardComponent を、
RunBehaviorTree() は UBehaviorTreeComponent を生成する
 RunBehaviorTree() は UBlackboardComponent が存在しない場合には
UseBlackboard() を呼び出すので、
UseBlackboard() を明示的に呼び出す必要はない
 ただし BehaviorTree に Blackboard Asset が設定されている必要がある
 BehaviorTree に設定されている Blackboard Asset の子を使いたい場合や
RunBehaviorTree() の呼び出し前に Key の Set を行いたい場合は、
UseBlackboard() を明示的に呼び出す必要がある
 両方共、何回呼び出しても対応する Component は 1 つしか生成されない
 複数回呼び出しても、無視されるか、既存のものが新しい設定で初期化される
まとめ
 2 つの Node の ReturnValue は何を返しているのか?
 引数が None であるときに false を返している
 IsValid() で事前に判定している場合、利用する必要はない
Blackboard の Key の初期値
Key の初期値に関する疑問
 Blueprint の変数には、初期値を設定することができる
 一方、Blackboard の Key には、初期値を設定するところがない
 Blackboard の Key の初期値はどうなっている?
Blackboard の Key の初期値
Blueprint で検証
初期値の検証 1
 Blackboard で扱うことができるすべての Type の Key を作成し、
Editor での Play 中に値を確認してみる
 Enum : UNKNOWN! とは?
 String : n/a とは?
 Rotator / Vector : (invalid) とは?
初期値の検証 2
 Key を GetBlackboardValueAs○○() で取得して PrintString() してみる
初期値の検証 2
 Key を GetBlackboardValueAs○○() で取得して PrintString() してみる
 Enum : (Byte の) 0
 String : 空の文字列
 Rotator / Vector : !?!?!?
初期値の検証
 ひとまず初期値はわかった
 が、Rotator / Vector は
何故こんな値なのか?
 意図的な設定なのか
 たまたまメモリに
残っていた値を
解釈したらこうなったのか
Blackboard の Key の初期値
エンジンソースを読む
Key の Type 毎の処理定義
 Blackboard の Key に対する処理は、
UBlackboardKeyType を継承した
UBlackboardKeyType_○○ で規定されている
 (/Runtime/AIModule/Classes/BehaviorTree/Blackboard/BlackboardKeyType.h)
 GetValue() : 値の取得
 SetValue() : 値の設定
 InitializeMemory() : 値の初期化
 例 :
Bool Key の値を取得するときの処理内容は
UBlackboardKeyType_Bool::GetValue()
Vector Key の初期化処理
 Vector Key の値を初期化するときの処理内容は
UBlackboardKeyType_Vector::InitializeMemory()
 FAISystem::InvalidLocation を Set している
 (/Runtime/AIModule/Private/BehaviorTree/Blackboard/BlackboardKeyType_Vector.cpp line:56)
Vector Key の初期化処理
 FAISystem::InvalidLocation は {x, y, z} が float の最大値を取る値
 (/Runtime/AIModule/Classes/AITypes.h line:24)
 float の最大値は 2 の 128 乗 ≒ 3.402823466e+38
→ 謎の巨大な値の正体
 つまり、謎の巨大な値は意図的に設定されたものである
Vector Key の初期値の取り扱い
ということは…
 初期値をそのまま計算に使わないように注意する必要がある
 例えば、FVector::Normalize() を実行すると
途中計算で x * x した時点で不定値 (–nan(ind)) が生じる
Vector Key の初期値の取り扱い
 Vector Key が初期値かどうか判定する方法は?
→ IsVectorValueSet() を使う
 (/Runtime/AIModule/Private/BehaviorTree/BlackboardComponent.cpp line:671)
 名前が Key Name である Vector Key が
謎の巨大な値であれば false を返す実装になっている
 初期値を計算に利用してしまうのを避けるために使える
 IsRotatorValueSet() など、他の Type 版は存在しない
Vector Key の初期値の取り扱い
 Vector Key を初期値に戻す方法は?
→ ClearValue() を使う
 (/Runtime/AIModule/Private/BehaviorTree/BlackboardComponent.cpp line:689)
 Vector 以外の Type の Key に対しても使える
 UBlackboardKeyType_○○::Clear() を間接的に呼び出して
Key に特定の値を Set する実装になっている
 Key にどのような値が Set されるかは実装次第だが、
標準で用意されている Type においては
初期値が Set される実装になっているため、
初期値に戻す関数として考えて差し支えない
Vector Key の初期値の取り扱い
 謎の巨大な値を『無効な値』として扱うと便利なことがある
 例: 目標地点が設定されていればそこへ移動する Behavior Tree Task
Vector Key の初期値の取り扱い
 謎の巨大な値を『無効な値』として扱うと便利なことがある
 例: 目標地点が設定されていればそこへ移動する Behavior Tree Task
まとめ
 Key の初期値は規定されている
 不定値ではない
 Blueprint の変数の初期値と一致するとは限らない
 Rotator / Vector Key は初期値として『無効な値』扱いの
巨大な値が設定されるので、
そのまま計算に使わないように注意
 IsVectorValueSet() で初期値でないことを確認してから使うなどする
 Vector Key においては
初期値を『無効な値』として活用することもできる
余談 1 : Blackboard の Key の
データはどこにある?
 UBlackboardComponent のメンバである
TArray<uint8> ValueMemory に格納される
 色々な Type の変数が一緒くたに格納される
 UBlackboardComponent::GetValue() の実装などを参照
 (/Runtime/AIModule/Classes/BehaviorTree/BlackboardComponent.h line:355)
 Blackboard Key を TArray 化できないのは
このような実装になっているためだと思われる
 BlackboardKeyType_○○ はあくまで
Key の取り扱いを規定するもので、
この Class の Instance がデータを持つわけではない
余談 2 : 存在しない名前の Key を
Get しようとすると何が返る?
 UBlackboardKeyType_○○::InvalidValue として
定義されたものが返る
 Key の初期値と InvalidValue は
同一のものが設定されている模様
 よって、存在しない Key の名前を与えて
GetValueAsVector() を呼び出すと謎の巨大な値が返る
 ただし、初期値と InvalidValue を別の値にすることは
実装上可能なので、常に等しいとは限らない点に注意
Behavior Tree Task の
通常版と AI 版の Event の違い
通常版と AI 版の Event?
 Behavior Tree Task の Overridable な Event のこと
 例 : Receive Tick 系の場合
 ReceiveTick() : 通常版
 ReceiveTickAI() : AI 版
通常版と AI 版の比較
 どっちを使えばいいの?
 AI を作るんだから AI 版?
 でも Behavior Tree って AI を作るためのものなのでは?
 通常版は何のために存在するのか?
 両方実装したらどちらが呼び出される?両方?
 通常版の Owner Actor は何を指すの?
Behavior Tree Task の
通常版と AI 版の Event の違い
Blueprint で検証
検証 1 : どちらが呼び出されるか
 AIController で RunBehaviorTree() を行い
以下のような Behavior Tree Task を実行する
 片方を実装しなかったり両方実装したりして、
どのように実行されるかを見る
検証 1 : どちらが呼び出されるか
 AI 版だけ実装 → AI 版が実行される
 通常版だけ実装 → 通常版が実行される
検証 1 : どちらが呼び出されるか
 両方実装 → AI 版だけが実行される
検証 1 : どちらが呼び出されるか
 AI 版は置くだけで何のノードも繋がない → ???
(虚無)
検証 1 : どちらが呼び出されるか
 AI 版は置くだけで何の Node も繋がない → AI 版が呼び出される
検証 1 : どちらが呼び出されるか
 わかったこと
 通常版と AI 版はどちらか片方だけが実行される
 両方実装されている場合、AI 版の実行が優先される
 Event に Node を繋いでいなくても実装されている扱いになる
検証 2 :
通常版の Owner Actor は何を指す?
 GetDisplayName() を使って名前を取得し PrintString()
→ AIController でした
Behavior Tree Task の
通常版と AI 版の Event の違い
エンジンソースを読む
通常版と AI 版の呼び分け部分を読む
 例として ReceiveTick 系の呼び分けを見ていく
 UBTTask_BlueprintBase::TickTask() で
通常版と AI 版の呼び分けを行っている
 (/Runtime/AIModule/Private/BehaviorTree/Tasks/BTTask_BlueprintBase.cpp line:98)
通常版と AI 版の呼び分け部分を読む
 以下の 2 つの式が両方 true だと AI 版が呼ばれる
 AIOwner != nullptr
 (ReceiveTickImplementations &
FBTNodeBPImplementationHelper::AISpecific)
通常版と AI 版の呼び分け部分を読む
 AIOwner != nullptr について
 AIController* AIOwner は
UBTTask_BlueprintBase::SetOwner() にて変更の機会がある
 (/Runtime/AIModule/Private/BehaviorTree/Tasks/BTTask_BlueprintBase.cpp line:39)
 AActor* ActorOwner には引数 InActorOwner がそのまま入る
 AIOwner は InActorOwner を Cast() した結果が入るので、
InActorOwner が AIController* でない場合は nullptr となる
通常版と AI 版の呼び分け部分を読む
 AIOwner != nullptr について
 UBTTask_BlueprintBase::SetOwner() は
UBTNode::InitializeInSubtree() にて呼び出しの機会がある
 (/Runtime/AIModule/Private/BehaviorTree/BTNode.cpp line:68)
 UBehaviorTreeComponent の Owner が引数として渡される
 つまり、UBehaviorTreeComponent を持つ Actor が
AAIController であるとき、式は true となる
ReceiveTick 系の挙動を追う
 (ReceiveTickImplementations &
FBTNodeBPImplementationHelper::AISpecific) について
 uint32 ReceiveTickImplementations には
FBTNodeBPImplementationHelper::CheckEventImplementationVersion()
の返却値が入る
 (/Runtime/AIModule/Private/BehaviorTree/Tasks/BTTask_BlueprintBase.cpp line:12)
 CheckEventImplementationVersion() は
2 つの指定した名前の Blueprint Function が実装されているかどうかを調べて
bit フラグ形式で結果を返す
ReceiveTick 系の挙動を追う
 (ReceiveTickImplementations &
FBTNodeBPImplementationHelper::AISpecific) について
 uint32 ReceiveTickImplementations には
FBTNodeBPImplementationHelper::CheckEventImplementationVersion()
の返却値が入る
 (/Runtime/AIModule/Private/BehaviorTree/Tasks/BTTask_BlueprintBase.cpp line:12)
 今回の場合、具体的には以下の値が返る
 0 (2 進数 00) : 通常版も AI 版も実装されていない
 1 (2 進数 01) : 通常版 (ReceiveTick) だけ実装されている
 2 (2 進数 10) : AI 版 (ReceiveTickAI) だけ実装されている
 3 (2 進数 11) : 両方実装されている
 つまり、(通常版の実装の有無に依らず)
AI 版が実装されていれば、式は true となる
ReceiveTick 系の挙動を追う
 条件式まとめ
 UBehaviorTreeComponent を持つ Actor が AAIController で、
かつ AI 版が実装されていれば、AI 版が呼び出される
 ReceiveTick 系を例に見てきたが、ReceiveExecute 系など他も同じ
新たな疑問
 UBehaviorTreeComponent を持つ Actor が
AAIController ではない状況なんてあるの?
 AAIController 以外の Actor に
UBehaviorTreeComponent を付けても動作するの?
AAIController 以外に
UBehaviorTreeComponent を付ける
 Blueprint では UBehaviorTreeComponent を生成・追加する
Node が見当たらなかったので、C++ でやってみた
AAIController 以外に
UBehaviorTreeComponent を付ける
 Pawn 継承 Class に UBehaviorTreeComponent を追加できた
AAIController 以外に
UBehaviorTreeComponent を付ける
 UBehaviorTreeComponent の Owner の名前と、
通常版と AI 版のどちらを呼んでいるかを表示する
Behavior Tree Task を作成
AAIController 以外に
UBehaviorTreeComponent を付ける
 UBehaviorTreeComponent を
無理矢理 Pawn に付けたものと
普通に RunBehaviorTree() で付けたものの
出力を比較する
 無理矢理 Pawn に付けた方は、
AI 版が実装されているにもかかわらず
通常版の方が呼び出されていることがわかる
まとめ
 通常版と AI 版、どっちを使えばいいの?
 ほとんどの場合、AI 版オンリーでよい
 AI 版の方が引数が具体的で便利
 通常版だとわざわざ Cast() する手間がかかる場合が多い
 一番避けるべきなのは、両方ごちゃ混ぜで使うこと
まとめ
 通常版の Owner Actor は何を指すの?
 Behavior Tree Task を動作させている
UBehaviorTreeComponent の Owner である Actor
 UBehaviorTreeComponent を
AAIController 以外の Actor に付けて動作させることは可能
 中断処理を組みやすい BehaviorTree の構造を
上手く活用できるケースがあるかも
 ただし AIMoveTo() のような経路探索系処理は
Pawn を所有する AIController の存在を前提としているため
使用は難しい
本当に言いたかったこと
検証をしよう!
 各項目の流れは以下のような感じでした
 疑問を持つ
 Blueprint で簡単に検証してみる
 新規プロジェクトを作成し、なるべく小さく作って検証する
 エンジンソースを読んで動作を理解する
 気合い
検証をしよう!
 わからないものをわからないまま使い続けていると…
検証をしよう!
 いつか困るかもしれません
 『わからないことをわからないままにできない』ということが、
UE4 を趣味から仕事にしたときに一番大きく変化した部分でした
 UE4 はわからなくても「なんとなく」でできてしまう面があります
 検証して、理解を深めて、情報を共有していきましょう!
検証をしよう!
 ※ 趣味で小規模にやる分にはそこまで考えなくていいです
 参考 : 何もかもわかっていない状態で作った 3 年前のぷちコン作品
ご清聴ありがとうございました!

わからないまま使っている?UE4 の AI の基本的なこと

  • 1.
    わからないまま使っている? UE4 の AIの基本的なこと らりほま 2018/02/25 Unreal Engine Meetup Nagoya #6 in 名古屋城
  • 2.
    自己紹介  らりほま (Twitter: @rarihoma)  株式会社ヒストリアのエンジニア  UE4 歴 3 年  エンジニア歴 1 年  Gray ちゃん 3D モデルデータ配布中  http://rarihoma.xvs.jp/products/graychan
  • 3.
    講演のテーマ『AI』  注意  やたらと地味で細かい内容です 間違っている部分があればご指摘お願いします  Unreal Engine 4.18.3 で検証しています  今日話さないこと  すごく賢い AI の作り方 (← むしろ自分が知りたい)  Navigation  Sensing  EQS
  • 4.
    UE4 の AIの資料  公式ドキュメント  ビヘイビアツリーのクイックスタート ガイド | Unreal Engine  https://docs.unrealengine.com/latest/JPN/Engine/AI/BehaviorTrees/QuickStart/ind ex.html  動画  はじめてのAI~ 愛のあるAIを作ろう - YouTube  https://www.youtube.com/watch?v=gT8uuc0DxWk&t=1225s  スクウェア・エニックスにおける UNREAL ENGINE 4 を用いた人工知能技術の開発 事例 - YouTube  https://www.youtube.com/watch?v=BV2GTGbSjq8  書籍  Unreal Engine 4 で極めるゲーム開発  作れる!学べる!Unreal Engine 4 ゲーム開発入門
  • 5.
    目次  UE4 のAI の雑な解説  UserBlackboard() と RunBehaviorTree() の動作  Blackboard の Key の初期値  Behavior Tree Task の通常版と AI 版の Event の違い
  • 6.
    UE4 の AIの雑な解説
  • 7.
    UE4 の AIの雑な解説  BehaviorTree  挙動を定義  中断処理を 組みやすい
  • 8.
    UE4 の AIの雑な解説  Blackboard  変数を定義  変数のことを Key と呼ぶ
  • 9.
    UE4 の AIの雑な解説  AIController で UseBlackboard() と RunBehaviorTree() を実行 → AIController が所有 (Possess) している Pawn が BehaviorTree に従って動作する
  • 10.
  • 11.
    UseBlackboard() と RunBehaviorTree() に関する疑問 2 つの Node は具体的に何を行っているのか?  2 つの Node の ReturnValue は何を返しているのか?
  • 12.
  • 13.
    検証 : AIController のComponent を見る  UseBlackboard() は BlackboardComponent を返しているので、 RunBehaviorTree() も何らかの Component を付加しているのでは? → AIController に付加された Component を 各 Node の実行前・実行後で比較する
  • 14.
    検証 : AIController のComponent を見る  AIController に最初から付加されている Component
  • 15.
    検証 : AIController のComponent を見る  UseBlackboard() 実行後  BlackboardComponent が付加される
  • 16.
    検証 : AIController のComponent を見る  UseBlackboard() → RunBehaviorTree() 実行後  BTComponent が付加される
  • 17.
    検証 : AIController のComponent を見る  UseBlackboard() は実行せずに RunBehaviorTree() だけ実行後  !?!?!?  BTComponent だけでなく BlackboardComponent も付加される
  • 18.
  • 19.
  • 21.
    UseBlackboard() のソースを読む  ソースからわかること UBlackboardComponent がなければ AAIController に追加する  UBlackboardComponent が既にある場合は、 追加せずに既存のものを初期化する  UseBlackboard() を何度も呼び出しても UBlackboardComponent は 1 つだけしか付加されない  UBlackboardData (Blackboard の Asset) が異なる場合、 新しく指定された方で初期化される  引数 BlackboardAsset が nullptr のときのみ false を返す  return される変数 bSuccess が 初期値 true から変更される機会が実は存在しない
  • 22.
  • 24.
    RunBehaviorTree() のソースを読む  ソースからわかること UBehaviorTreeComponent がなければ AAIController に追加する  RunBehaviorTree() を何度も呼び出しても UBehaviorTreeComponent は 1 つだけしか付加されない
  • 25.
    RunBehaviorTree() のソースを読む  ソースからわかること 引数 BTAsset に Blackboard Asset が指定されていて、 かつ UBlacokboardComponent が存在しないか 存在していても互換性がない場合、 UseBlackboard() が実行される  UseBlackboard() Node を実行しなくても UBlackboardComponent が追加されていたのはこのため  既存の Blackboard の Asset が BTAsset が指定する Blackboard Asset の親である場合、 または両者の Key がすべて同一であれば、 互換性があると見なされる  要するに、これから実行する BehaviorTree 内で使われる可能性のある Key が 既に存在する UBlackboardComponent 内に無いのは困るので、 その場合は UBlackboardComponent を作り直してしまえ、ということ
  • 26.
    RunBehaviorTree() のソースを読む  ソースからわかること 引数 BTAsset が NULL のときのみ false を返す  bSuccess = UseBlackboard(~); という行は UseBlackboard() が常に true を返す状況でしか呼ばれないため、 return される変数 bSuccess は初期値 true から変更されない
  • 27.
    実例 1  UseBlackboard()は 呼び出さずに RunBehaviorTree() だけ 呼び出した場合  BehaviorTree に BlackboardAsset が 設定されているので、 内部で UseBlackboard() が走って UBlackboardComponent が 生成され、問題なく動作する
  • 28.
    実例 2  UseBlackboard()は 呼び出さずに RunBehaviorTree() だけ 呼び出した場合  BehaviorTree に BlackboardAsset が 設定されていないので、 内部で UseBlackboard() が走らず UBlackboardComponent が 生成されないので、 アクセス違反を起こす可能性がある
  • 29.
    実例 3  RunBehaviorTree()で実行される BehaviorTree と UseBlackboard() で既に生成された Blackboard とで Blackboard Asset の互換性がない場合  UBlackboardComponent が初期化され、データが消し飛ぶ 互換性なし
  • 30.
    実例 4  RunBehaviorTree()で実行される BehaviorTree と UseBlackboard() で既に生成された Blackboard とで Blackboard Asset の互換性がある場合  UBlackboardComponent のデータは維持される  さらに、子の Blackboard にしか存在しない Key も使える 互換性あり (子 - 親 の関係)
  • 31.
    実例 5  上と下のNode 群はほぼ同義
  • 32.
    まとめ  2 つのNode は具体的に何を行っているのか?  UseBlackboard() は UBlackboardComponent を、 RunBehaviorTree() は UBehaviorTreeComponent を生成する  RunBehaviorTree() は UBlackboardComponent が存在しない場合には UseBlackboard() を呼び出すので、 UseBlackboard() を明示的に呼び出す必要はない  ただし BehaviorTree に Blackboard Asset が設定されている必要がある  BehaviorTree に設定されている Blackboard Asset の子を使いたい場合や RunBehaviorTree() の呼び出し前に Key の Set を行いたい場合は、 UseBlackboard() を明示的に呼び出す必要がある  両方共、何回呼び出しても対応する Component は 1 つしか生成されない  複数回呼び出しても、無視されるか、既存のものが新しい設定で初期化される
  • 33.
    まとめ  2 つのNode の ReturnValue は何を返しているのか?  引数が None であるときに false を返している  IsValid() で事前に判定している場合、利用する必要はない
  • 34.
    Blackboard の Keyの初期値
  • 35.
    Key の初期値に関する疑問  Blueprintの変数には、初期値を設定することができる  一方、Blackboard の Key には、初期値を設定するところがない  Blackboard の Key の初期値はどうなっている?
  • 36.
    Blackboard の Keyの初期値 Blueprint で検証
  • 37.
    初期値の検証 1  Blackboardで扱うことができるすべての Type の Key を作成し、 Editor での Play 中に値を確認してみる  Enum : UNKNOWN! とは?  String : n/a とは?  Rotator / Vector : (invalid) とは?
  • 38.
    初期値の検証 2  Keyを GetBlackboardValueAs○○() で取得して PrintString() してみる
  • 39.
    初期値の検証 2  Keyを GetBlackboardValueAs○○() で取得して PrintString() してみる  Enum : (Byte の) 0  String : 空の文字列  Rotator / Vector : !?!?!?
  • 40.
    初期値の検証  ひとまず初期値はわかった  が、Rotator/ Vector は 何故こんな値なのか?  意図的な設定なのか  たまたまメモリに 残っていた値を 解釈したらこうなったのか
  • 41.
    Blackboard の Keyの初期値 エンジンソースを読む
  • 42.
    Key の Type毎の処理定義  Blackboard の Key に対する処理は、 UBlackboardKeyType を継承した UBlackboardKeyType_○○ で規定されている  (/Runtime/AIModule/Classes/BehaviorTree/Blackboard/BlackboardKeyType.h)  GetValue() : 値の取得  SetValue() : 値の設定  InitializeMemory() : 値の初期化  例 : Bool Key の値を取得するときの処理内容は UBlackboardKeyType_Bool::GetValue()
  • 43.
    Vector Key の初期化処理 Vector Key の値を初期化するときの処理内容は UBlackboardKeyType_Vector::InitializeMemory()  FAISystem::InvalidLocation を Set している  (/Runtime/AIModule/Private/BehaviorTree/Blackboard/BlackboardKeyType_Vector.cpp line:56)
  • 44.
    Vector Key の初期化処理 FAISystem::InvalidLocation は {x, y, z} が float の最大値を取る値  (/Runtime/AIModule/Classes/AITypes.h line:24)  float の最大値は 2 の 128 乗 ≒ 3.402823466e+38 → 謎の巨大な値の正体  つまり、謎の巨大な値は意図的に設定されたものである
  • 45.
    Vector Key の初期値の取り扱い ということは… 初期値をそのまま計算に使わないように注意する必要がある  例えば、FVector::Normalize() を実行すると 途中計算で x * x した時点で不定値 (–nan(ind)) が生じる
  • 46.
    Vector Key の初期値の取り扱い Vector Key が初期値かどうか判定する方法は? → IsVectorValueSet() を使う  (/Runtime/AIModule/Private/BehaviorTree/BlackboardComponent.cpp line:671)  名前が Key Name である Vector Key が 謎の巨大な値であれば false を返す実装になっている  初期値を計算に利用してしまうのを避けるために使える  IsRotatorValueSet() など、他の Type 版は存在しない
  • 47.
    Vector Key の初期値の取り扱い Vector Key を初期値に戻す方法は? → ClearValue() を使う  (/Runtime/AIModule/Private/BehaviorTree/BlackboardComponent.cpp line:689)  Vector 以外の Type の Key に対しても使える  UBlackboardKeyType_○○::Clear() を間接的に呼び出して Key に特定の値を Set する実装になっている  Key にどのような値が Set されるかは実装次第だが、 標準で用意されている Type においては 初期値が Set される実装になっているため、 初期値に戻す関数として考えて差し支えない
  • 48.
    Vector Key の初期値の取り扱い 謎の巨大な値を『無効な値』として扱うと便利なことがある  例: 目標地点が設定されていればそこへ移動する Behavior Tree Task
  • 49.
    Vector Key の初期値の取り扱い 謎の巨大な値を『無効な値』として扱うと便利なことがある  例: 目標地点が設定されていればそこへ移動する Behavior Tree Task
  • 50.
    まとめ  Key の初期値は規定されている 不定値ではない  Blueprint の変数の初期値と一致するとは限らない  Rotator / Vector Key は初期値として『無効な値』扱いの 巨大な値が設定されるので、 そのまま計算に使わないように注意  IsVectorValueSet() で初期値でないことを確認してから使うなどする  Vector Key においては 初期値を『無効な値』として活用することもできる
  • 51.
    余談 1 :Blackboard の Key の データはどこにある?  UBlackboardComponent のメンバである TArray<uint8> ValueMemory に格納される  色々な Type の変数が一緒くたに格納される  UBlackboardComponent::GetValue() の実装などを参照  (/Runtime/AIModule/Classes/BehaviorTree/BlackboardComponent.h line:355)  Blackboard Key を TArray 化できないのは このような実装になっているためだと思われる  BlackboardKeyType_○○ はあくまで Key の取り扱いを規定するもので、 この Class の Instance がデータを持つわけではない
  • 52.
    余談 2 :存在しない名前の Key を Get しようとすると何が返る?  UBlackboardKeyType_○○::InvalidValue として 定義されたものが返る  Key の初期値と InvalidValue は 同一のものが設定されている模様  よって、存在しない Key の名前を与えて GetValueAsVector() を呼び出すと謎の巨大な値が返る  ただし、初期値と InvalidValue を別の値にすることは 実装上可能なので、常に等しいとは限らない点に注意
  • 53.
    Behavior Tree Taskの 通常版と AI 版の Event の違い
  • 54.
    通常版と AI 版のEvent?  Behavior Tree Task の Overridable な Event のこと  例 : Receive Tick 系の場合  ReceiveTick() : 通常版  ReceiveTickAI() : AI 版
  • 55.
    通常版と AI 版の比較 どっちを使えばいいの?  AI を作るんだから AI 版?  でも Behavior Tree って AI を作るためのものなのでは?  通常版は何のために存在するのか?  両方実装したらどちらが呼び出される?両方?  通常版の Owner Actor は何を指すの?
  • 56.
    Behavior Tree Taskの 通常版と AI 版の Event の違い Blueprint で検証
  • 57.
    検証 1 :どちらが呼び出されるか  AIController で RunBehaviorTree() を行い 以下のような Behavior Tree Task を実行する  片方を実装しなかったり両方実装したりして、 どのように実行されるかを見る
  • 58.
    検証 1 :どちらが呼び出されるか  AI 版だけ実装 → AI 版が実行される  通常版だけ実装 → 通常版が実行される
  • 59.
    検証 1 :どちらが呼び出されるか  両方実装 → AI 版だけが実行される
  • 60.
    検証 1 :どちらが呼び出されるか  AI 版は置くだけで何のノードも繋がない → ??? (虚無)
  • 61.
    検証 1 :どちらが呼び出されるか  AI 版は置くだけで何の Node も繋がない → AI 版が呼び出される
  • 62.
    検証 1 :どちらが呼び出されるか  わかったこと  通常版と AI 版はどちらか片方だけが実行される  両方実装されている場合、AI 版の実行が優先される  Event に Node を繋いでいなくても実装されている扱いになる
  • 63.
    検証 2 : 通常版のOwner Actor は何を指す?  GetDisplayName() を使って名前を取得し PrintString() → AIController でした
  • 64.
    Behavior Tree Taskの 通常版と AI 版の Event の違い エンジンソースを読む
  • 65.
    通常版と AI 版の呼び分け部分を読む 例として ReceiveTick 系の呼び分けを見ていく  UBTTask_BlueprintBase::TickTask() で 通常版と AI 版の呼び分けを行っている  (/Runtime/AIModule/Private/BehaviorTree/Tasks/BTTask_BlueprintBase.cpp line:98)
  • 66.
    通常版と AI 版の呼び分け部分を読む 以下の 2 つの式が両方 true だと AI 版が呼ばれる  AIOwner != nullptr  (ReceiveTickImplementations & FBTNodeBPImplementationHelper::AISpecific)
  • 67.
    通常版と AI 版の呼び分け部分を読む AIOwner != nullptr について  AIController* AIOwner は UBTTask_BlueprintBase::SetOwner() にて変更の機会がある  (/Runtime/AIModule/Private/BehaviorTree/Tasks/BTTask_BlueprintBase.cpp line:39)  AActor* ActorOwner には引数 InActorOwner がそのまま入る  AIOwner は InActorOwner を Cast() した結果が入るので、 InActorOwner が AIController* でない場合は nullptr となる
  • 68.
    通常版と AI 版の呼び分け部分を読む AIOwner != nullptr について  UBTTask_BlueprintBase::SetOwner() は UBTNode::InitializeInSubtree() にて呼び出しの機会がある  (/Runtime/AIModule/Private/BehaviorTree/BTNode.cpp line:68)  UBehaviorTreeComponent の Owner が引数として渡される  つまり、UBehaviorTreeComponent を持つ Actor が AAIController であるとき、式は true となる
  • 69.
    ReceiveTick 系の挙動を追う  (ReceiveTickImplementations& FBTNodeBPImplementationHelper::AISpecific) について  uint32 ReceiveTickImplementations には FBTNodeBPImplementationHelper::CheckEventImplementationVersion() の返却値が入る  (/Runtime/AIModule/Private/BehaviorTree/Tasks/BTTask_BlueprintBase.cpp line:12)  CheckEventImplementationVersion() は 2 つの指定した名前の Blueprint Function が実装されているかどうかを調べて bit フラグ形式で結果を返す
  • 70.
    ReceiveTick 系の挙動を追う  (ReceiveTickImplementations& FBTNodeBPImplementationHelper::AISpecific) について  uint32 ReceiveTickImplementations には FBTNodeBPImplementationHelper::CheckEventImplementationVersion() の返却値が入る  (/Runtime/AIModule/Private/BehaviorTree/Tasks/BTTask_BlueprintBase.cpp line:12)  今回の場合、具体的には以下の値が返る  0 (2 進数 00) : 通常版も AI 版も実装されていない  1 (2 進数 01) : 通常版 (ReceiveTick) だけ実装されている  2 (2 進数 10) : AI 版 (ReceiveTickAI) だけ実装されている  3 (2 進数 11) : 両方実装されている  つまり、(通常版の実装の有無に依らず) AI 版が実装されていれば、式は true となる
  • 71.
    ReceiveTick 系の挙動を追う  条件式まとめ UBehaviorTreeComponent を持つ Actor が AAIController で、 かつ AI 版が実装されていれば、AI 版が呼び出される  ReceiveTick 系を例に見てきたが、ReceiveExecute 系など他も同じ
  • 72.
    新たな疑問  UBehaviorTreeComponent を持つActor が AAIController ではない状況なんてあるの?  AAIController 以外の Actor に UBehaviorTreeComponent を付けても動作するの?
  • 73.
    AAIController 以外に UBehaviorTreeComponent を付ける Blueprint では UBehaviorTreeComponent を生成・追加する Node が見当たらなかったので、C++ でやってみた
  • 74.
    AAIController 以外に UBehaviorTreeComponent を付ける Pawn 継承 Class に UBehaviorTreeComponent を追加できた
  • 75.
    AAIController 以外に UBehaviorTreeComponent を付ける UBehaviorTreeComponent の Owner の名前と、 通常版と AI 版のどちらを呼んでいるかを表示する Behavior Tree Task を作成
  • 77.
    AAIController 以外に UBehaviorTreeComponent を付ける UBehaviorTreeComponent を 無理矢理 Pawn に付けたものと 普通に RunBehaviorTree() で付けたものの 出力を比較する  無理矢理 Pawn に付けた方は、 AI 版が実装されているにもかかわらず 通常版の方が呼び出されていることがわかる
  • 78.
    まとめ  通常版と AI版、どっちを使えばいいの?  ほとんどの場合、AI 版オンリーでよい  AI 版の方が引数が具体的で便利  通常版だとわざわざ Cast() する手間がかかる場合が多い  一番避けるべきなのは、両方ごちゃ混ぜで使うこと
  • 79.
    まとめ  通常版の OwnerActor は何を指すの?  Behavior Tree Task を動作させている UBehaviorTreeComponent の Owner である Actor  UBehaviorTreeComponent を AAIController 以外の Actor に付けて動作させることは可能  中断処理を組みやすい BehaviorTree の構造を 上手く活用できるケースがあるかも  ただし AIMoveTo() のような経路探索系処理は Pawn を所有する AIController の存在を前提としているため 使用は難しい
  • 80.
  • 81.
    検証をしよう!  各項目の流れは以下のような感じでした  疑問を持つ Blueprint で簡単に検証してみる  新規プロジェクトを作成し、なるべく小さく作って検証する  エンジンソースを読んで動作を理解する  気合い
  • 82.
  • 83.
    検証をしよう!  いつか困るかもしれません  『わからないことをわからないままにできない』ということが、 UE4を趣味から仕事にしたときに一番大きく変化した部分でした  UE4 はわからなくても「なんとなく」でできてしまう面があります  検証して、理解を深めて、情報を共有していきましょう!
  • 84.
    検証をしよう!  ※ 趣味で小規模にやる分にはそこまで考えなくていいです 参考 : 何もかもわかっていない状態で作った 3 年前のぷちコン作品
  • 85.