Skip to content

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 :

HandlerNiveauDescription
onDialogglobal / sceneBlock dialog — afficher du texte
onChoiceglobal / sceneBlock choice — présenter des choix
onConditionglobal / sceneBlock condition — évaluer et brancher
onActionglobal / sceneBlock action — déclencher des effets
onResolveCharacterglobal / sceneRésoudre quel personnage parle
onBeforeBlockglobalAvant chaque block (delay, animations d'entrée…)
onValidateNextBlockglobalValider avant de progresser vers un block
onInvalidateBlockglobalRéagir quand la validation échoue
onSceneEnterglobal / sceneUne scène démarre
onSceneExitglobal / sceneUne scène se termine
onBlocksceneOverride un block spécifique par UUID
onDialogIdsceneOverride un block DIALOG spécifique par UUID (type-safe)
onChoiceIdsceneOverride un block CHOICE spécifique par UUID (type-safe)
onConditionIdsceneOverride un block CONDITION spécifique par UUID (type-safe)
onActionIdsceneOverride un block ACTION spécifique par UUID (type-safe)
onResolveConditionglobalRésolveur unifié de conditions (visibilité des choix + pré-évaluation des conditions)
setChoiceFilterglobalDé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.

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

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 SceneHandle spé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 :

  1. handle.onBlock(uuid) ou handle.onDialogId(uuid) / handle.onActionId(uuid) / ... — override spécifique à un block
  2. handle.onDialog() / handle.onChoice() / ... — handler de type au niveau scène
  3. engine.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.

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

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.

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

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.

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) 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.

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) 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.

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