Files
esengine/docs/en/guide/getting-started.md
YHH 31fd34b221 fix: update Laya examples, add CLI docs, fix changesets workflow (#340)
* fix: update Laya examples, add CLI docs, fix changesets workflow

- Update Laya examples to use Laya 3.x Script pattern (@regClass)
- Add CLI tool quick start section to README (npx @esengine/cli init)
- Fix changesets workflow to only build framework packages
- Remove unnecessary Rust/WASM build steps from changesets workflow
- Remove redundant 'pnpm build' from changeset:publish script

* docs: add CLI documentation and update Laya examples

- Add CLI quick start section to getting-started.md (zh/en)
- Update Laya examples to use Laya 3.x Script pattern
2025-12-26 16:37:37 +08:00

11 KiB

Quick Start

This guide will help you get started with ECS Framework, from installation to creating your first ECS application.

Installation

The easiest way to add ECS to your existing project:

# In your project directory
npx @esengine/cli init

The CLI automatically detects your project type (Cocos Creator 2.x/3.x, LayaAir 3.x, or Node.js) and generates the necessary integration code, including:

  • ECSManager component/script - Manages ECS lifecycle
  • Example components and systems - Helps you get started quickly
  • Automatic dependency installation

Manual NPM Installation

If you prefer manual configuration:

# Using npm
npm install @esengine/ecs-framework

Initialize Core

Basic Initialization

The core of ECS Framework is the Core class, a singleton that manages the entire framework lifecycle.

import { Core } from '@esengine/ecs-framework'

// Method 1: Using config object (recommended)
const core = Core.create({
  debug: true,                    // Enable debug mode for detailed logs and performance monitoring
  debugConfig: {                 // Optional: Advanced debug configuration
    enabled: false,               // Whether to enable WebSocket debug server
    websocketUrl: 'ws://localhost:8080',
    debugFrameRate: 30,          // Debug data send frame rate
    channels: {
      entities: true,
      systems: true,
      performance: true,
      components: true,
      scenes: true
    }
  }
});

// Method 2: Simplified creation (backward compatible)
const core = Core.create(true);  // Equivalent to { debug: true }

// Method 3: Production environment configuration
const core = Core.create({
  debug: false                   // Disable debug in production
});

Core Configuration Details

interface ICoreConfig {
  /** Enable debug mode - affects log level and performance monitoring */
  debug?: boolean;

  /** Advanced debug configuration - for dev tools integration */
  debugConfig?: {
    enabled: boolean;                    // Enable debug server
    websocketUrl: string;               // WebSocket server URL
    autoReconnect?: boolean;            // Auto reconnect
    debugFrameRate?: 60 | 30 | 15;     // Debug data send frame rate
    channels: {                         // Data channel configuration
      entities: boolean;                // Entity data
      systems: boolean;                 // System data
      performance: boolean;             // Performance data
      components: boolean;              // Component data
      scenes: boolean;                  // Scene data
    };
  };
}

Core Instance Management

Core uses singleton pattern, accessible via static property after creation:

// Create instance
const core = Core.create(true);

// Get created instance
const instance = Core.Instance;  // Returns current instance, null if not created

Game Loop Integration

Important: Before creating entities and systems, you need to understand how to integrate ECS Framework into your game engine.

Core.update(deltaTime) is the framework heartbeat, must be called every frame. It handles:

  • Updating the built-in Time class
  • Updating all global managers (timers, object pools, etc.)
  • Updating all entity systems in all scenes
  • Processing entity creation and destruction
  • Collecting performance data (in debug mode)

See engine integration examples: Game Engine Integration

Create Your First ECS Application

1. Define Components

Components are pure data containers that store entity state:

import { Component, ECSComponent } from '@esengine/ecs-framework'

// Position component
@ECSComponent('Position')
class Position extends Component {
  x: number = 0
  y: number = 0

  constructor(x: number = 0, y: number = 0) {
    super()
    this.x = x
    this.y = y
  }
}

// Velocity component
@ECSComponent('Velocity')
class Velocity extends Component {
  dx: number = 0
  dy: number = 0

  constructor(dx: number = 0, dy: number = 0) {
    super()
    this.dx = dx
    this.dy = dy
  }
}

// Sprite component
@ECSComponent('Sprite')
class Sprite extends Component {
  texture: string = ''
  width: number = 32
  height: number = 32

  constructor(texture: string, width: number = 32, height: number = 32) {
    super()
    this.texture = texture
    this.width = width
    this.height = height
  }
}

2. Create Entity Systems

Systems contain game logic and process entities with specific components. ECS Framework provides Matcher-based entity filtering:

import { EntitySystem, Matcher, Time, ECSSystem } from '@esengine/ecs-framework'

// Movement system - handles position and velocity
@ECSSystem('MovementSystem')
class MovementSystem extends EntitySystem {

  constructor() {
    // Use Matcher to define target entities: must have both Position and Velocity
    super(Matcher.empty().all(Position, Velocity))
  }

  protected process(entities: readonly Entity[]): void {
    // process method receives all matching entities
    for (const entity of entities) {
      const position = entity.getComponent(Position)!
      const velocity = entity.getComponent(Velocity)!

      // Update position (using framework's Time class)
      position.x += velocity.dx * Time.deltaTime
      position.y += velocity.dy * Time.deltaTime

      // Boundary check example
      if (position.x < 0) position.x = 0
      if (position.y < 0) position.y = 0
    }
  }
}

// Render system - handles visible objects
@ECSSystem('RenderSystem')
class RenderSystem extends EntitySystem {

  constructor() {
    // Must have Position and Sprite, optional Velocity (for direction)
    super(Matcher.empty().all(Position, Sprite).any(Velocity))
  }

  protected process(entities: readonly Entity[]): void {
    for (const entity of entities) {
      const position = entity.getComponent(Position)!
      const sprite = entity.getComponent(Sprite)!
      const velocity = entity.getComponent(Velocity) // May be null

      // Flip sprite based on velocity direction (optional logic)
      let flipX = false
      if (velocity && velocity.dx < 0) {
        flipX = true
      }

      // Render logic (pseudocode here)
      this.drawSprite(sprite.texture, position.x, position.y, sprite.width, sprite.height, flipX)
    }
  }

  private drawSprite(texture: string, x: number, y: number, width: number, height: number, flipX: boolean = false) {
    // Actual render implementation depends on your game engine
    const direction = flipX ? '<-' : '->'
    console.log(`Render ${texture} at (${x.toFixed(1)}, ${y.toFixed(1)}) direction: ${direction}`)
  }
}

3. Create Scene

Recommended to extend Scene class for custom scenes:

import { Scene } from '@esengine/ecs-framework'

// Recommended: Extend Scene for custom scene
class GameScene extends Scene {

  initialize(): void {
    // Scene initialization logic
    this.name = "MainScene";

    // Add systems to scene
    this.addSystem(new MovementSystem());
    this.addSystem(new RenderSystem());
  }

  onStart(): void {
    // Logic when scene starts running
    console.log("Game scene started");
  }

  unload(): void {
    // Cleanup logic when scene unloads
    console.log("Game scene unloaded");
  }
}

// Create and set scene
const gameScene = new GameScene();
Core.setScene(gameScene);

4. Create Entities

// Create player entity
const player = gameScene.createEntity("Player");
player.addComponent(new Position(100, 100));
player.addComponent(new Velocity(50, 30));  // Move 50px/sec (x), 30px/sec (y)
player.addComponent(new Sprite("player.png", 64, 64));

Scene Management

Core has built-in scene management, very simple to use:

import { Core, Scene } from '@esengine/ecs-framework';

// Initialize Core
Core.create({ debug: true });

// Create and set scene
class GameScene extends Scene {
  initialize(): void {
    this.name = "GamePlay";
    this.addSystem(new MovementSystem());
    this.addSystem(new RenderSystem());
  }
}

const gameScene = new GameScene();
Core.setScene(gameScene);

// Game loop (auto-updates scene)
function gameLoop(deltaTime: number) {
  Core.update(deltaTime);  // Auto-updates global services and scene
}

// Switch scenes
Core.loadScene(new MenuScene());  // Delayed switch (next frame)
Core.setScene(new GameScene());   // Immediate switch

// Access current scene
const currentScene = Core.scene;

// Using fluent API
const player = Core.ecsAPI?.createEntity('Player')
  .addComponent(Position, 100, 100)
  .addComponent(Velocity, 50, 0);

Advanced: Using WorldManager for Multi-World

Only for complex server-side applications (MMO game servers, game room systems, etc.):

import { Core, WorldManager } from '@esengine/ecs-framework';

// Initialize Core
Core.create({ debug: true });

// Get WorldManager from service container (Core auto-creates and registers it)
const worldManager = Core.services.resolve(WorldManager);

// Create multiple independent game worlds
const room1 = worldManager.createWorld('room_001');
const room2 = worldManager.createWorld('room_002');

// Create scenes in each world
const gameScene1 = room1.createScene('game', new GameScene());
const gameScene2 = room2.createScene('game', new GameScene());

// Activate scenes
room1.setSceneActive('game', true);
room2.setSceneActive('game', true);

// Game loop (need to manually update worlds)
function gameLoop(deltaTime: number) {
  Core.update(deltaTime);       // Update global services
  worldManager.updateAll();     // Manually update all worlds
}

Game Engine Integration

Laya 3.x Engine Integration

Using Laya.Script component to manage ECS lifecycle is recommended:

import { Core, Scene } from '@esengine/ecs-framework';

const { regClass } = Laya;

@regClass()
export class ECSManager extends Laya.Script {
  private ecsScene = new GameScene();

  onAwake(): void {
    // Initialize ECS
    Core.create({ debug: true });
    Core.setScene(this.ecsScene);
  }

  onUpdate(): void {
    // Auto-updates global services and scene
    Core.update(Laya.timer.delta / 1000);
  }

  onDestroy(): void {
    // Cleanup resources
    Core.destroy();
  }
}

In Laya IDE, attach the ECSManager script to a node in your scene.

Cocos Creator Integration

import { Component, _decorator } from 'cc';
import { Core } from '@esengine/ecs-framework';

const { ccclass } = _decorator;

@ccclass('ECSGameManager')
export class ECSGameManager extends Component {
  onLoad() {
    // Initialize ECS
    Core.create(true);
    Core.setScene(new GameScene());
  }

  update(deltaTime: number) {
    // Auto-updates global services and scene
    Core.update(deltaTime);
  }

  onDestroy() {
    // Cleanup resources
    Core.destroy();
  }
}

Next Steps

You've successfully created your first ECS application! Next you can:

FAQ

Why isn't my system executing?

Ensure:

  1. System is added to scene: this.addSystem(system) (in Scene's initialize method)
  2. Scene is set: Core.setScene(scene)
  3. Game loop is calling: Core.update(deltaTime)

How to debug ECS applications?

Enable debug mode:

Core.create({ debug: true })

// Get debug data
const debugData = Core.getDebugData()
console.log(debugData)