Pause menu
Pause scene updates while keeping clear visual context for players.
Pause menu
A pause menu can freeze the game, display a menu overlay, and resume cleanly when dismissed. In ExoJS, this is done entirely within a single scene: set scene.paused = true to freeze updates, show a pause overlay on scene.ui, and reverse both on resume.
Approach
One scene, one UI layer. The pause overlay (a Panel + Label) lives on scene.ui and is hidden until paused. Toggling scene.paused stops update() and all scene systems while the scene keeps drawing — so the world stays visible behind the overlay. A BlurFilter on scene.root provides visual separation.
class GameScene extends Scene {
init(loader) {
this.player = new Sprite(loader.get(Texture, 'hero'));
this.addChild(this.player);
// ... game setup ...
this.blur = new BlurFilter({ radius: 0, quality: 2 });
// Pause overlay on the UI layer, hidden until paused.
this.pausePanel = new Panel({ width: 420, height: 140, cornerRadius: 18, color: new Color(0, 0, 0, 0.6) });
this.pausePanel.anchorIn(this.ui, 'center');
this.pausePanel.visible = false;
this.ui.addChild(this.pausePanel);
this.pauseLabel = new Label('PAUSED', { fontSize: 56, fontWeight: 'bold' });
this.pauseLabel.anchorIn(this.ui, 'center');
this.pauseLabel.visible = false;
this.ui.addChild(this.pauseLabel);
this.inputs.onTrigger(Keyboard.Escape, () => this.togglePause());
}
update(delta) {
// Not called while paused — SceneManager skips update() + systems.
// ... normal game logic ...
}
draw(context) {
context.backend.clear(new Color(20, 24, 34));
context.render(this.root);
}
destroy() {
this.root.clearFilters();
super.destroy();
}
togglePause() {
this.paused = !this.paused;
this.pausePanel.visible = this.paused;
this.pauseLabel.visible = this.paused;
if (this.paused) {
this.blur.radius = 0;
this.root.filters = [this.blur];
this.tweens.create(this.blur).to({ radius: 6 }, 0.35).start();
} else {
this.root.clearFilters();
}
}
}
await app.start(new GameScene());
How scene.paused works
Setting scene.paused = true:
- Freezes
update()— the SceneManager skipsupdate()and all scene-level systems. No game logic runs. - Keeps drawing — the scene still renders every frame, so the world remains visible behind the overlay.
- Tweens on
app.tweensstill run — tween managers at the application level are unaffected, so blur animations and UI transitions animate smoothly while the game is frozen.
scene.ui widgets are always interactive regardless of scene.paused, so the pause panel’s buttons remain clickable during the pause.
Cleanup
The destroy hook clears the blur filter from scene.root. Without this, the filter array would linger if the scene is ever replaced via app.scene.setScene(...). As a rule, any filter applied in init or togglePause should be cleaned up in destroy.
Switching scenes from the pause menu
If the pause menu offers options like “Return to main menu” or “Quit”, use app.scene.setScene(...) to navigate away — it unloads the current scene and loads the next one, optionally with a fade transition:
// Inside a resume button's onClick handler:
resumeButton.onClick.add(() => {
this.togglePause(); // unpause first
});
// Inside a "main menu" button's onClick handler:
mainMenuButton.onClick.add(() => {
this.app.scene.setScene(new MainMenuScene(), { transition: { type: 'fade' } });
});
Examples
A rotating sprite with a pause overlay — press Esc to freeze the game under a blurred background with a PAUSED label, Esc again to resume.
Where to go next
The next recipe, Split screen, covers rendering the same scene from multiple viewpoints into viewport regions.