Lifecycle & Validation
Lifecycle complet
Ordre d'exécution pour chaque block
- Cleanup du block précédent — La fonction de cleanup retournée par le handler du block précédent s'exécute au moment de la transition (quand
next()est appelé) onValidateNextBlock— Validation avant exécutiononBeforeBlock— Pré-traitement (doit appelerresolve()pour continuer)- Handler de type (Tier 2 puis Tier 1)
Events de scène
engine.onSceneEnter(({ scene, context }) => {
// Called when handle.start() is executed
});
engine.onSceneExit(({ scene, context }) => {
// Called when the scene ends (naturally or via cancel)
});engine.OnSceneEnter(args => {
// Called when handle.Start() is executed
});
engine.OnSceneExit(args => {
// Called when the scene ends (naturally or via cancel)
});engine.onSceneEnter([](auto* scene, auto*) {
// Called when handle->start() is executed
});
engine.onSceneExit([](auto* scene, auto*) {
// Called when the scene ends (naturally or via cancel)
});engine.on_scene_enter(func(args):
pass # Called when handle.start() is executed
)
engine.on_scene_exit(func(args):
pass # Called when the scene ends (naturally or via cancel)
)onValidateNextBlock
Intercepte chaque transition de block pour validation. Le handler reçoit le personnage résolu du prochain block (nextContext) et du block précédent (fromContext) :
engine.onValidateNextBlock(({ nextBlock, fromBlock, nextContext, fromContext }) => {
return { valid: true };
});
engine.onInvalidateBlock(({ scene, reason }) => {
console.error('Invalid block:', reason);
scene.cancel();
});engine.OnValidateNextBlock(args => {
// args.NextContext.Character, args.FromContext?.Character
return new ValidationResult { Valid = true };
});
engine.OnInvalidateBlock(args => {
Console.Error.WriteLine($"Invalid block: {args.Reason}");
args.Scene.Cancel();
});engine.onValidateNextBlock([](const auto& args) {
// args.nextContext.character, args.fromContext.character (check args.hasFromContext)
return ValidationResult{true};
});
engine.onInvalidateBlock([](auto* scene, const auto& reason) {
std::cerr << "Invalid block: " << reason << "\n";
scene->cancel();
});engine.on_validate_next_block(func(args):
# args["nextContext"]["character"], args["fromContext"]["character"]
return {"valid": true}
)
engine.on_invalidate_block(func(args):
printerr("Invalid block: %s" % args["reason"])
args["scene"].cancel()
)Character Gating
Utilisez nextContext.character pour contrôler quels blocks peuvent s'exécuter selon l'état du jeu :
// Block if the character is stunned
engine.onValidateNextBlock(({ nextContext }) => {
const { character } = nextContext;
if (!character) return { valid: false, reason: 'no_character' };
if (game.characterHasStatus(character, 'stunned'))
return { valid: false, reason: 'character_stunned' };
return { valid: true };
});engine.OnValidateNextBlock(args => {
var character = args.NextContext.Character;
if (character == null)
return ValidationResult.Fail("no_character");
if (game.CharacterHasStatus(character, "stunned"))
return ValidationResult.Fail("character_stunned");
return ValidationResult.Ok();
});engine.onValidateNextBlock([&game](const auto& args) {
auto* character = args.nextContext.character;
if (!character) return ValidationResult{false, "no_character"};
if (game.characterHasStatus(character, "stunned"))
return ValidationResult{false, "character_stunned"};
return ValidationResult{true};
});engine.on_validate_next_block(func(args):
var character = args["nextContext"]["character"]
if character == null:
return {"valid": false, "reason": "no_character"}
if game.character_has_status(character, "stunned"):
return {"valid": false, "reason": "character_stunned"}
return {"valid": true}
)Utilisez fromContext.character pour valider les transitions entre personnages (ex: relations, cooldowns). fromContext est null pour le premier block d'une scène.
onBeforeBlock
Appelé avant chaque block. resolve() doit être appelé pour continuer :
engine.onBeforeBlock(({ block, resolve }) => {
const delay = block.nativeProperties?.delay;
if (delay) {
setTimeout(resolve, delay * 1000);
} else {
resolve();
}
});engine.OnBeforeBlock(args => {
var delay = args.Block.NativeProperties?.Delay;
if (delay.HasValue)
{
// use your engine's delay system (coroutine, DOTween, Invoke, etc.)
DelayThenCall((float)delay.Value, args.Resolve);
}
else
{
args.Resolve();
}
});engine.onBeforeBlock([](const auto& args) {
auto delay = args.block->nativeProperties
? args.block->nativeProperties->delay : std::nullopt;
if (delay.has_value()) {
// use your engine's timer system (FTimerManager, SDL_AddTimer, etc.)
scheduleDelay(delay.value(), [&args]() { args.resolve(); });
} else {
args.resolve();
}
});engine.on_before_block(func(args):
var delay = args["block"].get("nativeProperties", {}).get("delay", 0)
if delay > 0:
await get_tree().create_timer(delay).timeout
args["resolve"].call()
)Fonctions de cleanup
Un handler peut retourner une fonction de cleanup, appelée quand le block est quitté :
engine.onDialog(({ block, next }) => {
const element = showDialogUI(block);
// next() is called later — by player input, timer, etc.
return () => {
element.remove(); // called when the engine moves to the next block
};
});engine.OnDialog(args => {
var element = ShowDialogUI(args.Block);
// next() is called later — by player input, timer, etc.
return () => element.SetActive(false);
});engine.onDialog([](auto*, auto* block, auto* ctx, auto next) -> CleanupFn {
auto* element = showDialogUI(block);
// next() is called later — by player input, timer, etc.
return [element]() { element->remove(); };
});engine.on_dialog(func(args):
var element = show_dialog_ui(args["block"])
# next is called later — by player input, timer, etc.
return func(): element.queue_free()
)Error Boundaries
Chaque appel de handler est encapsulé dans un try/catch. Si un handler throw :
- L'erreur est silencieuse — elle n'est pas loguée ni re-throw. Si votre scène se termine de façon inattendue, vérifiez vos handlers.
- Pour le main track : la scène se termine proprement
- Pour les async tracks : seul le track affecté est terminé — les autres tracks et le flow principal continuent
C'est compatible cross-language (try/catch en TS, C#, C++, GDScript).
cancel()
Appeler scene.cancel() déclenche cette séquence :
- Tous les async tracks sont annulés
- La fonction de cleanup du block courant est exécutée
- Le handler
onSceneExitest appelé - La scène est marquée comme terminée
engine.onInvalidateBlock(({ scene, reason }) => {
console.error('Validation failed:', reason);
scene.cancel();
});engine.OnInvalidateBlock(args => {
Console.Error.WriteLine($"Validation failed: {args.Reason}");
args.Scene.Cancel();
});engine.onInvalidateBlock([](auto* scene, const auto& reason) {
std::cerr << "Validation failed: " << reason << "\n";
scene->cancel();
});engine.on_invalidate_block(func(args):
printerr("Validation failed: %s" % args["reason"])
args["scene"].cancel()
)NativeProperties
Propriétés d'exécution qui contrôlent comment un block est dispatché par le engine :
| Champ | Type | Description |
|---|---|---|
isAsync | boolean? | Exécuter sur un track async parallèle |
delay | number? | Délai avant exécution (consommé par onBeforeBlock) |
timeout | number? | Timeout d'exécution |
portPerCharacter | boolean? | Un port de sortie par personnage dans metadata |
skipIfMissingActor | boolean? | Sauter le block si l'acteur référencé est absent |
debug | boolean? | Flag de debug pour l'éditeur |
waitForBlocks | string[]? | UUIDs de blocks qui doivent avoir été visités avant que ce block puisse progresser |
waitInput | boolean? | Flag passif pour contrôle explicite de l'input joueur |
