处理器
Handler
handler 是 engine 与游戏之间的桥梁。它们像观察者一样工作 — 你注册一个函数,当对应事件发生时 engine 会调用它。显示文本、播放动画、评估状态等 — 都是通过 handler 在游戏引擎中触发相应行为。
engine 公开以下 handler:
| Handler | 级别 | 描述 |
|---|---|---|
onDialog | global / scene | dialog block — 显示文本 |
onChoice | global / scene | choice block — 呈现选项 |
onCondition | global / scene | condition block — 评估和分支 |
onAction | global / scene | action block — 触发副作用 |
onResolveCharacter | global / scene | 解析哪个角色在说话 |
onBeforeBlock | global | 每个 block 之前(delay、入场动画…) |
onValidateNextBlock | global | 进入 block 前的验证 |
onInvalidateBlock | global | 验证失败时的处理 |
onSceneEnter | global / scene | scene 开始 |
onSceneExit | global / scene | scene 结束 |
onBlock | scene | 按 UUID 覆盖特定 block |
onDialogId | scene | 按 UUID 覆盖特定 DIALOG block(类型安全) |
onChoiceId | scene | 按 UUID 覆盖特定 CHOICE block(类型安全) |
onConditionId | scene | 按 UUID 覆盖特定 CONDITION block(类型安全) |
onActionId | scene | 按 UUID 覆盖特定 ACTION block(类型安全) |
onResolveCondition | global | 统一 condition 解析器(choice 可见性 + condition 预评估) |
setChoiceFilter | global | 已弃用 — 请使用 onResolveCondition 代替 |
onDialog、onChoice 和 onAction 是必需的 — start() 调用时 engine 验证它们是否存在,缺失时抛出描述性错误。当安装了 onResolveCondition 时,onCondition 是可选的 — engine 从预评估的 condition 组中自动路由。
// required — the engine won't start without these
engine.onDialog(dialogHandler);
engine.onChoice(choiceHandler);
engine.onCondition(conditionHandler);
engine.onAction(actionHandler);
// optional — lifecycle, validation, pre-execution
engine.onBeforeBlock(beforeBlockHandler);
engine.onResolveCharacter(resolveCharacterHandler);
engine.onValidateNextBlock(validateHandler);
engine.onSceneEnter(sceneEnterHandler);
engine.onSceneExit(sceneExitHandler);
const handle = engine.scene(sceneId);
handle.start();// required — the engine won't start without these
engine.OnDialog(dialogHandler);
engine.OnChoice(choiceHandler);
engine.OnCondition(conditionHandler);
engine.OnAction(actionHandler);
// optional — lifecycle, validation, pre-execution
engine.OnBeforeBlock(beforeBlockHandler);
engine.OnResolveCharacter(resolveCharacterHandler);
engine.OnValidateNextBlock(validateHandler);
engine.OnSceneEnter(sceneEnterHandler);
engine.OnSceneExit(sceneExitHandler);
var handle = engine.Scene(sceneId);
handle.Start();// required — the engine won't start without these
engine.onDialog(dialogHandler);
engine.onChoice(choiceHandler);
engine.onCondition(conditionHandler);
engine.onAction(actionHandler);
// optional — lifecycle, validation, pre-execution
engine.onBeforeBlock(beforeBlockHandler);
engine.onResolveCharacter(resolveCharacterHandler);
engine.onValidateNextBlock(validateHandler);
engine.onSceneEnter(sceneEnterHandler);
engine.onSceneExit(sceneExitHandler);
auto handle = engine.scene(sceneId);
handle->start();# required — the engine won't start without these
engine.on_dialog(dialog_handler)
engine.on_choice(choice_handler)
engine.on_condition(condition_handler)
engine.on_action(action_handler)
# optional — lifecycle, validation, pre-execution
engine.on_before_block(before_block_handler)
engine.on_resolve_character(resolve_character_handler)
engine.on_validate_next_block(validate_handler)
engine.on_scene_enter(scene_enter_handler)
engine.on_scene_exit(scene_exit_handler)
var handle = engine.scene(scene_id)
handle.start()Two-Tier Handler System
engine 在两个层级上解析 handler:
- Global handler — 注册在 engine 上,定义每个 scene 的默认行为。大多数情况下这就够了。
- Scene handler — 注册在特定的
SceneHandle上,当 scene 需要不同的渲染或控制流程时,可以覆盖或扩展默认行为。这种情况很少见,但可用。
当一个 block 被分发时,engine 按以下顺序解析 handler:
handle.onBlock(uuid)或handle.onDialogId(uuid)/handle.onActionId(uuid)/ ... — block 级别的覆盖handle.onDialog()/handle.onChoice()/ ... — scene 级别的类型 handlerengine.onDialog()/engine.onChoice()/ ... — global handler
当两个层级都存在时,两者按顺序执行 — 先 scene,再 global — 除非 scene handler 调用了 context.preventGlobalHandler() 来抑制 global 的执行。
// Tier 1 — global
engine.onDialog(({ block, context, next }) => {
console.log('Global dialog handler');
next();
});
// Tier 2 — scene-specific
const handle = engine.scene(sceneId);
handle.onDialog(({ block, context, next }) => {
console.log('Scene-specific dialog handler');
context.preventGlobalHandler();
next();
});
handle.start();// Tier 1 — global
engine.OnDialog(args => {
Console.WriteLine("Global dialog handler");
args.Next();
return null;
});
// Tier 2 — scene-specific
var handle = engine.Scene(sceneId);
handle.OnDialog(args => {
Console.WriteLine("Scene-specific dialog handler");
args.Context.PreventGlobalHandler();
args.Next();
return null;
});
handle.Start();// Tier 1 — global
engine.onDialog([](auto*, auto* block, auto* ctx, auto next) -> CleanupFn {
std::cout << "Global dialog handler\n";
next();
return {};
});
// Tier 2 — scene-specific
auto handle = engine.scene(sceneId);
handle->onDialog([](auto*, auto* block, auto* ctx, auto next) -> CleanupFn {
std::cout << "Scene-specific dialog handler\n";
ctx->preventGlobalHandler();
next();
return {};
});
handle->start();# Tier 1 — global
engine.on_dialog(func(args):
print("Global dialog handler")
args["next"].call()
return Callable()
)
# Tier 2 — scene-specific
var handle = engine.scene(scene_id)
handle.on_dialog(func(args):
print("Scene-specific dialog handler")
args["context"].prevent_global_handler()
args["next"].call()
return Callable()
)
handle.start()Character Resolution
角色解析是可选的。通过注册 onResolveCharacter callback,engine 会在每个 metadata.characters 中包含角色的 block 之前调用它。callback 接收分配给 block 的角色列表,返回应该激活的角色 — 如果没有可用角色则返回 undefined。解析后的角色可通过所有 handler 中的 context.character 访问。
这是查询游戏状态的理想集成点:检查角色是否在场景中、是否存活、是否在镜头范围内等。返回 undefined 可以触发多种策略:通过 skipIfMissingActor 跳过 block、通过 handle.cancel() 取消 scene、或直接在 handler 中处理。
// Engine-level — applies to all scenes
engine.onResolveCharacter((characters) => {
return party.getActiveLeader(characters);
});
// Scene-level override
const handle = engine.scene(sceneId);
handle.onResolveCharacter((characters) => {
return battle.getActiveUnit(characters);
});engine.OnResolveCharacter(chars => party.GetActiveLeader(chars));
var handle = engine.Scene(sceneId);
handle.OnResolveCharacter(chars => battle.GetActiveUnit(chars));engine.onResolveCharacter([](const auto& chars) {
return party.getActiveLeader(chars);
});
auto handle = engine.scene(sceneId);
handle->onResolveCharacter([](const auto& chars) {
return battle.getActiveUnit(chars);
});engine.on_resolve_character(func(chars):
return party.get_active_leader(chars)
)
var handle = engine.scene(scene_id)
handle.on_resolve_character(func(chars):
return battle.get_active_unit(chars)
)Scene Lifecycle
onSceneEnter 和 onSceneExit callback 可以在 scene 开始和结束时做出反应 — 启用电影模式、冻结 NPC、准备 UI、清理资源等。它们在 global 级别(engine 上)和 scene 级别(通过 handle.onEnter() / handle.onExit())都可用。如果定义了 scene handler,它会替代 global handler。
engine.onSceneEnter(({ scene }) => {
game.cinemaMode(true);
game.stopNpcMovements();
});
engine.onSceneExit(() => {
game.cinemaMode(false);
game.resumeNpcMovements();
});
// scene-level override
const handle = engine.scene(sceneId);
handle.onEnter(({ scene }) => {
game.playIntroSequence(scene);
});engine.OnSceneEnter(args => {
Game.CinemaMode(true);
Game.StopNpcMovements();
});
engine.OnSceneExit(args => {
Game.CinemaMode(false);
Game.ResumeNpcMovements();
});
// scene-level override
var handle = engine.Scene(sceneId);
handle.OnEnter(args => {
Game.PlayIntroSequence(args.Scene);
});engine.onSceneEnter([&game](auto* scene, auto*) {
game.cinemaMode(true);
game.stopNpcMovements();
});
engine.onSceneExit([&game](auto*, auto*) {
game.cinemaMode(false);
game.resumeNpcMovements();
});
// scene-level override
auto handle = engine.scene(sceneId);
handle->onEnter([&game](auto* scene, auto*) {
game.playIntroSequence(scene);
});engine.on_scene_enter(func(args):
game.cinema_mode(true)
game.stop_npc_movements()
)
engine.on_scene_exit(func(args):
game.cinema_mode(false)
game.resume_npc_movements()
)
# scene-level override
var handle = engine.scene(scene_id)
handle.on_enter(func(args):
game.play_intro_sequence(args["scene"])
)Block Override
onBlock(uuid) 可以通过标识符定位特定 block 并为其分配专用 handler。这是一个罕见的用例 — 通用 handler 覆盖了绝大多数需求 — 但对于个别 block 需要不同行为的非常特殊的场景,它是可用的。
const handle = engine.scene(sceneId);
handle.onBlock('block-uuid-123', ({ block, context, next }) => {
next();
});var handle = engine.Scene(sceneId);
handle.OnBlock("block-uuid-123", args => {
args.Next();
return null;
});auto handle = engine.scene(sceneId);
handle->onBlock("block-uuid-123", [](auto*, auto*, auto*, auto next) -> CleanupFn {
next();
return {};
});var handle = engine.scene(scene_id)
handle.on_block("block-uuid-123", func(args):
args["next"].call()
return Callable()
)Type-Safe Block Override
onDialogId(uuid)、onChoiceId(uuid)、onConditionId(uuid) 和 onActionId(uuid) 是 onBlock(uuid) 的类型安全替代方法。工作方式完全相同 — 相同的优先级、相同的 preventGlobalHandler 支持 — 但 handler 接收特殊化的 block 类型和 context,而不是通用联合类型。
当你在注册时已知 block 类型,并需要 block 和 context 的完整自动补全时使用这些方法。
const handle = engine.scene(sceneId);
handle.onActionId('block-uuid-123', ({ block, context, next }) => {
// block is ActionBlock — actions is directly accessible
for (const action of block.actions ?? []) {
executeAction(action);
}
// context is ActionContext — resolve/reject are available
context.resolve();
next();
});var handle = engine.Scene(sceneId);
handle.OnActionId("block-uuid-123", args => {
// args.Block is ActionBlock — Actions is directly accessible
foreach (var action in args.Block.Actions ?? [])
ExecuteAction(action);
// args.Context is IActionContext — Resolve/Reject are available
args.Context.Resolve();
args.Next();
});auto handle = engine.scene(sceneId);
handle->onActionId("block-uuid-123", [](auto* scene, const ActionBlock* block, IActionContext* ctx, auto next) -> CleanupFn {
// block is const ActionBlock* — actions is directly accessible
for (const auto& action : block->actions)
executeAction(action);
// ctx is IActionContext* — resolve/reject are available
ctx->resolve();
next();
return {};
});var handle = engine.scene(scene_id)
handle.on_action_id("block-uuid-123", func(args):
# args["block"] contains actions directly
for action in args["block"].get("actions", []):
execute_action(action)
# args["context"] has resolve/reject
args["context"]["resolve"].call()
args["next"].call()
return Callable()
)