Skip to content

异步轨道

当 block 设置了 nativeProperties.isAsync = true 时,engine 会创建一个独立于主流程运行的并行轨道

轨道的创建方式

在端口解析过程中,如果存在多个输出连接:

  • 第一个非 async 连接成为当前流程的延续
  • 其他连接(指向具有 isAsync 的 block)成为新的并行轨道

这适用于主轨道 async 轨道 — async 轨道可以从自己的 async 连接中创建子轨道,形成并行执行的层次结构。

轨道生命周期

  • onBeforeBlock 会为所有 block 调用(主轨道和 async 轨道) — resolve() 的详细信息请参阅 Lifecycle
  • async 轨道像主轨道一样将输出连接分为 main 和 async
  • 轨道在 scene 结束或调用 cancel() 时自动取消
  • 当轨道自然结束时(没有更多连接),其子轨道继续独立存在
  • 当轨道被显式取消时(cancel()),取消会级联到所有子轨道

waitForBlocks — 轨道同步

使用 nativeProperties.waitForBlocks 来同步并行轨道。它接受一个 block UUID 数组,这些 block 必须在当前 block 可以继续之前被访问:

  • 在起始 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 环境对话("barks")async 轨道上的 dialog block — NPC 在主对话进行时评论、反应或闲聊
与事件同步的角色反应使用 waitForBlocks 在到达特定 block 时触发反应
播放环境音效或音乐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 和异步轨道。全局 handler(第 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,建议在每个 handle 上注册 scene 级(第 2 层)handler,而不是在全局 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