Scene Transitions
Scene transitions animate the visual handoff when pushing, popping, or replacing scenes. During a transition, both scenes coexist on the stack so you can crossfade, flash, or run any custom animation.
Quick Start
Section titled “Quick Start”import { fade } from "@yagejs/renderer";
// Push with a fade to blackawait engine.scenes.push(new GameScene(), { transition: fade({ duration: 400 }),});
// Pop with a fadeawait engine.scenes.pop({ transition: fade({ duration: 300 }),});Built-in Transitions
Section titled “Built-in Transitions”Renderer (visual)
Section titled “Renderer (visual)”fade({ duration?, color? }) — Fades to a solid color and back. The
first half fades out, the second half fades in. Defaults to 300ms, black.
import { fade } from "@yagejs/renderer";
fade() // 300ms blackfade({ duration: 500, color: 0x1a1a2e }) // 500ms dark blueflash({ duration?, color? }) — Flashes a solid color that decays from
full opacity to zero. Great for impacts or screen-clearing effects. Defaults
to 200ms, white.
import { flash } from "@yagejs/renderer";
flash() // 200ms whiteflash({ duration: 150, color: 0xff0000 }) // quick red flashcrossFade({ duration? }) — Cross-dissolves between scenes. The
outgoing scene fades 1→0 while the incoming scene fades 0→1. Both stay
visible throughout — no blackout in the middle. Defaults to 400ms.
import { crossFade } from "@yagejs/renderer";
crossFade() // 400mscrossFade({ duration: 600 })@yagejs/core ships the SceneTransition contract and orchestration but
no concrete transitions — all built-ins live in @yagejs/renderer because
they need PIXI. For multi-step sequences (delayed fades, strobing flashes,
chained effects) write a custom transition against the contract; the
built-ins each manage their own scene visibility, so chaining them is
usually not what you want.
Per-Scene Defaults
Section titled “Per-Scene Defaults”Set defaultTransition on a scene class to use it automatically when no
call-site transition is provided:
class MenuScene extends Scene { readonly name = "menu"; readonly defaultTransition = fade({ duration: 300 });
onEnter() { /* ... */ }}
// Uses MenuScene.defaultTransition automaticallyawait engine.scenes.push(new MenuScene());
// Override with a call-site transitionawait engine.scenes.push(new MenuScene(), { transition: flash({ duration: 200 }),});Checking Transition State
Section titled “Checking Transition State”// On the scene managerif (engine.scenes.isTransitioning) { // don't accept input during transitions}
// On any sceneif (scene.isTransitioning) { return;}Events
Section titled “Events”The engine event bus emits events when transitions start and end:
engine.events.on("scene:transition:started", ({ kind }) => { console.log(`Transition started: ${kind}`); // "push", "pop", or "replace"});
engine.events.on("scene:transition:ended", ({ kind }) => { console.log(`Transition ended: ${kind}`);});Custom Transitions
Section titled “Custom Transitions”Implement the SceneTransition interface to create your own. The
getSceneContainer helper from @yagejs/renderer returns the PIXI
Container for a scene so you can manipulate its alpha, visible,
position, or filters directly:
import type { SceneTransition, SceneTransitionContext } from "@yagejs/core";import type { Container } from "pixi.js";import { getSceneContainer } from "@yagejs/renderer";
function slideIn(duration: number, width: number): SceneTransition { let toRoot: Container | undefined; return { duration, begin(ctx: SceneTransitionContext) { toRoot = getSceneContainer(ctx, ctx.toScene); if (toRoot) toRoot.x = width; }, tick(_dt: number, ctx: SceneTransitionContext) { if (!toRoot) return; const t = Math.min(ctx.elapsed / duration, 1); const eased = 1 - Math.pow(1 - t, 3); toRoot.x = width * (1 - eased); }, end() { if (toRoot) toRoot.x = 0; toRoot = undefined; }, };}The SceneTransitionContext gives you:
elapsed— wall-clock ms sincebegin()kind—"push","pop", or"replace"engineContext— DI container for resolving servicesfromScene/toScene— the scenes involved
Composition with Loading Scenes
Section titled “Composition with Loading Scenes”LoadingScene carries its own transition — the
one used when it hands off to its target scene. It composes cleanly with a
call-site transition passed to push/replace:
await engine.scenes.replace(new Boot(), { transition: fade({ duration: 400 }), // mount Boot with this fade});// Boot.transition animates the handoff Boot → target separately.Lifecycle Details
Section titled “Lifecycle Details”How replace Works
Section titled “How replace Works”When replacing with a transition:
- The new scene is pushed first (
onEnterfires) transition.begin()runs synchronously after mount/onEnterand before the first frame is rendered — safe to reachtoScene’s container here- Both scenes coexist during the transition (
tickper frame) transition.end()fires (can still reference both scenes)- The old scene is removed (
onExit, entity destruction) scene:replacedevent emitted
Queueing
Section titled “Queueing”Multiple push/pop/replace/popAll calls queue automatically — they
don’t cancel each other. Each completes its transition before the next
starts.
engine.scenes.popAll() is also queued: it waits for any in-flight
transition and pending ops to finish before tearing the stack down. Use it
for “restart from menu”-style flows — not as an emergency abort.