ハンドラー
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 がその存在を検証し、欠けている場合は記述的なエラーをスローします。onCondition は onResolveCondition がインストールされている場合はオプションです — 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 を2つの階層で解決します:
- 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 を置き換えます。
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 型とコンテキストを受け取ります。
登録時に 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()
)