Skip to content

非同期トラック

block に nativeProperties.isAsync = true が設定されている場合、engine はメインフローとは独立して動作する並列トラックを作成します。

トラックの作成方法

port 解決中に複数の送出 connection が存在する場合:

  • 最初の非 async connection が現在のフローの継続となります
  • その他の connectionisAsync を持つ block へ)が新しい並列トラックになります

これはメイントラック async トラックの両方に適用されます — async トラックは独自の async connection からサブトラックを spawn でき、並列実行の階層を作成できます。

トラックのライフサイクル

  • onBeforeBlockすべての block で呼び出されます(メインおよび async トラック) — resolve() の詳細は Lifecycle を参照
  • async トラックはメイントラックと同様に、送出 connection をメイン vs async に分離します
  • トラックは scene 終了時または cancel() 呼び出し時に自動的にキャンセルされます
  • トラックが自然に終了した場合(connection がなくなった)、サブトラックは独立して存続します
  • トラックが明示的にキャンセルされた場合(cancel())、キャンセルはすべての子トラックにカスケードします

waitForBlocks — トラック同期

nativeProperties.waitForBlocks を使用して並列トラックを同期します。block が進行する前に訪問済みでなければならない block UUID の配列を受け入れます:

  • 開始 block の場合:トラック全体が実行開始前に待機します。必要な block がすべて訪問されるまで onBeforeBlock は呼び出されません。
  • その他の block の場合:handler が next() を呼び出すと、条件が満たされるまで進行が延期されます。

delaywaitForBlocks を使用した完全な実行シーケンス:

spawn → waitForBlocks ゲート → onBeforeBlock (delay) → handler → next()

waitInput — プレイヤー入力フラグ

nativeProperties.waitInputパッシブフラグです — engine はそれを公開しますが解釈しません。ゲーム handler がそれを読み取り、明示的なプレイヤー入力を待つかどうかを決定します。

TrackInfo API — 可観測性

scene.getTrackInfos() を使用して実行中の async トラックを検査します。各トラックの状態の読み取り専用スナップショットを返します:

ts
const tracks = scene.getTrackInfos();
for (const track of tracks) {
  console.log(`Track ${track.id} (parent: ${track.parentTrackId}) at block ${track.currentBlockUuid}`);
}
csharp
var tracks = scene.GetTrackInfos();
foreach (var track in tracks)
{
    Console.WriteLine($"Track {track.Id} (parent: {track.ParentTrackId}) at block {track.CurrentBlockUuid}");
}
cpp
auto tracks = scene->getTrackInfos();
for (const auto& track : tracks) {
    std::cout << "Track " << track.id
              << " (parent: " << track.parentTrackId << ")"
              << " at block " << track.currentBlockUuid << "\n";
}
gdscript
var tracks = scene.get_track_infos()
for track in tracks:
    print("Track %d (parent: %s) at block %s" % [
        track["id"], str(track["parentTrackId"]), track["currentBlockUuid"]])

TrackInfo には idparentTrackIdstartBlockUuidcurrentBlockUuidrunning が含まれます。デバッグオーバーレイ、プレイモードレンダラー、検証に使用します。

Async トラックで動作するもの(と動作しないもの)

async トラックは、メインの会話と並行して起こること — 環境エフェクト、並列アニメーション、仲間の反応 — に最適です。ただし制限があります。

推奨 — 並列コンテンツ:

ユースケース動作する理由
NPC の環境セリフ(「バーク」)async トラック上の dialog block — NPC がメインの会話の進行中にコメント、反応、掛け合いを行います
イベントに同期したキャラクター反応waitForBlocks を使用して特定の block に到達したときに反応をトリガー
環境音やBGMの再生action block、プレイヤーのインタラクション不要
カメラ移動のトリガーaction block、並列実行
精密なタイミングのエフェクトwaitForBlocks + delay を組み合わせて精密なタイミングを実現

非推奨 — プレイヤーインタラクションやゲームロジック分岐:

ユースケース問題となる理由
async トラック内の CHOICE blockプレイヤーは既にメイントラックとインタラクション中 — 誰が async の choice に応答するのか?
重要なゲームステート変更async トラックがキャンセルされた場合(scene 終了)、action は実行されません

async トラック内の choice

async トラック内の CHOICE block は、プレイヤーがメインの対話に既に参加している間に選択を行うべきことを意味します。最も一般的なシナリオは AI 駆動の「choice」(例:仲間の NPC がパーソナリティに基づいて自動選択する)です。async トラックが自動選択する scene レベル handler なしで CHOICE block に到達した場合、フローは停止するか無言で終了します。

複数の Scene の並列実行

engine は複数の scene の同時実行をサポートしています。各 SceneHandle は独自のステート、訪問済み block、async トラックを持ちます。グローバル handler(Tier 1)は共有されます — どの scene が呼び出しているかは scene 引数で判別できます:

ts
engine.onDialog(({ scene, block, context, next }) => {
  if (scene === mainDialogue) {
    showMainUI(block);
    // next() called later — by player input
  } else if (scene === tutorialOverlay) {
    showTutorialBubble(block);
    // auto-advance after display
    next();
  }
});

const mainDialogue = engine.scene('main-quest');
const tutorialOverlay = engine.scene('tutorial-hints');
mainDialogue.start();
tutorialOverlay.start();
csharp
engine.OnDialog(args => {
    if (args.Scene == mainDialogue)
    {
        ShowMainUI(args.Block);
        // next() called later — by player input
    }
    else if (args.Scene == tutorialOverlay)
    {
        ShowTutorialBubble(args.Block);
        // auto-advance after display
        args.Next();
    }
    return null;
});

var mainDialogue = engine.Scene("main-quest");
var tutorialOverlay = engine.Scene("tutorial-hints");
mainDialogue.Start();
tutorialOverlay.Start();
cpp
engine.onDialog([&](auto* scene, auto* block, auto*, auto next) -> CleanupFn {
    if (scene == mainDialogue.get()) {
        showMainUI(block);
        // next() called later — by player input
    } else if (scene == tutorialOverlay.get()) {
        showTutorialBubble(block);
        // auto-advance after display
        next();
    }
    return {};
});

auto mainDialogue = engine.scene("main-quest");
auto tutorialOverlay = engine.scene("tutorial-hints");
mainDialogue->start();
tutorialOverlay->start();
gdscript
engine.on_dialog(func(args):
    if args["scene"] == main_dialogue:
        show_main_ui(args["block"])
        # next() called later — by player input
    elif args["scene"] == tutorial_overlay:
        show_tutorial_bubble(args["block"])
        # auto-advance after display
        args["next"].call()
    return Callable()
)

var main_dialogue = engine.scene("main-quest")
var tutorial_overlay = engine.scene("tutorial-hints")
main_dialogue.start()
tutorial_overlay.start()

Scene ごとのルーティング

並行する scene が多い場合は、グローバル handler 内でルーティングする代わりに、各ハンドルに scene レベル(Tier 2)の handler を登録することを検討してください。よりクリーンな分離が実現でき、if/else チェーンが不要になります。

Visual Reference

  • Main track: A → B → C
  • Track 1 (parallel): D → E
  • Track 2 (sub-track of D): F
  • Scene cancel → all tracks cancelled
  • Track D ends naturally → F continues