本ページにはプロモーションが含まれます
PlaymakerUnity

【Unity】PlaymakerでUniTask(async/await)を使う

この記事は約8分で読めます。

はじめに

これなに?
PlaymakerでA,B,Cという非同期処理が終わったら最後にイベントを発火できる。または、A,B,Cという非同期処理を順番に実行して最後イベントを発火できる。

用途は?
・ロードや初期化処理がすべて終わったらゲーム開始する
・アニメーションとボイス再生が終わってから次の会話へ進む
・複数アセットの読み込み完了後にシーン遷移する
・演出・SE・カメラ移動が終わったら操作を解放する
・保存・通信などの非同期処理完了後に結果を表示する
・カットシーン再生が終わったら次のゲーム進行へ移る
・複数条件の準備が整ったタイミングでイベントを発火する
などなど、実際コードで書くと複雑だったり、Playmakerの既存アクションのみで実装すると手間がかかる機能をカスタムアクションでシンプルに実装できるようにしました。

Playmakerで非同期処理をより扱いやすくするため、
以前公開していた async/await 対応の Playmakerカスタムアクションを大幅にアップデートしました。

PlaymakerでFSM間でのステート遷移の逐次実行、並列実行ができるようになります。
Playmakerで複数のFSMを安全に非同期制御するための仕組みです。

アップデートでは、

  • 逐次実行(AwaitHandlerS)
  • 並列実行(AwaitHandlerP)
  • AsyncHandler に通知を返すアクション(NotifyAsyncHandler)

と分離し、それぞれが最適な動きをするように調整しています。

使い方

前提:

逐次(Sequence)

1・

SequenceGOとか適当に空オブジェクトを作成。

2・

AsyncHandler.csを貼り付ける。PlaymakerFSMを同オブジェクトにアタッチしてEdit。

イベント構成

3・

AwaitHandlerSカスタムアクションで逐次実行するグローバルイベントを設定。
全体的な構成。サンプルなので1つのFSMにすべて詰めているが、別のFSMのイベントをコールして戻すなども可能。

4・

各イベントの中身 EventA~Cは待機秒数が違うだけ。

5・

これが重要。どのイベントが発火してどこに完了通知を送るかを、
NotifyAsyncHandlerアクションで設定してあげる。

6・

A~Cの処理が終わったらCompleteEventを呼ぶ。これは単純にSendEventなのでNotifyAsyncHandlerアクションはいらない。

結果:

ログにあるとおり B → C → A (計6秒)の順番で処理が進み最後、CompleteEventを発火して終了してくれる。

[01:22:43.778] [AsyncHandler] OnEnable:新しいCancellationTokenSourceを生成しました。
[01:22:43.778] [AwaitHandlerS] 実行開始(Sequential)
[01:22:43.778] [AsyncHandler] StartAsync:A=EventB,B=EventC,C=EventA,Complete=CompleteEvent,Delay=True(1.00s),Parallel=False
[01:22:43.778] [AsyncHandler] Delay開始:1.00秒
[01:22:45.672] [AsyncHandler] シーケンシャルモードで実行(A→B→C)
[01:22:45.672] [AsyncHandler] イベント送信:EventB→FSM
[01:22:47.662] SequenceGO:FSM:EventBNotifyAsync:DebugLog:EVENTBDONE
[01:22:47.662] [AsyncHandler] FSMから完了通知を受信しました:EventB
[01:22:47.662] [AsyncHandler] 完了通知受信:EventB
[01:22:47.662] [AsyncHandler] イベント送信:EventC→FSM
[01:22:47.662] [NotifyAsyncHandler] AsyncHandlerに'EventB'の完了を通知しました。
[01:22:50.668] SequenceGO:FSM:EventCNotifyAsync:DebugLog:EVENTCDONE
[01:22:50.669] [AsyncHandler] FSMから完了通知を受信しました:EventC
[01:22:50.669] [AsyncHandler] 完了通知受信:EventC
[01:22:50.669] [AsyncHandler] イベント送信:EventA→FSM
[01:22:50.669] [NotifyAsyncHandler] AsyncHandlerに'EventC'の完了を通知しました。
[01:22:51.674] SequenceGO:FSM:EventANotifyAsync:DebugLog:EVENTADONE
[01:22:51.674] [AsyncHandler] FSMから完了通知を受信しました:EventA
[01:22:51.674] [AsyncHandler] 完了通知受信:EventA
[01:22:51.674] [AsyncHandler] CompleteEvent送信:CompleteEvent→FSM
[01:22:51.674] [NotifyAsyncHandler] AsyncHandlerに'EventA'の完了を通知しました。
[01:22:51.674] SequenceGO:FSM:Completed:DebugLog:CompleteEventDone

逐次は、同じFSM内のステートならステートをつなげて順番に処理すれば良いだけです。逐次に実行したいアクションが複数のFSMにある場合、実行順がバラバラだと困るので、該当のイベント順で完了通知をもって逐次処理をさせるのが目的です。

並列(Parallel)

並列はPlaymakerの仕様上、同じFSMステートグラフ内で並列実行はできないので、
FSMを別オブジェクトに分ける必要があります。
※同一オブジェクトに複数のFSMでもよいです。今回はわかりやすいようFSM_A~Cでゲームオブジェクトごと分けています。

1・

ParallelGOとか適当に空オブジェクトを作成。

2・

AsyncHandlerコンポーネントをParallelGOに貼り付けます。PlaymakerFSM(名前:ParallelFSM)を同オブジェクトにアタッチしてEdit。

各イベントラベルを作成していきます。ParallelFSMからFSM_A~CのEventA~Cを並列で実行していきためのものです。

3・

AwaitHandlerPカスタムアクションに、並列実行したい別のFSMのオブジェクトとイベントを設定(紐づける)する。

4・

各FSM_A~Cの中身。FSM_AならEventA実行 1秒待機、FSM_BならEventB実行 2秒待機、FSM_CならEventC実行 3秒待機 → 最後にNotifyAsyncHandlerアクション

5・

NotifyAsyncHandlerアクションはParallelGOで指定したイベント名で完了通知を返すようにする。
どこの(ParallelGO) → だれに(EventA~C) → 通知を返す。

6・

FSM_A~Cが並列に実行され、3秒以内でA → B → Cと完了し、最後にCompleteEventが発火します。

結果:

ログみるとわかると思うが、FSM_Cが3秒 FSM_Bが2秒 FSM_Aが1秒なので、平行実行されると3秒の間にA → B → Cと処理される。
※AwaitHandlerPアクション実行開始の際はわかりやすくするためにわざと1秒ディレイ入れてます。

[01:50:20.570] [AwaitHandlerP] 実行開始(Parallel)
[01:50:22.247] FSM_A:FSM_A:NotifyAsync:DebugLog:ParalleEventADone.
[01:50:23.286] FSM_B:FSM_B:NotifyAsync:DebugLog:ParalleEventBDone.
[01:50:24.247] FSM_C:FSM_C:NotifyAsync:DebugLog:ParalleEventCDone.
[01:50:24.247] ParallelGO:ParallelFSM:Completed:DebugLog:CompleteEventDone

補足

通常の Playmaker FSM 構造

MainFSM
├─ EventA → StateA
├─ EventB → StateB
└─ EventC → StateC

普通は、これらのイベント(EventA〜C)を順に送る or 条件で分岐して動かす。
つまり「EventAを送る → StateAが動く」という直列制御の仕組み。

並列制御(AwaitHandlerPやUniTask.WhenAll)の場合

MainFSM
├─ EventA → SubFSM_A(またはサブ処理)
├─ EventB → SubFSM_B
└─ EventC → SubFSM_C

・各イベント(A〜C)は 「処理の入り口」 として使うだけ。
・並列制御の責任は、メインFSMではなく「ハンドラー側(C#)」が持つ。

 

まとめ

Playmakerと Unitaskを組み合わせると、async/awaitでアクション制御の柔軟性が一気に広がります。
逐次と並列を分け、各ステートの完了を正確にトラッキングして通知を返す設計で安定性を実現しました。

  • 初心者でも扱いやすく
  • 大規模プロジェクトでもスケールし
  • Playmakerと C# の混在設計に相性が良い

 

おわりに

複数のPlaymaker FSMを起動時にスタートさせ、処理完了後にフラグを返し、
監視FSMが次の処理を進める――そんな管理で組んでいたけど、
この方式なら見やすく・コンパクト・簡単に非同期処理の実装ができます。

https://note.com/akasaka_c/n/n35d65ab8b3dc
noteでも書いたよ。

https://github.com/kamadochoko/PlayMakerActions
カスタムアクションはご自由にどうぞ。不具合あったらisお願い。MITライセンス。