Handlers
Handlers
Les handlers sont le pont entre le engine et votre jeu. Ils fonctionnent comme des observateurs — vous abonnez une fonction, et le engine l'exécute quand l'événement correspondant se produit. C'est à travers eux que vous déclenchez les bons comportements dans votre moteur : afficher du texte, jouer une animation, évaluer un état, etc.
Le engine expose les handlers suivants :
| Handler | Niveau | Description |
|---|---|---|
onDialog | global / scene | Block dialog — afficher du texte |
onChoice | global / scene | Block choice — présenter des choix |
onCondition | global / scene | Block condition — évaluer et brancher |
onAction | global / scene | Block action — déclencher des effets |
onResolveCharacter | global / scene | Résoudre quel personnage parle |
onBeforeBlock | global | Avant chaque block (delay, animations d'entrée…) |
onValidateNextBlock | global | Valider avant de progresser vers un block |
onInvalidateBlock | global | Réagir quand la validation échoue |
onSceneEnter | global / scene | Une scène démarre |
onSceneExit | global / scene | Une scène se termine |
onBlock | scene | Override un block spécifique par UUID |
onDialogId | scene | Override un block DIALOG spécifique par UUID (type-safe) |
onChoiceId | scene | Override un block CHOICE spécifique par UUID (type-safe) |
onConditionId | scene | Override un block CONDITION spécifique par UUID (type-safe) |
onActionId | scene | Override un block ACTION spécifique par UUID (type-safe) |
onResolveCondition | global | Résolveur unifié de conditions (visibilité des choix + pré-évaluation des conditions) |
setChoiceFilter | global | Déprécié — utilisez onResolveCondition à la place |
onDialog, onChoice et onAction sont required — le engine valide leur présence à l'appel de start() et throw une erreur descriptive si un manque. onCondition est optionnel quand onResolveCondition est installé — le engine auto-route depuis les groupes de conditions pré-évalués.
// 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
Le engine résout les handlers sur deux niveaux :
- Global handlers — enregistrés sur le engine, ils définissent le comportement par défaut de chaque scène. Ils suffisent dans la majorité des cas.
- Scene handlers — enregistrés sur un
SceneHandlespécifique, ils permettent de court-circuiter ou d'étendre le comportement par défaut quand une scène nécessite un rendu ou un contrôle différent. C'est rare, mais disponible.
Quand un block est dispatché, le engine résout le handler dans cet ordre :
handle.onBlock(uuid)ouhandle.onDialogId(uuid)/handle.onActionId(uuid)/ ... — override spécifique à un blockhandle.onDialog()/handle.onChoice()/ ... — handler de type au niveau scèneengine.onDialog()/engine.onChoice()/ ... — handler global
Quand les deux niveaux sont présents, les deux s'exécutent en séquence — scène d'abord, puis global — sauf si le scene handler appelle context.preventGlobalHandler() pour supprimer le passage 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
Le système de résolution de personnage est optionnel. En enregistrant un callback onResolveCharacter, le engine l'invoque avant chaque block qui contient des personnages dans ses metadata.characters. Le callback reçoit la liste des personnages assignés au block et retourne celui qui doit être actif — ou undefined si aucun n'est disponible. Le personnage résolu est ensuite accessible via context.character dans tous les handlers.
C'est le point d'intégration idéal pour interroger l'état de votre jeu : vérifier si un personnage est présent dans la scène, en vie, dans le champ de la caméra, etc. Retourner undefined ouvre la porte à plusieurs stratégies : sauter le block via skipIfMissingActor, annuler la scène via handle.cancel(), ou gérer le cas directement dans le 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
Les callbacks onSceneEnter et onSceneExit permettent de réagir au démarrage et à la fin d'une scène — activer un mode cinématique, arrêter les NPC, préparer l'UI, nettoyer les ressources, etc. Ils sont disponibles au niveau global (sur le engine) et au niveau scène (via handle.onEnter() / handle.onExit()). Le scene handler remplace le global s'il est défini.
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) permet de cibler un block précis par son identifiant pour lui attribuer un handler dédié. C'est un cas d'usage rare — les handlers génériques couvrent la grande majorité des besoins — mais pour des scénarios très spécifiques où un block individuel nécessite un comportement distinct, c'est disponible.
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) et onActionId(uuid) sont des alternatives type-safe à onBlock(uuid). Ils fonctionnent exactement de la même façon — même priorité, même support de preventGlobalHandler — mais le handler reçoit le type de block spécialisé et le contexte au lieu de l'union générique.
Utilisez-les quand vous connaissez le type du block au moment de l'enregistrement et que vous voulez l'autocomplétion complète sur block et 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()
)