Godot のサンプル
ゲームでよくある場面を FlowTask で書いたサンプルと、それを試すデモのシーンです。写して、自分のゲームに合わせて変えて使ってください。Unity にも同じ構成のサンプルがあります(Unity のサンプル)。
- リポジトリを取得し、Godot 4.4.1 以降の .NET 版で
samples/godot/を開きます。 - 実行(F5)を押します。メインシーンは
Demo/Demo.tscnです。
- 左のボタンで場面を選びます。Escape(
ui_cancel)が戻るキーです。 - 右上の Pause でゲームを止めると、敵は止まり、メニューと左のボタンは動き続けます。止めている間の Hit は、再開したときに届きます。
- このプロジェクトは、リポジトリの FlowTask をソースから参照します(
FlowTaskSamples.csprojの ProjectReference)。ゲームに写すときは、NuGet のFlowTask.Godotを入れます(インストール)。 - UI はシーンファイルを使わず、コードで組んでいます。ゲームではシーンにするところです。
- ストア、リソースの読み込み、設定の保存先は、デモ用の偽物です(
Demo/DemoServices.cs)。
| 場面 | フォルダ | 示すこと |
|---|---|---|
| 戻るキー | BackKey/ |
層付きのリスト、Flow.Own、Signal の合成。一番上の層で待つフローに届け、Block() で飲み込む |
| 確認ダイアログ | ConfirmDialog/ |
ボタン、戻るキー、時間切れの Race。閉じるアニメーション(Tween)を finally で待つ |
| 敵 AI | Enemy/ |
RunWhileInTree とゲームの Clock、被弾の購読、FlowProperty.WaitUntil |
| チュートリアル | Tutorial/ |
手順ごとのスコープが NodeLifetime.Own で吹き出しを持つ、スキップの Race |
| 購入 | Shop/ |
Task のストアのブリッジ、想定した失敗を専用の例外型にする、再試行、遅れて届いたレシート |
| ポーズ | Pause/ |
Pause() の参照数、UI の Clock、閉じるときに時間の上限付きで保存する、ツリーのポーズを合わせる |
| 並列読み込み | Loading/ |
WhenAll、進捗、取り消し、スレッドの読み込みを止めずに手放す |
GameClocks.cs は、ほかの場面が使う Clock の組です。敵やタイマーは Game(DefaultClock の子で、Engine.TimeScale に従う)で、メニューやダイアログは Ui(UnscaledClock の子)で動かすので、ゲームを止めても UI は動き続けます。GameClocks と BackKeyRouter は World(セッション)ごとに 1 つ作ってフローに渡し、static なフィールドには置きません。デモは GameClocks.ForCurrentScope() で Clock を作ります。デモのフローが持つので、デモが終わると World から外れ、デモのシーンを読み直しても Clock は増えません。
ノードとフローの寿命は、NodeLifetime.Own(スコープがノードを持つ)と、RunWhileInTree / WhileInTree(ノードがフローを持つ)で結んでいます(ノードの寿命)。詳しい説明は各ファイルのコメントにあります。以下は Godot に固有の要点です。場面の考え方は Unity のサンプルと同じです。
BackKeyInputは_UnhandledInputでui_cancelを読むので、フォーカスのあるコントロールが先にキーを使えば、そちらが優先されます。押下を待っていたフローは、同じフレームのうちに再開します。- Android の戻るボタン(
NotificationWMGoBackRequest)も渡します。そのためには、プロジェクト設定のapplication/config/quit_on_go_backを false にします。 - ツリーのポーズ中もポーズメニューを戻るキーで閉じられるよう、
ProcessModeをAlwaysにしています。
確認ダイアログ
Section titled “確認ダイアログ”- ダイアログのノードは
NodeLifetime.Ownでフローが持ち、フローが終わると解放されます。ボタンはPressedSignal()でブリッジします。 - 閉じるアニメーションの Tween は、
SetIgnoreTimeScale()とTweenPauseMode.Processで倍率とツリーのポーズに影響されません。ノードが解放されると Tween はfinishedを出さずに消えるので、FlowTask.WaitUntil(tween, t => !t.IsValid())で待ちます。 - 判定の部分
Askはノードではなく素のSignal<FlowUnit>を受け取るので、ノードなしでテストできます(下のテスト)。
Clock の Pause() が止めるのは、その Clock で動くフローだけです。物理、AnimationPlayer、_process も止めるには、ツリーのポーズ(GetTree().Paused)を使います。ただし、Clock のポーズは参照数で、ツリーのポーズは 1 つの真偽値です。そこで PauseLink だけがツリーのポーズを書き、毎フレーム Game の Clock に合わせます。
- 画面は
clocks.Game.Pause()を取るだけです。誰が Pause しても(メニュー、設定画面、カットシーン)ツリーは止まり、最後の Pause が外れたときに動き出します。 PauseLinkはセッションに 1 つ、ツリーに残るノードに置きます。ほかの場所でGetTree().Pausedを書きません。FlowWorldNodeはツリーのポーズ中も Tick するので、UI の Clock のフローは動き続けます。メニューのノードはProcessMode = Alwaysにします(セットアップ)。
並列読み込み
Section titled “並列読み込み”ResourceLoader.LoadThreadedRequestで読み込みを始め、FlowTask.WaitUntil(request, r => r.IsDone)で終わりを待ちます。リソースを読み込めないと、その読み込みはResourceLoadExceptionを投げ、FlowTask.WhenAllは最初の例外でほかの読み込みを巻き戻してから、その例外を投げ直します。取り消しボタンを押したときは、LoadAllは null を返します。- スレッドの読み込みは止められず、結果を受け取ること(
LoadThreadedGet)で手放します。ただしLoadThreadedGetは読み込みが終わるまでメインスレッドを止めます。そこでThreadedResourceLoaderは、読み込み中に解放されたリクエストを自分のルートのフローで終わりまで待ってから受け取ります。取り消しボタンや画面の解放でゲームが固まりません。
tests/godot/Samples/ が、擬似入力でサンプルを動かして結果を確かめます(tools/godot/run-smoke.ps1)。ConfirmDialogAskTests.cs は、確認ダイアログの判定を、Godot のノードなしで、仮想時間の TestWorld で試す NUnit のテストです(テスト)。