---
title: "scene-manager"
---

﻿# 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](/en/guide/persistent-entity/) migration across scenes (v2.3.0+)

## Basic Usage

### Recommended: Using Core's Static Methods

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

```typescript
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:

```typescript
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:

```typescript
// 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:

```typescript
// 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:

```typescript
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 Static Methods (Recommended)

#### Core.setScene()

Immediately switch scenes.

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

**Parameters**:
- `scene` - The scene instance to set

**Returns**:
- Returns the set scene instance

**Example**:
```typescript
const gameScene = Core.setScene(new GameScene());
console.log(gameScene.name);
```

#### Core.loadScene()

Delayed scene loading (switches on next frame).

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

**Parameters**:
- `scene` - The scene instance to load

**Example**:
```typescript
Core.loadScene(new GameOverScene());
```

#### Core.scene

Get the currently active scene.

```typescript
public static get scene(): IScene | null
```

**Returns**:
- Current scene instance, or null if no scene

**Example**:
```typescript
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:

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

#### setScene()

Immediately switch scenes.

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

#### loadScene()

Delayed scene loading.

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

#### currentScene

Get the current scene.

```typescript
public get currentScene(): IScene | null
```

#### hasScene

Check if there's an active scene.

```typescript
public get hasScene(): boolean
```

#### hasPendingScene

Check if there's a pending scene transition.

```typescript
public get hasPendingScene(): boolean
```

## Best Practices

### 1. Use Core's Static Methods

```typescript
// 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()

```typescript
// 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()`:

```typescript
// 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:

```typescript
// 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:

```typescript
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:

```typescript
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

## Related Documentation

- [Persistent Entity](/en/guide/persistent-entity/) - Learn how to keep entities across scene transitions

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