Skip to content

处理器

Handler

handler 是 engine 与游戏之间的桥梁。它们像观察者一样工作 — 你注册一个函数,当对应事件发生时 engine 会调用它。显示文本、播放动画、评估状态等 — 都是通过 handler 在游戏引擎中触发相应行为。

engine 公开以下 handler:

Handler级别描述
onDialogglobal / scenedialog block — 显示文本
onChoiceglobal / scenechoice block — 呈现选项
onConditionglobal / scenecondition block — 评估和分支
onActionglobal / sceneaction block — 触发副作用
onResolveCharacterglobal / scene解析哪个角色在说话
onBeforeBlockglobal每个 block 之前(delay、入场动画…)
onValidateNextBlockglobal进入 block 前的验证
onInvalidateBlockglobal验证失败时的处理
onSceneEnterglobal / scenescene 开始
onSceneExitglobal / scenescene 结束
onBlockscene按 UUID 覆盖特定 block
onDialogIdscene按 UUID 覆盖特定 DIALOG block(类型安全)
onChoiceIdscene按 UUID 覆盖特定 CHOICE block(类型安全)
onConditionIdscene按 UUID 覆盖特定 CONDITION block(类型安全)
onActionIdscene按 UUID 覆盖特定 ACTION block(类型安全)
onResolveConditionglobal统一 condition 解析器(choice 可见性 + condition 预评估)
setChoiceFilterglobal已弃用 — 请使用 onResolveCondition 代替

onDialogonChoiceonAction必需的start() 调用时 engine 验证它们是否存在,缺失时抛出描述性错误。当安装了 onResolveCondition 时,onCondition可选的 — engine 从预评估的 condition 组中自动路由。

ts
// 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();
csharp
// 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();
cpp
// 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();
gdscript
# 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:

  1. handle.onBlock(uuid)handle.onDialogId(uuid) / handle.onActionId(uuid) / ... — block 级别的覆盖
  2. handle.onDialog() / handle.onChoice() / ... — scene 级别的类型 handler
  3. engine.onDialog() / engine.onChoice() / ... — global handler

当两个层级都存在时,两者按顺序执行 — 先 scene,再 global — 除非 scene handler 调用了 context.preventGlobalHandler() 来抑制 global 的执行。

ts
// 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();
csharp
// 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();
cpp
// 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();
gdscript
# 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 中处理。

ts
// 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);
});
csharp
engine.OnResolveCharacter(chars => party.GetActiveLeader(chars));

var handle = engine.Scene(sceneId);
handle.OnResolveCharacter(chars => battle.GetActiveUnit(chars));
cpp
engine.onResolveCharacter([](const auto& chars) {
    return party.getActiveLeader(chars);
});

auto handle = engine.scene(sceneId);
handle->onResolveCharacter([](const auto& chars) {
    return battle.getActiveUnit(chars);
});
gdscript
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

onSceneEnteronSceneExit callback 可以在 scene 开始和结束时做出反应 — 启用电影模式、冻结 NPC、准备 UI、清理资源等。它们在 global 级别(engine 上)和 scene 级别(通过 handle.onEnter() / handle.onExit())都可用。如果定义了 scene handler,它会替代 global handler。

ts
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);
});
csharp
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);
});
cpp
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);
});
gdscript
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 需要不同行为的非常特殊的场景,它是可用的。

ts
const handle = engine.scene(sceneId);
handle.onBlock('block-uuid-123', ({ block, context, next }) => {
  next();
});
csharp
var handle = engine.Scene(sceneId);
handle.OnBlock("block-uuid-123", args => {
    args.Next();
    return null;
});
cpp
auto handle = engine.scene(sceneId);
handle->onBlock("block-uuid-123", [](auto*, auto*, auto*, auto next) -> CleanupFn {
    next();
    return {};
});
gdscript
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 类型,并需要 blockcontext 的完整自动补全时使用这些方法。

ts
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();
});
csharp
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();
});
cpp
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 {};
});
gdscript
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()
)

Visual Reference

Two-Tier Handler Dispatch