生命周期与验证
完整生命周期
每个 Block 的执行顺序
- 上一个 block 的清理 — 上一个 block 的 handler 返回的清理函数在转换时执行(
next()被调用时) onValidateNextBlock— 执行前的验证onBeforeBlock— 预处理(必须调用resolve()才能继续)- 类型 handler(先第 2 层,再第 1 层)
Scene 事件
ts
engine.onSceneEnter(({ scene, context }) => {
// Called when handle.start() is executed
});
engine.onSceneExit(({ scene, context }) => {
// Called when the scene ends (naturally or via cancel)
});csharp
engine.OnSceneEnter(args => {
// Called when handle.Start() is executed
});
engine.OnSceneExit(args => {
// Called when the scene ends (naturally or via cancel)
});cpp
engine.onSceneEnter([](auto* scene, auto*) {
// Called when handle->start() is executed
});
engine.onSceneExit([](auto* scene, auto*) {
// Called when the scene ends (naturally or via cancel)
});gdscript
engine.on_scene_enter(func(args):
pass # Called when handle.start() is executed
)
engine.on_scene_exit(func(args):
pass # Called when the scene ends (naturally or via cancel)
)onValidateNextBlock
拦截每次 block 转换进行验证。handler 接收下一个 block(nextContext)和上一个 block(fromContext)的已解析角色:
ts
engine.onValidateNextBlock(({ nextBlock, fromBlock, nextContext, fromContext }) => {
return { valid: true };
});
engine.onInvalidateBlock(({ scene, reason }) => {
console.error('Invalid block:', reason);
scene.cancel();
});csharp
engine.OnValidateNextBlock(args => {
// args.NextContext.Character, args.FromContext?.Character
return new ValidationResult { Valid = true };
});
engine.OnInvalidateBlock(args => {
Console.Error.WriteLine($"Invalid block: {args.Reason}");
args.Scene.Cancel();
});cpp
engine.onValidateNextBlock([](const auto& args) {
// args.nextContext.character, args.fromContext.character (check args.hasFromContext)
return ValidationResult{true};
});
engine.onInvalidateBlock([](auto* scene, const auto& reason) {
std::cerr << "Invalid block: " << reason << "\n";
scene->cancel();
});gdscript
engine.on_validate_next_block(func(args):
# args["nextContext"]["character"], args["fromContext"]["character"]
return {"valid": true}
)
engine.on_invalidate_block(func(args):
printerr("Invalid block: %s" % args["reason"])
args["scene"].cancel()
)Character Gating
使用 nextContext.character 根据游戏状态控制哪些 block 可以执行:
ts
// Block if the character is stunned
engine.onValidateNextBlock(({ nextContext }) => {
const { character } = nextContext;
if (!character) return { valid: false, reason: 'no_character' };
if (game.characterHasStatus(character, 'stunned'))
return { valid: false, reason: 'character_stunned' };
return { valid: true };
});csharp
engine.OnValidateNextBlock(args => {
var character = args.NextContext.Character;
if (character == null)
return ValidationResult.Fail("no_character");
if (game.CharacterHasStatus(character, "stunned"))
return ValidationResult.Fail("character_stunned");
return ValidationResult.Ok();
});cpp
engine.onValidateNextBlock([&game](const auto& args) {
auto* character = args.nextContext.character;
if (!character) return ValidationResult{false, "no_character"};
if (game.characterHasStatus(character, "stunned"))
return ValidationResult{false, "character_stunned"};
return ValidationResult{true};
});gdscript
engine.on_validate_next_block(func(args):
var character = args["nextContext"]["character"]
if character == null:
return {"valid": false, "reason": "no_character"}
if game.character_has_status(character, "stunned"):
return {"valid": false, "reason": "character_stunned"}
return {"valid": true}
)使用 fromContext.character 验证角色之间的转换(例如:关系检查、冷却时间)。fromContext 在场景的第一个 block 中为 null。
onBeforeBlock
在每个 block 之前调用。必须调用 resolve() 才能继续:
ts
engine.onBeforeBlock(({ block, resolve }) => {
const delay = block.nativeProperties?.delay;
if (delay) {
setTimeout(resolve, delay * 1000);
} else {
resolve();
}
});csharp
engine.OnBeforeBlock(args => {
var delay = args.Block.NativeProperties?.Delay;
if (delay.HasValue)
{
// use your engine's delay system (coroutine, DOTween, Invoke, etc.)
DelayThenCall((float)delay.Value, args.Resolve);
}
else
{
args.Resolve();
}
});cpp
engine.onBeforeBlock([](const auto& args) {
auto delay = args.block->nativeProperties
? args.block->nativeProperties->delay : std::nullopt;
if (delay.has_value()) {
// use your engine's timer system (FTimerManager, SDL_AddTimer, etc.)
scheduleDelay(delay.value(), [&args]() { args.resolve(); });
} else {
args.resolve();
}
});gdscript
engine.on_before_block(func(args):
var delay = args["block"].get("nativeProperties", {}).get("delay", 0)
if delay > 0:
await get_tree().create_timer(delay).timeout
args["resolve"].call()
)清理函数
handler 可以返回一个清理函数,在离开 block 时调用:
ts
engine.onDialog(({ block, next }) => {
const element = showDialogUI(block);
// next() is called later — by player input, timer, etc.
return () => {
element.remove(); // called when the engine moves to the next block
};
});csharp
engine.OnDialog(args => {
var element = ShowDialogUI(args.Block);
// next() is called later — by player input, timer, etc.
return () => element.SetActive(false);
});cpp
engine.onDialog([](auto*, auto* block, auto* ctx, auto next) -> CleanupFn {
auto* element = showDialogUI(block);
// next() is called later — by player input, timer, etc.
return [element]() { element->remove(); };
});gdscript
engine.on_dialog(func(args):
var element = show_dialog_ui(args["block"])
# next is called later — by player input, timer, etc.
return func(): element.queue_free()
)错误边界
每个 handler 调用都包裹在 try/catch 中。如果 handler 抛出异常:
- 错误是静默的 — 不会被记录或重新抛出。如果您的 scene 意外结束,请检查您的 handler。
- 对于主轨道:scene 会干净地结束
- 对于异步轨道:只有受影响的轨道被终止 — 其他轨道和主流程继续运行
这是跨语言兼容的(TS、C#、C++、GDScript 中的 try/catch)。
cancel()
调用 scene.cancel() 会触发以下序列:
- 所有异步轨道被取消
- 当前 block 的清理函数被执行
onSceneExithandler 被调用- scene 被标记为已完成
ts
engine.onInvalidateBlock(({ scene, reason }) => {
console.error('Validation failed:', reason);
scene.cancel();
});csharp
engine.OnInvalidateBlock(args => {
Console.Error.WriteLine($"Validation failed: {args.Reason}");
args.Scene.Cancel();
});cpp
engine.onInvalidateBlock([](auto* scene, const auto& reason) {
std::cerr << "Validation failed: " << reason << "\n";
scene->cancel();
});gdscript
engine.on_invalidate_block(func(args):
printerr("Validation failed: %s" % args["reason"])
args["scene"].cancel()
)NativeProperties
控制 engine 如何调度 block 的执行属性:
| 字段 | 类型 | 描述 |
|---|---|---|
isAsync | boolean? | 在并行异步轨道上执行 |
delay | number? | 执行前的延迟(由 onBeforeBlock 消费) |
timeout | number? | 执行超时 |
portPerCharacter | boolean? | metadata 中每个角色一个输出端口 |
skipIfMissingActor | boolean? | 如果引用的角色不存在则跳过 block |
debug | boolean? | 编辑器调试标志 |
waitForBlocks | string[]? | 此 block 进展前必须已被访问的 block UUID |
waitInput | boolean? | 用于显式玩家输入控制的被动标志 |
