异步轨道
当 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()时,推进会被延迟直到条件满足。
使用 delay 和 waitForBlocks 的完整执行序列:
spawn → waitForBlocks 门控 → onBeforeBlock (delay) → handler → next()waitInput — 玩家输入标志
nativeProperties.waitInput 是一个被动标志 — engine 公开它但不解释它。您的游戏 handler 读取它来决定是否等待明确的玩家输入。
TrackInfo API — 可观测性
使用 scene.getTrackInfos() 来检查运行中的 async 轨道。返回每个轨道状态的只读快照:
const tracks = scene.getTrackInfos();
for (const track of tracks) {
console.log(`Track ${track.id} (parent: ${track.parentTrackId}) at block ${track.currentBlockUuid}`);
}var tracks = scene.GetTrackInfos();
foreach (var track in tracks)
{
Console.WriteLine($"Track {track.Id} (parent: {track.ParentTrackId}) at block {track.CurrentBlockUuid}");
}auto tracks = scene->getTrackInfos();
for (const auto& track : tracks) {
std::cout << "Track " << track.id
<< " (parent: " << track.parentTrackId << ")"
<< " at block " << track.currentBlockUuid << "\n";
}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 包含:id、parentTrackId、startBlockUuid、currentBlockUuid、running。用于调试覆盖层、播放模式渲染器或验证。
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 在调用:
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();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();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();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
