Files
esengine/docs/en/guide/scene-manager.md
yhh 397f79caa5 docs: 更新 v2.3.0 版本文档和 changelog
- 添加 v2.3.0 changelog(中英文)
- 更新文档版本号从 v2.2.22+ 到 v2.3.0+
- 更新 package.json 版本至 2.3.0
- vitepress 配置添加 ignoreDeadLinks
2025-12-06 10:44:08 +08:00

11 KiB

SceneManager

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.

Use Cases

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

Features

  • 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+)

Basic Usage

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);
}

Advanced: Using SceneManager Directly

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

Scene Transitions

Immediate Transition

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());

Delayed Transition

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
    }
  }
}

API Reference

Core.setScene()

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

Core.loadScene()

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());

Core.scene

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}`);
}

SceneManager Methods (Advanced)

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

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

setScene()

Immediately switch scenes.

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

loadScene()

Delayed scene loading.

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

currentScene

Get the current scene.

public get currentScene(): IScene | null

hasScene

Check if there's an active scene.

public get hasScene(): boolean

hasPendingScene

Check if there's a pending scene transition.

public get hasPendingScene(): boolean

Best Practices

1. Use Core's Static Methods

// 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());

2. Only Call Core.update()

// 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

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
      }
    }
  }
}

4. Scene Responsibility Separation

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
}

5. Resource Management

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');
  }
}

6. Event-Driven Scene Transitions

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
      });
    }
  }
}

Architecture Overview

SceneManager's position in ECS Framework:

Core (Global Services)
  └── SceneManager (Scene Management, auto-updated)
      └── Scene (Current Scene)
          ├── EntitySystem (Systems)
          ├── Entity (Entities)
          └── Component (Components)

Comparison with WorldManager

Feature SceneManager WorldManager
Use Case 95% of game applications Advanced multi-world isolation scenarios
Complexity Simple Complex
Scene Count Single scene (switchable) Multiple Worlds, each with multiple scenes
Performance Overhead Minimal Higher
Usage Core.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.