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、開始アニメーション…)
onValidateNextBlockglobalblock に進む前のバリデーション
onInvalidateBlockglobalバリデーション失敗時の処理
onSceneEnterglobal / scenescene の開始
onSceneExitglobal / scenescene の終了
onBlocksceneUUID で特定の block をオーバーライド
onDialogIdsceneUUID で特定の DIALOG block をオーバーライド(型安全)
onChoiceIdsceneUUID で特定の CHOICE block をオーバーライド(型安全)
onConditionIdsceneUUID で特定の CONDITION block をオーバーライド(型安全)
onActionIdsceneUUID で特定の ACTION block をオーバーライド(型安全)
onResolveConditionglobal統合 condition リゾルバー(choice の可視性 + condition の事前評価)
setChoiceFilterglobal非推奨 — 代わりに onResolveCondition を使用してください

onDialogonChoiceonAction必須です — start() 呼び出し時に engine がその存在を検証し、欠けている場合は記述的なエラーをスローします。onConditiononResolveCondition がインストールされている場合はオプションです — 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 を2つの階層で解決します:

  • 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 を置き換えます。

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 型とコンテキストを受け取ります。

登録時に 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