【State Tree 入門】State Tree の State の選択順と Transition の基本の確認

alt text

前回:UE5 State Tree に入門してみる - Self-Taught CODE Tokushima Tech Blog

前回はクイックスタートを行いましたが、今回はそこで利用した State と他の State に遷移する Transition の挙動を確認しておきます。

準備

STTask_PrintStateEvent

State がどのように呼ばれているかを確認します。基本的にはこのタスクしか使いません。

全体
alt text
Enter State 後の Finish Task 部分
alt text
variables
alt text
  • CallFinishTask:Finish Task を呼ぶかどうか
    • false の場合は Finish Task しない
  • ForceFailed:Finish Task する場合に失敗させるか
    • false の場合は、成功になる
  • DelayBeforeFinish:Finish Task 前に Delay を入れるか
    • false の場合は、Enter State 時に即 Finish となる
  • DelayDulation:Delay の長さ

Delay の設定に関しては、おそらく多くの場合、Enter State して即 Finish することは無いと思うのと、State の終了順での挙動確認のために入れています。

また、Tick に関しては、ログの抑制と Tick の呼ばれる順が見たかっただけなので Do Once としています。

BP_StudyTransitionActor

今回利用する Actor です。これにテスト用の State Tree を設定しています。

State Tree の Run Status の変化を見るためだけのイベントハンドラをつけています。

alt text
alt text

Debugger

Debugger を出しておきます。

alt text alt text

State のイベントの呼び出し確認

State Tree Child_1 Child_2
alt text alt text alt text
# PIE 開始
[STTask_PrintStateEvent_C] [Child_1] Enter State
[STTask_PrintStateEvent_C_0] [Child_2] Enter State
[BP_StudyTransitionActor_C_1] State Tree Status Changed: Running
[STTask_PrintStateEvent_C] [Child_1] First Tick
[STTask_PrintStateEvent_C_0] [Child_2] First Tick
# PIE 停止
[STTask_PrintStateEvent_C_0] [Child_2] Exit State
[STTask_PrintStateEvent_C] [Child_1] Exit State
[BP_StudyTransitionActor_C_1] State Tree Status Changed: Stopped
  • Enter State は Root からリーフに向かって実行
  • State Tree の Run Status は、リーフの選択ができた時点で Running のように見えるが、たまたまかも
  • Tick も Root からリーフに向かって実行されているよう
  • Exit State は逆にリーフから Root (遷移先) に向かっている
  • この例では、Finish Task を全く呼ばないので、 Child_2 の State の Task が終了せず PIE 停止まで動かない

State の終了の確認

親のみ終了

Child_1 (親) Child_2 (子)
alt text alt text

Debugger

Start Frame Next Frame
alt text alt text

Output Log

[Child_1] Enter State
[Child_2] Enter State
[Child_1] First Tick
[Child_2] First Tick
[Child_1] Finish Task # Finish したのは親
[Child_2] Completed Succeeded
[Child_1] Completed Succeeded
[Child_2] Exit State
[Child_1] Exit State
[Child_1] Enter State
# 以降繰り返し
  • Output Log から、Finish したのは親 (Child_1) であるとわかる
    • その後、先に子 (Child_2) が成功で完了、続いて親が成功で完了している
  • Debugger から、Start Tree 時点で active statesRoot,Child_1,Child_2 が選ばれているのがわかる
    • 次のフレームに移動したところで、State(s) Completed となり、Child_2,Child_1,Root の順で表記されている
    • 続いて Tick Transitions - Trigger Transitions になり、また Root からの選択が繰り返されている
    • これは、遷移先が現状 Root になっており、また Root が選ばれると Child_2 までの選択が繰り返されるからだろう

子のみ終了

Child_1 (親) Child_2 (子)
alt text alt text

Debugger

Start Frame Next Frame
alt text alt text

Output Log

[Child_1] Enter State
[Child_2] Enter State
[Child_1] First Tick
[Child_2] First Tick
[Child_2] Finish Task # Finish したのは子
[Child_2] Completed Succeeded
[Child_1] Completed Succeeded
[Child_2] Exit State
[Child_1] Exit State
[Child_1] Enter State
# 以降繰り返し
  • 親のみ終了時と異なるのは以下
    • Debugger で Next Frame の Tick State TreeTick Tasks で実行されているのが、先程は Child_2 (終了しない方) だったが、今回も Child_1 と終了しない方が実行されている。
    • おそらく前の Tick で Finish Task されているため、そちらは実行されていないのだろう

State の終了順序の確認

続いて、両方が終了する場合を確認する。

即親が失敗する

alt text

Delay 無しなので Enter State で即 Finish Task で失敗させる。

Output Log

[Child_1] Enter State
[Child_1] Finish Task Failed
[Child_1] Completed Failed
[Child_1] Exit State
[Child_1] Enter State
# 以降繰り返し

Debugger

alt text

  • Debugger を見ても、Output Log を見ても 子(Child_2)がそもそも選択されないことがわかる
  • 親が Enter State 時点で失敗するのはあまり無いケースかもしれないが一応

親が失敗し、子が成功する

親が先 alt text alt text
子が先 Delay Duration を 0.2 Delay Duration を 0.1

親が先に失敗 Output Log

[Child_1] Enter State
[Child_2] Enter State
[Child_1] First Tick
[Child_2] First Tick
[Child_1] Finish Task Failed # 親が失敗
[Child_2] Completed Failed
[Child_1] Completed Failed
[Child_2] Exit State
[Child_1] Exit State
[Child_1] Enter State
# 以降繰り返し

子が先に成功 Output Log

[Child_1] Enter State
[Child_2] Enter State
[Child_1] First Tick
[Child_2] First Tick
[Child_2] Finish Task Succeeded # 子が成功
[Child_2] Completed Succeeded
[Child_1] Completed Succeeded
[Child_2] Exit State
[Child_1] Exit State
[Child_1] Enter State
# 以降繰り返し
  • 0.1sec の差をつけているので、終了のフレームがズレていると思われる
  • よって、それぞれ先に終了した方の結果で State の結果も Failed/Succeeded が決まっている

⚠️異なるパターン

いくつかサンプルを作っているとほぼ同じサンプルにも関わらず結果が変わるケースが出ました。

alt text alt text alt text

親が先に失敗 Output Log

[Parent] Enter State
[Child] Enter State
[Parent] First Tick
[Child] First Tick
[Parent] Finish Task Failed
[Child] Finish Task Succeeded # 親が先に成功しているのに子のログが出る時間を延ばしても同じ
[Child] Completed Succeeded # 成功してしまう
[Parent] Completed Succeeded
[Child] Exit State
[Parent] Exit State
  • 当然子が先に成功するに関しても成功になる
  • 気になるのは、ChildDelay 時間を延ばすと、その分ログが出るのも待機するようになる
  • 先程の例では、同じように ChildDelay の時間を延ばしても待機しない
  • UE Editor を再起動しても挙動は変わらない

同時に起こる場合

一応同じフレームで発生する場合も調べておく。これは最初に行った「即親が失敗」とな異なり、Delay 有りなので、子も選択(active) になる。

Output Log

親が失敗・子が成功

[Child_1] Enter State
[Child_2] Enter State
[Child_1] First Tick
[Child_2] First Tick
[Child_1] Finish Task Failed # 親が先に失敗
[Child_2] Finish Task Succeeded
[Child_2] Completed Failed
[Child_1] Completed Failed
[Child_2] Exit State
[Child_1] Exit State
[Child_1] Enter State
[Child_2] Enter State
[Child_2] Finish Task Succeeded # 子が先に成功
[Child_1] Finish Task Failed
[Child_2] Completed Failed
[Child_1] Completed Failed
[Child_2] Exit State
[Child_1] Exit State
# 以降繰り返し

親が成功・子が失敗

[Child_1] Enter State
[Child_2] Enter State
[Child_1] First Tick
[Child_2] First Tick
[Child_1] Finish Task Succeeded # 親が先に成功
[Child_2] Finish Task Failed
[Child_2] Completed Failed
[Child_1] Completed Failed
[Child_2] Exit State
[Child_1] Exit State
[Child_1] Enter State
[Child_2] Enter State
[Child_2] Finish Task Failed # 子が先に失敗
[Child_1] Finish Task Succeeded
[Child_2] Completed Failed
[Child_1] Completed Failed
[Child_2] Exit State
[Child_1] Exit State
# 以降繰り返し
  • どうやら同フレームで Finish Task した場合には、失敗が優先される ように見える
  • 🚧ここに関してはこの挙動が正しいか分からない上に、あまり起こり得ないような状況なので一旦保留する

Transition の確認

Task の結果による Transition の確認

alt text

Child_1 Child_2
alt text alt text

Transitions は Completed が最後になるようにしてある。
これは Completed が先にあると、成功・失敗にかかわらず Completed に遷移してしまうため

条件 結果
親 (Child_1) が先に成功 Child_1 Succeeded
親 (Child_1) が先に失敗 Child_1 Failed
子 (Child_1-2) が先に成功 Child_1-2 Succeeded
子 (Child_1-2) が先に失敗 Child_1-2 Failed

Output Log

# 親が先に成功
[Child_1] Enter State
[Child_1-2] Enter State
[Child_1] First Tick
[Child_1-2] First Tick
[Child_1] Finish Task Succeeded
[Child_1-2] Completed Succeeded
[Child_1] Completed Succeeded
[Child_1-2] Exit State
[Child_1] Exit State
[Child_1 Succeeded] Enter State
# 親が先に失敗
[Child_1] Enter State
[Child_1-2] Enter State
[Child_1] First Tick
[Child_1-2] First Tick
[Child_1] Finish Task Failed
[Child_1-2] Completed Failed
[Child_1] Completed Failed
[Child_1-2] Exit State
[Child_1] Exit State
[Child_1 Failed] Enter State
# 子が先に成功
[Child_1] Enter State
[Child_1-2] Enter State
[Child_1] First Tick
[Child_1-2] First Tick
[Child_1-2] Finish Task Succeeded
[Child_1-2] Completed Succeeded
[Child_1] Completed Succeeded
[Child_1-2] Exit State
[Child_1] Exit State
[Child_1-2 Succeeded] Enter State
# 子が先に失敗
[Child_1] Enter State
[Child_1-2] Enter State
[Child_1] First Tick
[Child_1-2] First Tick
[Child_1-2] Finish Task Failed
[Child_1-2] Completed Failed
[Child_1] Completed Failed
[Child_1-2] Exit State
[Child_1] Exit State
[Child_1-2 Failed] Enter State

Debugger

最後の「子が先に失敗」の Debugger を一部掲載しておきます。

alt text

  • 結果として、ほぼ期待通りの遷移となった
  • Debugger を見ても、失敗時には Root,Child_1-2 Failed が新しく active states となっているのが分かる

Transition 先の深さによる State 選択の確認

alt text

Child_2 からの遷移はシンプルに Succeeded 時です。

遷移先 結果
Level1 Level -> Level2 -> Level2-2
Level2 Level -> Level2 -> Level2-2
Level2-2 Level -> Level2 -> Level2-2
Level3 Level -> Level3
Child_1-3 Child_1 -> Child_1-3

Output Log

# 遷移先 Level1
# 遷移先 Level2
# 遷移先 Level2-2
[Child_1] Enter State
[Child_1-2] Enter State
[Child_1] First Tick
[Child_1-2] First Tick
[Child_1-2] Finish Task Succeeded
[Child_1-2] Completed Succeeded
[Child_1] Completed Succeeded
[Child_1-2] Exit State
[Child_1] Exit State
[Level1] Enter State
[Level2] Enter State
[Level2-2] Enter State
[Level1] First Tick
[Level2] First Tick
[Level2-2] First Tick
[Level2-2] Exit State
[Level2] Exit State
[Level1] Exit State
# 遷移先 Level3
[Child_1] Enter State
[Child_1-2] Enter State
[Child_1] First Tick
[Child_1-2] First Tick
[Child_1-2] Finish Task Succeeded
[Child_1-2] Completed Succeeded
[Child_1] Completed Succeeded
[Child_1-2] Exit State
[Child_1] Exit State
[Level1] Enter State
[Level3] Enter State
[Level1] First Tick
[Level3] First Tick
[Level3] Exit State
[Level1] Exit State
# 遷移先 Child_1-3
[Child_1] Enter State
[Child_1-2] Enter State
[Child_1] First Tick
[Child_1-2] First Tick
[Child_1-2] Finish Task Succeeded
[Child_1-2] Completed Succeeded
[Child_1] Completed Succeeded
[Child_1-2] Exit State
# Child_1 に Enter していない?
[Child_1-3] Enter State
[Child_1-3] First Tick
[Child_1-3] Exit State
[Child_1] Exit State
  • Root の別の子ツリー (Level1 以下) への遷移は期待通りに動作している
    • 中間ノードを選択したとしても、Child_1-2 から遷移先までを一度 Root まで戻り Level1 を通り、リーフまで選択している
  • 遷移元である Child_1 のツリー内の Child_1-3 への遷移は興味深い
    • 期待通りではあるのだが、Root まで戻り Child_1 に再度 Enter するかと思っていたが、Enter していない
    • 以下のように Debugger としては active states として Child_1 も含めて選択されているものの、Enter Sates には入っていないことがわかる

alt text

まとめ

  • 今回は State ノードがどのように選択され、イベントが実行されるかを確認した
  • また、Transition についても、基本的な部分を確認した
  • 特に複雑な条件も付与していないため、おおまか期待通りの動作となった
  • ただ、親と子が時間差で終了するとき、子の Finish Task を待つケースとそうでないケースが偶然ながら見られた。
    • このケースは再現性に乏しいが、例えば Move To Task や AI Move To を使った Task を使うと簡単に再現できる
    • 今回利用した Print と Delay だけするタスクの場合は、再現できたりできなかったりするのがよくわからない
    • いずれにしろ、不用意に中間ノードで Finish Task はしない方がベターというのが現状での認識になりそうである
  • もう少し実践的なサンプルで試してみないと、Print だけの確認では限界がありそう