Skip to content

scene-manager

SceneManager is a lightweight scene manager provided by ECS Framework, suitable for 95% of game applications. It provides a simple and intuitive API with support for scene transitions and delayed loading.

SceneManager is suitable for:

  • Single-player games
  • Simple multiplayer games
  • Mobile games
  • Games requiring scene transitions (menu, game, pause, etc.)
  • Projects that don’t need multi-World isolation
  • Lightweight, zero extra overhead
  • Simple and intuitive API
  • Supports delayed scene transitions (avoids switching mid-frame)
  • Automatic ECS fluent API management
  • Automatic scene lifecycle handling
  • Integrated with Core, auto-updated
  • Supports Persistent Entity migration across scenes (v2.3.0+)
Section titled “Recommended: Using Core’s Static Methods”

This is the simplest and recommended approach, suitable for most applications:

import { Core, Scene } from '@esengine/ecs-framework';
// 1. Initialize Core
Core.create({ debug: true });
// 2. Create and set scene
class GameScene extends Scene {
protected initialize(): void {
this.name = "GameScene";
// Add systems
this.addSystem(new MovementSystem());
this.addSystem(new RenderSystem());
// Create initial entities
const player = this.createEntity("Player");
player.addComponent(new Transform(400, 300));
player.addComponent(new Health(100));
}
public onStart(): void {
console.log("Game scene started");
}
}
// 3. Set scene
Core.setScene(new GameScene());
// 4. Game loop (Core.update automatically updates the scene)
function gameLoop(deltaTime: number) {
Core.update(deltaTime); // Automatically updates all services and scenes
}
// Laya engine integration
Laya.timer.frameLoop(1, this, () => {
const deltaTime = Laya.timer.delta / 1000;
Core.update(deltaTime);
});
// Cocos Creator integration
update(deltaTime: number) {
Core.update(deltaTime);
}

If you need more control, you can use SceneManager directly:

import { Core, SceneManager, Scene } from '@esengine/ecs-framework';
// Initialize Core
Core.create({ debug: true });
// Get SceneManager (already auto-created and registered by Core)
const sceneManager = Core.services.resolve(SceneManager);
// Set scene
const gameScene = new GameScene();
sceneManager.setScene(gameScene);
// Game loop (still use Core.update)
function gameLoop(deltaTime: number) {
Core.update(deltaTime); // Core automatically calls sceneManager.update()
}

Important: Regardless of which approach you use, you should only call Core.update() in the game loop. It automatically updates SceneManager and scenes. You don’t need to manually call sceneManager.update().

Use Core.setScene() or sceneManager.setScene() to immediately switch scenes:

// Method 1: Using Core (recommended)
Core.setScene(new MenuScene());
// Method 2: Using SceneManager
const sceneManager = Core.services.resolve(SceneManager);
sceneManager.setScene(new MenuScene());

Use Core.loadScene() or sceneManager.loadScene() for delayed scene transition, which takes effect on the next frame:

// Method 1: Using Core (recommended)
Core.loadScene(new GameOverScene());
// Method 2: Using SceneManager
const sceneManager = Core.services.resolve(SceneManager);
sceneManager.loadScene(new GameOverScene());

When switching scenes from within a System, use delayed transitions:

class GameOverSystem extends EntitySystem {
process(entities: readonly Entity[]): void {
const player = entities.find(e => e.name === 'Player');
const health = player?.getComponent(Health);
if (health && health.value <= 0) {
// Delayed transition to game over scene (takes effect next frame)
Core.loadScene(new GameOverScene());
// Current frame continues execution, won't interrupt current system processing
}
}
}

Immediately switch scenes.

public static setScene<T extends IScene>(scene: T): T

Parameters:

  • scene - The scene instance to set

Returns:

  • Returns the set scene instance

Example:

const gameScene = Core.setScene(new GameScene());
console.log(gameScene.name);

Delayed scene loading (switches on next frame).

public static loadScene<T extends IScene>(scene: T): void

Parameters:

  • scene - The scene instance to load

Example:

Core.loadScene(new GameOverScene());

Get the currently active scene.

public static get scene(): IScene | null

Returns:

  • Current scene instance, or null if no scene

Example:

const currentScene = Core.scene;
if (currentScene) {
console.log(`Current scene: ${currentScene.name}`);
}

If you need to use SceneManager directly, get it through the service container:

const sceneManager = Core.services.resolve(SceneManager);

Immediately switch scenes.

public setScene<T extends IScene>(scene: T): T

Delayed scene loading.

public loadScene<T extends IScene>(scene: T): void

Get the current scene.

public get currentScene(): IScene | null

Check if there’s an active scene.

public get hasScene(): boolean

Check if there’s a pending scene transition.

public get hasPendingScene(): boolean
// Recommended: Use Core's static methods
Core.setScene(new GameScene());
Core.loadScene(new MenuScene());
const currentScene = Core.scene;
// Not recommended: Don't directly use SceneManager unless you have special needs
const sceneManager = Core.services.resolve(SceneManager);
sceneManager.setScene(new GameScene());
// Correct: Only call Core.update()
function gameLoop(deltaTime: number) {
Core.update(deltaTime); // Automatically updates all services and scenes
}
// Incorrect: Don't manually call sceneManager.update()
function gameLoop(deltaTime: number) {
Core.update(deltaTime);
sceneManager.update(); // Duplicate update, will cause issues!
}

3. Use Delayed Transitions to Avoid Issues

Section titled “3. Use Delayed Transitions to Avoid Issues”

When switching scenes from within a System, use loadScene() instead of setScene():

// Recommended: Delayed transition
class HealthSystem extends EntitySystem {
process(entities: readonly Entity[]): void {
for (const entity of entities) {
const health = entity.getComponent(Health);
if (health.value <= 0) {
Core.loadScene(new GameOverScene());
// Current frame continues processing other entities
}
}
}
}
// Not recommended: Immediate transition may cause issues
class HealthSystem extends EntitySystem {
process(entities: readonly Entity[]): void {
for (const entity of entities) {
const health = entity.getComponent(Health);
if (health.value <= 0) {
Core.setScene(new GameOverScene());
// Scene switches immediately, other entities in current frame may not process correctly
}
}
}
}

Each scene should be responsible for only one specific game state:

// Good design - clear responsibilities
class MenuScene extends Scene {
// Only handles menu-related logic
}
class GameScene extends Scene {
// Only handles gameplay logic
}
class PauseScene extends Scene {
// Only handles pause screen logic
}
// Avoid this design - mixed responsibilities
class MegaScene extends Scene {
// Contains menu, game, pause, and all other logic
}

Clean up resources in the scene’s unload() method:

class GameScene extends Scene {
private textures: Map<string, any> = new Map();
private sounds: Map<string, any> = new Map();
protected initialize(): void {
this.loadResources();
}
private loadResources(): void {
this.textures.set('player', loadTexture('player.png'));
this.sounds.set('bgm', loadSound('bgm.mp3'));
}
public unload(): void {
// Cleanup resources
this.textures.clear();
this.sounds.clear();
console.log('Scene resources cleaned up');
}
}

Use the event system to trigger scene transitions, keeping code decoupled:

class GameScene extends Scene {
protected initialize(): void {
// Listen to scene transition events
this.eventSystem.on('goto:menu', () => {
Core.loadScene(new MenuScene());
});
this.eventSystem.on('goto:gameover', (data) => {
Core.loadScene(new GameOverScene());
});
}
}
// Trigger events in System
class GameLogicSystem extends EntitySystem {
process(entities: readonly Entity[]): void {
if (levelComplete) {
this.scene.eventSystem.emitSync('goto:gameover', {
score: 1000,
level: 5
});
}
}
}

SceneManager’s position in ECS Framework:

Core (Global Services)
└── SceneManager (Scene Management, auto-updated)
└── Scene (Current Scene)
├── EntitySystem (Systems)
├── Entity (Entities)
└── Component (Components)
FeatureSceneManagerWorldManager
Use Case95% of game applicationsAdvanced multi-world isolation scenarios
ComplexitySimpleComplex
Scene CountSingle scene (switchable)Multiple Worlds, each with multiple scenes
Performance OverheadMinimalHigher
UsageCore.setScene()worldManager.createWorld()

When to use SceneManager:

  • Single-player games
  • Simple multiplayer games
  • Mobile games
  • Scenes that need transitions but don’t need to run simultaneously

When to use WorldManager:

  • MMO game servers (one World per room)
  • Game lobby systems (complete isolation per game room)
  • Need to run multiple completely independent game instances

SceneManager provides simple yet powerful scene management capabilities for most games. Through Core’s static methods, you can easily manage scene transitions.