feat(core): 添加持久化实体支持跨场景迁移
实现实体生命周期策略,允许标记实体为持久化,在场景切换时自动迁移到新场景。 主要变更: - 新增 EEntityLifecyclePolicy 枚举(SceneLocal/Persistent) - Entity 添加 setPersistent()、setSceneLocal()、isPersistent API - Scene 添加 findPersistentEntities()、extractPersistentEntities()、receiveMigratedEntities() - SceneManager.setScene() 自动处理持久化实体迁移 - 添加完整的中英文文档和 21 个测试用例
This commit is contained in:
360
docs/en/guide/persistent-entity.md
Normal file
360
docs/en/guide/persistent-entity.md
Normal file
@@ -0,0 +1,360 @@
|
||||
# Persistent Entity
|
||||
|
||||
> **Version**: v2.2.22+
|
||||
|
||||
Persistent Entity is a special type of entity that automatically migrates to the new scene during scene transitions. It is suitable for game objects that need to maintain state across scenes, such as players, game managers, audio managers, etc.
|
||||
|
||||
## Basic Concepts
|
||||
|
||||
In the ECS framework, entities have two lifecycle policies:
|
||||
|
||||
| Policy | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `SceneLocal` | Scene-local entity, destroyed when scene changes | ✓ |
|
||||
| `Persistent` | Persistent entity, automatically migrates during scene transitions | |
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Creating a Persistent Entity
|
||||
|
||||
```typescript
|
||||
import { Scene } from '@esengine/ecs-framework';
|
||||
|
||||
class GameScene extends Scene {
|
||||
protected initialize(): void {
|
||||
// Create a persistent player entity
|
||||
const player = this.createEntity('Player').setPersistent();
|
||||
player.addComponent(new Position(100, 200));
|
||||
player.addComponent(new PlayerData('Hero', 500));
|
||||
|
||||
// Create a normal enemy entity (destroyed when scene changes)
|
||||
const enemy = this.createEntity('Enemy');
|
||||
enemy.addComponent(new Position(300, 200));
|
||||
enemy.addComponent(new EnemyAI());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Behavior During Scene Transitions
|
||||
|
||||
```typescript
|
||||
import { Core, Scene } from '@esengine/ecs-framework';
|
||||
|
||||
// Initial scene
|
||||
class Level1Scene extends Scene {
|
||||
protected initialize(): void {
|
||||
// Player - persistent, will migrate to the next scene
|
||||
const player = this.createEntity('Player').setPersistent();
|
||||
player.addComponent(new Position(0, 0));
|
||||
player.addComponent(new Health(100));
|
||||
|
||||
// Enemy - scene-local, destroyed when scene changes
|
||||
const enemy = this.createEntity('Enemy');
|
||||
enemy.addComponent(new Position(100, 100));
|
||||
}
|
||||
}
|
||||
|
||||
// Target scene
|
||||
class Level2Scene extends Scene {
|
||||
protected initialize(): void {
|
||||
// New enemy
|
||||
const enemy = this.createEntity('Boss');
|
||||
enemy.addComponent(new Position(200, 200));
|
||||
}
|
||||
|
||||
public onStart(): void {
|
||||
// Player has automatically migrated to this scene
|
||||
const player = this.findEntity('Player');
|
||||
console.log(player !== null); // true
|
||||
|
||||
// Position and health data are fully preserved
|
||||
const position = player?.getComponent(Position);
|
||||
const health = player?.getComponent(Health);
|
||||
console.log(position?.x, position?.y); // 0, 0
|
||||
console.log(health?.value); // 100
|
||||
}
|
||||
}
|
||||
|
||||
// Switch scenes
|
||||
Core.create({ debug: true });
|
||||
Core.setScene(new Level1Scene());
|
||||
|
||||
// Later switch to Level2
|
||||
Core.loadScene(new Level2Scene());
|
||||
// Player entity migrates automatically, Enemy entity is destroyed
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Entity Methods
|
||||
|
||||
#### setPersistent()
|
||||
|
||||
Marks the entity as persistent, preventing destruction during scene transitions.
|
||||
|
||||
```typescript
|
||||
public setPersistent(): this
|
||||
```
|
||||
|
||||
**Returns**: Returns the entity itself for method chaining
|
||||
|
||||
**Example**:
|
||||
```typescript
|
||||
const player = scene.createEntity('Player')
|
||||
.setPersistent();
|
||||
|
||||
player.addComponent(new Position(100, 200));
|
||||
```
|
||||
|
||||
#### setSceneLocal()
|
||||
|
||||
Restores the entity to scene-local policy (default).
|
||||
|
||||
```typescript
|
||||
public setSceneLocal(): this
|
||||
```
|
||||
|
||||
**Returns**: Returns the entity itself for method chaining
|
||||
|
||||
**Example**:
|
||||
```typescript
|
||||
// Dynamically cancel persistence
|
||||
player.setSceneLocal();
|
||||
```
|
||||
|
||||
#### isPersistent
|
||||
|
||||
Checks if the entity is persistent.
|
||||
|
||||
```typescript
|
||||
public get isPersistent(): boolean
|
||||
```
|
||||
|
||||
**Example**:
|
||||
```typescript
|
||||
if (entity.isPersistent) {
|
||||
console.log('This is a persistent entity');
|
||||
}
|
||||
```
|
||||
|
||||
#### lifecyclePolicy
|
||||
|
||||
Gets the entity's lifecycle policy.
|
||||
|
||||
```typescript
|
||||
public get lifecyclePolicy(): EEntityLifecyclePolicy
|
||||
```
|
||||
|
||||
**Example**:
|
||||
```typescript
|
||||
import { EEntityLifecyclePolicy } from '@esengine/ecs-framework';
|
||||
|
||||
if (entity.lifecyclePolicy === EEntityLifecyclePolicy.Persistent) {
|
||||
console.log('Persistent entity');
|
||||
}
|
||||
```
|
||||
|
||||
### Scene Methods
|
||||
|
||||
#### findPersistentEntities()
|
||||
|
||||
Finds all persistent entities in the scene.
|
||||
|
||||
```typescript
|
||||
public findPersistentEntities(): Entity[]
|
||||
```
|
||||
|
||||
**Returns**: Array of persistent entities
|
||||
|
||||
**Example**:
|
||||
```typescript
|
||||
const persistentEntities = scene.findPersistentEntities();
|
||||
console.log(`Scene has ${persistentEntities.length} persistent entities`);
|
||||
```
|
||||
|
||||
#### extractPersistentEntities()
|
||||
|
||||
Extracts and removes all persistent entities from the scene (typically called internally by the framework).
|
||||
|
||||
```typescript
|
||||
public extractPersistentEntities(): Entity[]
|
||||
```
|
||||
|
||||
**Returns**: Array of extracted persistent entities
|
||||
|
||||
#### receiveMigratedEntities()
|
||||
|
||||
Receives migrated entities (typically called internally by the framework).
|
||||
|
||||
```typescript
|
||||
public receiveMigratedEntities(entities: Entity[]): void
|
||||
```
|
||||
|
||||
**Parameters**:
|
||||
- `entities` - Array of entities to receive
|
||||
|
||||
## Use Cases
|
||||
|
||||
### 1. Player Entity Across Levels
|
||||
|
||||
```typescript
|
||||
class PlayerSetupScene extends Scene {
|
||||
protected initialize(): void {
|
||||
// Player maintains state across all levels
|
||||
const player = this.createEntity('Player').setPersistent();
|
||||
player.addComponent(new Transform(0, 0));
|
||||
player.addComponent(new Health(100));
|
||||
player.addComponent(new Inventory());
|
||||
player.addComponent(new PlayerStats());
|
||||
}
|
||||
}
|
||||
|
||||
class Level1 extends Scene { /* ... */ }
|
||||
class Level2 extends Scene { /* ... */ }
|
||||
class Level3 extends Scene { /* ... */ }
|
||||
|
||||
// Player entity automatically migrates between all levels
|
||||
Core.setScene(new PlayerSetupScene());
|
||||
// ... game progresses
|
||||
Core.loadScene(new Level1());
|
||||
// ... level complete
|
||||
Core.loadScene(new Level2());
|
||||
// Player data (health, inventory, stats) fully preserved
|
||||
```
|
||||
|
||||
### 2. Global Managers
|
||||
|
||||
```typescript
|
||||
class BootstrapScene extends Scene {
|
||||
protected initialize(): void {
|
||||
// Audio manager - persists across scenes
|
||||
const audioManager = this.createEntity('AudioManager').setPersistent();
|
||||
audioManager.addComponent(new AudioController());
|
||||
|
||||
// Achievement manager - persists across scenes
|
||||
const achievementManager = this.createEntity('AchievementManager').setPersistent();
|
||||
achievementManager.addComponent(new AchievementTracker());
|
||||
|
||||
// Game settings - persists across scenes
|
||||
const settings = this.createEntity('GameSettings').setPersistent();
|
||||
settings.addComponent(new SettingsData());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Dynamically Toggling Persistence
|
||||
|
||||
```typescript
|
||||
class GameScene extends Scene {
|
||||
protected initialize(): void {
|
||||
// Initially created as a normal entity
|
||||
const companion = this.createEntity('Companion');
|
||||
companion.addComponent(new Transform(0, 0));
|
||||
companion.addComponent(new CompanionAI());
|
||||
|
||||
// Listen for recruitment event
|
||||
this.eventSystem.on('companion:recruited', () => {
|
||||
// After recruitment, become persistent
|
||||
companion.setPersistent();
|
||||
console.log('Companion joined the party, will follow player across scenes');
|
||||
});
|
||||
|
||||
// Listen for dismissal event
|
||||
this.eventSystem.on('companion:dismissed', () => {
|
||||
// After dismissal, restore to scene-local
|
||||
companion.setSceneLocal();
|
||||
console.log('Companion left the party, will no longer persist across scenes');
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Clearly Identify Persistent Entities
|
||||
|
||||
```typescript
|
||||
// Recommended: Mark immediately when creating
|
||||
const player = this.createEntity('Player').setPersistent();
|
||||
|
||||
// Not recommended: Marking after creation (easy to forget)
|
||||
const player = this.createEntity('Player');
|
||||
// ... lots of code ...
|
||||
player.setPersistent(); // Easy to forget
|
||||
```
|
||||
|
||||
### 2. Use Persistence Appropriately
|
||||
|
||||
```typescript
|
||||
// ✓ Entities suitable for persistence
|
||||
const player = this.createEntity('Player').setPersistent(); // Player
|
||||
const gameManager = this.createEntity('GameManager').setPersistent(); // Global manager
|
||||
const audioManager = this.createEntity('AudioManager').setPersistent(); // Audio system
|
||||
|
||||
// ✗ Entities that should NOT be persistent
|
||||
const bullet = this.createEntity('Bullet'); // Temporary objects
|
||||
const enemy = this.createEntity('Enemy'); // Level-specific enemies
|
||||
const particle = this.createEntity('Particle'); // Effect particles
|
||||
```
|
||||
|
||||
### 3. Check Migrated Entities
|
||||
|
||||
```typescript
|
||||
class NewScene extends Scene {
|
||||
public onStart(): void {
|
||||
// Check if expected persistent entities exist
|
||||
const player = this.findEntity('Player');
|
||||
if (!player) {
|
||||
console.error('Player entity did not migrate correctly!');
|
||||
// Handle error case
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Avoid Circular References
|
||||
|
||||
```typescript
|
||||
// ✗ Avoid: Persistent entity referencing scene-local entity
|
||||
class BadScene extends Scene {
|
||||
protected initialize(): void {
|
||||
const player = this.createEntity('Player').setPersistent();
|
||||
const enemy = this.createEntity('Enemy');
|
||||
|
||||
// Dangerous: player is persistent but enemy is not
|
||||
// After scene change, enemy is destroyed, reference becomes invalid
|
||||
player.addComponent(new TargetComponent(enemy));
|
||||
}
|
||||
}
|
||||
|
||||
// ✓ Recommended: Use ID references or event system
|
||||
class GoodScene extends Scene {
|
||||
protected initialize(): void {
|
||||
const player = this.createEntity('Player').setPersistent();
|
||||
const enemy = this.createEntity('Enemy');
|
||||
|
||||
// Store ID instead of direct reference
|
||||
player.addComponent(new TargetComponent(enemy.id));
|
||||
|
||||
// Or use event system for communication
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Important Notes
|
||||
|
||||
1. **Destroyed entities will not migrate**: If an entity is destroyed before scene transition, it will not migrate even if marked as persistent.
|
||||
|
||||
2. **Component data is fully preserved**: All components and their state are preserved during migration.
|
||||
|
||||
3. **Scene reference is updated**: After migration, the entity's `scene` property will point to the new scene.
|
||||
|
||||
4. **Query system is updated**: Migrated entities are automatically registered in the new scene's query system.
|
||||
|
||||
5. **Delayed transitions also work**: Persistent entities migrate when using `Core.loadScene()` for delayed transitions as well.
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Scene](./scene) - Learn the basics of scenes
|
||||
- [SceneManager](./scene-manager) - Learn about scene transitions
|
||||
- [WorldManager](./world-manager) - Learn about multi-world management
|
||||
436
docs/en/guide/scene-manager.md
Normal file
436
docs/en/guide/scene-manager.md
Normal file
@@ -0,0 +1,436 @@
|
||||
# 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](./persistent-entity) migration across scenes (v2.2.22+)
|
||||
|
||||
## 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](./persistent-entity) - Learn how to keep entities across scene transitions
|
||||
- [WorldManager](./world-manager) - Learn about advanced multi-world isolation features
|
||||
|
||||
SceneManager provides simple yet powerful scene management capabilities for most games. Through Core's static methods, you can easily manage scene transitions.
|
||||
364
docs/en/guide/scene.md
Normal file
364
docs/en/guide/scene.md
Normal file
@@ -0,0 +1,364 @@
|
||||
# Scene Management
|
||||
|
||||
In the ECS architecture, a Scene is a container for the game world, responsible for managing the lifecycle of entities, systems, and components. Scenes provide a complete ECS runtime environment.
|
||||
|
||||
## Basic Concepts
|
||||
|
||||
Scene is the core container of the ECS framework, providing:
|
||||
- Entity creation, management, and destruction
|
||||
- System registration and execution scheduling
|
||||
- Component storage and querying
|
||||
- Event system support
|
||||
- Performance monitoring and debugging information
|
||||
|
||||
## Scene Management Options
|
||||
|
||||
ECS Framework provides two scene management approaches:
|
||||
|
||||
1. **[SceneManager](./scene-manager)** - Suitable for 95% of game applications
|
||||
- Single-player games, simple multiplayer games, mobile games
|
||||
- Lightweight, simple and intuitive API
|
||||
- Supports scene transitions
|
||||
|
||||
2. **[WorldManager](./world-manager)** - Suitable for advanced multi-world isolation scenarios
|
||||
- MMO game servers, game room systems
|
||||
- Multi-World management, each World can contain multiple scenes
|
||||
- Completely isolated independent environments
|
||||
|
||||
This document focuses on the usage of the Scene class itself. For detailed information about scene managers, please refer to the corresponding documentation.
|
||||
|
||||
## Creating a Scene
|
||||
|
||||
### Inheriting the Scene Class
|
||||
|
||||
**Recommended: Inherit the Scene class to create custom scenes**
|
||||
|
||||
```typescript
|
||||
import { Scene, EntitySystem } from '@esengine/ecs-framework';
|
||||
|
||||
class GameScene extends Scene {
|
||||
protected initialize(): void {
|
||||
// Set scene name
|
||||
this.name = "GameScene";
|
||||
|
||||
// Add systems
|
||||
this.addSystem(new MovementSystem());
|
||||
this.addSystem(new RenderSystem());
|
||||
this.addSystem(new PhysicsSystem());
|
||||
|
||||
// Create initial entities
|
||||
this.createInitialEntities();
|
||||
}
|
||||
|
||||
private createInitialEntities(): void {
|
||||
// Create player
|
||||
const player = this.createEntity("Player");
|
||||
player.addComponent(new Position(400, 300));
|
||||
player.addComponent(new Health(100));
|
||||
player.addComponent(new PlayerController());
|
||||
|
||||
// Create enemies
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const enemy = this.createEntity(`Enemy_${i}`);
|
||||
enemy.addComponent(new Position(Math.random() * 800, Math.random() * 600));
|
||||
enemy.addComponent(new Health(50));
|
||||
enemy.addComponent(new EnemyAI());
|
||||
}
|
||||
}
|
||||
|
||||
public onStart(): void {
|
||||
console.log("Game scene started");
|
||||
// Logic when scene starts
|
||||
}
|
||||
|
||||
public unload(): void {
|
||||
console.log("Game scene unloaded");
|
||||
// Cleanup logic when scene unloads
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Using Scene Configuration
|
||||
|
||||
```typescript
|
||||
import { ISceneConfig } from '@esengine/ecs-framework';
|
||||
|
||||
const config: ISceneConfig = {
|
||||
name: "MainGame",
|
||||
enableEntityDirectUpdate: false
|
||||
};
|
||||
|
||||
class ConfiguredScene extends Scene {
|
||||
constructor() {
|
||||
super(config);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Scene Lifecycle
|
||||
|
||||
Scene provides complete lifecycle management:
|
||||
|
||||
```typescript
|
||||
class ExampleScene extends Scene {
|
||||
protected initialize(): void {
|
||||
// Scene initialization: setup systems and initial entities
|
||||
console.log("Scene initializing");
|
||||
}
|
||||
|
||||
public onStart(): void {
|
||||
// Scene starts running: game logic begins execution
|
||||
console.log("Scene starting");
|
||||
}
|
||||
|
||||
public unload(): void {
|
||||
// Scene unloading: cleanup resources
|
||||
console.log("Scene unloading");
|
||||
}
|
||||
}
|
||||
|
||||
// Using scenes (lifecycle automatically managed by framework)
|
||||
const scene = new ExampleScene();
|
||||
// Scene's initialize(), begin(), update(), end() are automatically called by the framework
|
||||
```
|
||||
|
||||
**Lifecycle Methods**:
|
||||
|
||||
1. `initialize()` - Scene initialization, setup systems and initial entities
|
||||
2. `begin()` / `onStart()` - Scene starts running
|
||||
3. `update()` - Per-frame update (called by scene manager)
|
||||
4. `end()` / `unload()` - Scene unloading, cleanup resources
|
||||
|
||||
## Entity Management
|
||||
|
||||
### Creating Entities
|
||||
|
||||
```typescript
|
||||
class EntityScene extends Scene {
|
||||
createGameEntities(): void {
|
||||
// Create single entity
|
||||
const player = this.createEntity("Player");
|
||||
|
||||
// Batch create entities (high performance)
|
||||
const bullets = this.createEntities(100, "Bullet");
|
||||
|
||||
// Add components to batch-created entities
|
||||
bullets.forEach((bullet, index) => {
|
||||
bullet.addComponent(new Position(index * 10, 100));
|
||||
bullet.addComponent(new Velocity(Math.random() * 200 - 100, -300));
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Finding Entities
|
||||
|
||||
```typescript
|
||||
class SearchScene extends Scene {
|
||||
findEntities(): void {
|
||||
// Find by name
|
||||
const player = this.findEntity("Player");
|
||||
const player2 = this.getEntityByName("Player"); // Alias method
|
||||
|
||||
// Find by ID
|
||||
const entity = this.findEntityById(123);
|
||||
|
||||
// Find by tag
|
||||
const enemies = this.findEntitiesByTag(2);
|
||||
const enemies2 = this.getEntitiesByTag(2); // Alias method
|
||||
|
||||
if (player) {
|
||||
console.log(`Found player: ${player.name}`);
|
||||
}
|
||||
|
||||
console.log(`Found ${enemies.length} enemies`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Destroying Entities
|
||||
|
||||
```typescript
|
||||
class DestroyScene extends Scene {
|
||||
cleanupEntities(): void {
|
||||
// Destroy all entities
|
||||
this.destroyAllEntities();
|
||||
|
||||
// Single entity destruction through the entity itself
|
||||
const enemy = this.findEntity("Enemy_1");
|
||||
if (enemy) {
|
||||
enemy.destroy(); // Entity is automatically removed from the scene
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## System Management
|
||||
|
||||
### Adding and Removing Systems
|
||||
|
||||
```typescript
|
||||
class SystemScene extends Scene {
|
||||
protected initialize(): void {
|
||||
// Add systems
|
||||
const movementSystem = new MovementSystem();
|
||||
this.addSystem(movementSystem);
|
||||
|
||||
// Set system update order
|
||||
movementSystem.updateOrder = 1;
|
||||
|
||||
// Add more systems
|
||||
this.addSystem(new PhysicsSystem());
|
||||
this.addSystem(new RenderSystem());
|
||||
}
|
||||
|
||||
public removeUnnecessarySystems(): void {
|
||||
// Get system
|
||||
const physicsSystem = this.getEntityProcessor(PhysicsSystem);
|
||||
|
||||
// Remove system
|
||||
if (physicsSystem) {
|
||||
this.removeSystem(physicsSystem);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Event System
|
||||
|
||||
Scene has a built-in type-safe event system:
|
||||
|
||||
```typescript
|
||||
class EventScene extends Scene {
|
||||
protected initialize(): void {
|
||||
// Listen to events
|
||||
this.eventSystem.on('player_died', this.onPlayerDied.bind(this));
|
||||
this.eventSystem.on('enemy_spawned', this.onEnemySpawned.bind(this));
|
||||
this.eventSystem.on('level_complete', this.onLevelComplete.bind(this));
|
||||
}
|
||||
|
||||
private onPlayerDied(data: any): void {
|
||||
console.log('Player died event');
|
||||
// Handle player death
|
||||
}
|
||||
|
||||
private onEnemySpawned(data: any): void {
|
||||
console.log('Enemy spawned event');
|
||||
// Handle enemy spawn
|
||||
}
|
||||
|
||||
private onLevelComplete(data: any): void {
|
||||
console.log('Level complete event');
|
||||
// Handle level completion
|
||||
}
|
||||
|
||||
public triggerGameEvent(): void {
|
||||
// Send event (synchronous)
|
||||
this.eventSystem.emitSync('custom_event', {
|
||||
message: "This is a custom event",
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
// Send event (asynchronous)
|
||||
this.eventSystem.emit('async_event', {
|
||||
data: "Async event data"
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Scene Responsibility Separation
|
||||
|
||||
```typescript
|
||||
// Good scene design - clear responsibilities
|
||||
class MenuScene extends Scene {
|
||||
// Only handles menu-related logic
|
||||
}
|
||||
|
||||
class GameScene extends Scene {
|
||||
// Only handles gameplay logic
|
||||
}
|
||||
|
||||
class InventoryScene extends Scene {
|
||||
// Only handles inventory logic
|
||||
}
|
||||
|
||||
// Avoid this design - mixed responsibilities
|
||||
class MegaScene extends Scene {
|
||||
// Contains menu, game, inventory, and all other logic
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Proper System Organization
|
||||
|
||||
```typescript
|
||||
class OrganizedScene extends Scene {
|
||||
protected initialize(): void {
|
||||
// Add systems by function and dependencies
|
||||
this.addInputSystems();
|
||||
this.addLogicSystems();
|
||||
this.addRenderSystems();
|
||||
}
|
||||
|
||||
private addInputSystems(): void {
|
||||
this.addSystem(new InputSystem());
|
||||
}
|
||||
|
||||
private addLogicSystems(): void {
|
||||
this.addSystem(new MovementSystem());
|
||||
this.addSystem(new PhysicsSystem());
|
||||
this.addSystem(new CollisionSystem());
|
||||
}
|
||||
|
||||
private addRenderSystems(): void {
|
||||
this.addSystem(new RenderSystem());
|
||||
this.addSystem(new UISystem());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Resource Management
|
||||
|
||||
```typescript
|
||||
class ResourceScene extends Scene {
|
||||
private textures: Map<string, any> = new Map();
|
||||
private sounds: Map<string, any> = new Map();
|
||||
|
||||
protected initialize(): void {
|
||||
this.loadResources();
|
||||
}
|
||||
|
||||
private loadResources(): void {
|
||||
// Load resources needed by the scene
|
||||
this.textures.set('player', this.loadTexture('player.png'));
|
||||
this.sounds.set('bgm', this.loadSound('bgm.mp3'));
|
||||
}
|
||||
|
||||
public unload(): void {
|
||||
// Cleanup resources
|
||||
this.textures.clear();
|
||||
this.sounds.clear();
|
||||
console.log('Scene resources cleaned up');
|
||||
}
|
||||
|
||||
private loadTexture(path: string): any {
|
||||
// Load texture
|
||||
return null;
|
||||
}
|
||||
|
||||
private loadSound(path: string): any {
|
||||
// Load sound
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Learn about [SceneManager](./scene-manager) - Simple scene management for most games
|
||||
- Learn about [WorldManager](./world-manager) - For scenarios requiring multi-world isolation
|
||||
- Learn about [Persistent Entity](./persistent-entity) - Keep entities across scene transitions (v2.2.22+)
|
||||
|
||||
Scene is the core container of the ECS framework. Proper scene management makes your game architecture clearer, more modular, and easier to maintain.
|
||||
Reference in New Issue
Block a user