Compare commits

..

12 Commits

Author SHA1 Message Date
github-actions[bot]
c7f8208b6f chore: release packages (#358)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-27 10:31:43 +08:00
yhh
5131ec3c52 chore: update pnpm-lock.yaml 2025-12-27 10:28:35 +08:00
yhh
7d74623710 fix(core): 配置 publishConfig.directory 确保从 dist 目录发布 2025-12-27 10:27:00 +08:00
github-actions[bot]
044463dd5f chore: release packages (#357)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-27 09:55:30 +08:00
YHH
ce2db4e48a fix(core): 修复 World cleanup 在打包环境下的兼容性问题 (#356)
- 使用 forEach 替代 spread + for...of 解构模式,避免某些打包工具转换后的兼容性问题
- 重构 World 和 WorldManager 类,提升代码质量
- 提取默认配置为常量
- 统一双语注释格式
2025-12-27 09:51:04 +08:00
github-actions[bot]
0a88c6f2fc chore: release packages (#355)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-27 00:20:04 +08:00
yhh
b0b95c60b4 fix: 从 ignore 列表移除 network-server 以支持版本发布 2025-12-27 00:17:44 +08:00
yhh
683ac7a7d4 Merge branch 'master' of https://github.com/esengine/esengine 2025-12-27 00:16:12 +08:00
YHH
1e240e86f2 feat(cli): 增强 Node.js 服务端适配器 (#354)
* docs(network): 添加网络模块文档和 CLI 支持

- 添加中英文网络模块文档
- 将 network、network-protocols、network-server 加入 CLI 模块列表

* feat(cli): 增强 Node.js 服务端适配器

- 添加 @esengine/network-server 依赖支持
- 生成完整的 ECS 游戏服务器项目结构
- 修复 network-server 包支持 ESM/CJS 双格式
- 添加 ws@8.18.0 解决 Node.js 24 兼容性问题
- 组件使用 @ECSComponent 装饰器注册
- tsconfig 启用 experimentalDecorators
2025-12-27 00:13:58 +08:00
yhh
4d6c2fe7ff Merge branch 'master' of https://github.com/esengine/esengine 2025-12-26 23:21:17 +08:00
yhh
a42f2412d7 Merge branch 'master' of https://github.com/esengine/esengine 2025-12-26 23:04:47 +08:00
yhh
fdb19a33fb docs(network): 添加网络模块文档和 CLI 支持
- 添加中英文网络模块文档
- 将 network、network-protocols、network-server 加入 CLI 模块列表
2025-12-26 23:01:07 +08:00
30 changed files with 1247 additions and 852 deletions

View File

@@ -33,7 +33,6 @@
"@esengine/physics-rapier2d",
"@esengine/rapier2d",
"@esengine/world-streaming",
"@esengine/network-server",
"@esengine/editor-core",
"@esengine/editor-runtime",
"@esengine/editor-app",

View File

@@ -22,44 +22,112 @@ npm install @esengine/network
npm install @esengine/network-server
```
## Quick Setup with CLI
We recommend using ESEngine CLI to quickly create a complete game server project:
```bash
# Create project directory
mkdir my-game-server && cd my-game-server
npm init -y
# Initialize Node.js server with CLI
npx @esengine/cli init -p nodejs
```
The CLI will generate the following project structure:
```
my-game-server/
├── src/
│ ├── index.ts # Entry point
│ ├── server/
│ │ └── GameServer.ts # Network server configuration
│ └── game/
│ ├── Game.ts # ECS game class
│ ├── scenes/
│ │ └── MainScene.ts # Main scene
│ ├── components/ # ECS components
│ │ ├── PositionComponent.ts
│ │ └── VelocityComponent.ts
│ └── systems/ # ECS systems
│ └── MovementSystem.ts
├── tsconfig.json
├── package.json
└── README.md
```
Start the server:
```bash
# Development mode (hot reload)
npm run dev
# Production mode
npm run start
```
## Quick Start
### Client
```typescript
import { World } from '@esengine/ecs-framework';
import { Core, Scene } from '@esengine/ecs-framework';
import {
NetworkPlugin,
NetworkIdentity,
NetworkTransform
} from '@esengine/network';
// Create World and install network plugin
const world = new World();
const networkPlugin = new NetworkPlugin({
serverUrl: 'ws://localhost:3000'
});
networkPlugin.install(world.services);
// Define game scene
class GameScene extends Scene {
initialize(): void {
this.name = 'Game';
// Network systems are automatically added by NetworkPlugin
}
}
// Initialize Core
Core.create({ debug: false });
const scene = new GameScene();
Core.setScene(scene);
// Install network plugin
const networkPlugin = new NetworkPlugin();
await Core.installPlugin(networkPlugin);
// Register prefab factory
networkPlugin.registerPrefab('player', (netId, ownerId) => {
const entity = world.createEntity(`player_${netId}`);
entity.addComponent(new NetworkIdentity(netId, ownerId));
networkPlugin.registerPrefab('player', (scene, spawn) => {
const entity = scene.createEntity(`player_${spawn.netId}`);
const identity = entity.addComponent(new NetworkIdentity());
identity.netId = spawn.netId;
identity.ownerId = spawn.ownerId;
identity.isLocalPlayer = spawn.ownerId === networkPlugin.networkService.localClientId;
entity.addComponent(new NetworkTransform());
// Add other components...
return entity;
});
// Connect to server
await networkPlugin.connect('PlayerName');
console.log('Connected! Client ID:', networkPlugin.localPlayerId);
const success = await networkPlugin.connect('ws://localhost:3000', 'PlayerName');
if (success) {
console.log('Connected!');
}
// Game loop
function gameLoop(dt: number) {
Core.update(dt);
}
// Disconnect
networkPlugin.disconnect();
await networkPlugin.disconnect();
```
### Server
After creating a server project with CLI, the generated code already configures GameServer:
```typescript
import { GameServer } from '@esengine/network-server';
@@ -72,6 +140,7 @@ const server = new GameServer({
});
await server.start();
console.log('Server started on ws://localhost:3000');
```
## Core Concepts
@@ -229,15 +298,19 @@ interface INetworkCallbacks {
### Prefab Factory
```typescript
type PrefabFactory = (netId: number, ownerId: number) => Entity;
type PrefabFactory = (scene: Scene, spawn: MsgSpawn) => Entity;
```
Register prefab factories for network entity creation:
```typescript
networkPlugin.registerPrefab('enemy', (netId, ownerId) => {
const entity = world.createEntity(`enemy_${netId}`);
entity.addComponent(new NetworkIdentity(netId, ownerId));
networkPlugin.registerPrefab('enemy', (scene, spawn) => {
const entity = scene.createEntity(`enemy_${spawn.netId}`);
const identity = entity.addComponent(new NetworkIdentity());
identity.netId = spawn.netId;
identity.ownerId = spawn.ownerId;
entity.addComponent(new NetworkTransform());
entity.addComponent(new EnemyComponent());
return entity;
@@ -264,9 +337,12 @@ class NetworkInputSystem extends EntitySystem {
Usage example:
```typescript
const inputSystem = world.getSystem(NetworkInputSystem);
// Send input via NetworkPlugin (recommended)
networkPlugin.sendMoveInput(0, 1); // Movement
networkPlugin.sendActionInput('jump'); // Action
// Handle keyboard input
// Or use inputSystem directly
const inputSystem = networkPlugin.inputSystem;
if (keyboard.isPressed('W')) {
inputSystem.addMoveInput(0, 1);
}
@@ -519,74 +595,92 @@ const networkService = services.get(NetworkServiceToken);
### Complete Multiplayer Client
```typescript
import { World, EntitySystem, Matcher } from '@esengine/ecs-framework';
import { Core, Scene, EntitySystem, Matcher, Entity } from '@esengine/ecs-framework';
import {
NetworkPlugin,
NetworkIdentity,
NetworkTransform,
NetworkInputSystem
NetworkTransform
} from '@esengine/network';
// Create game world
const world = new World();
// Define game scene
class GameScene extends Scene {
initialize(): void {
this.name = 'MultiplayerGame';
// Network systems are automatically added by NetworkPlugin
// Add custom systems
this.addSystem(new LocalInputHandler());
}
}
// Configure network plugin
const networkPlugin = new NetworkPlugin({
serverUrl: 'ws://localhost:3000'
});
networkPlugin.install(world.services);
// Initialize
async function initGame() {
Core.create({ debug: false });
// Register player prefab
networkPlugin.registerPrefab('player', (netId, ownerId) => {
const entity = world.createEntity(`player_${netId}`);
const scene = new GameScene();
Core.setScene(scene);
const identity = new NetworkIdentity(netId, ownerId);
entity.addComponent(identity);
entity.addComponent(new NetworkTransform());
// Install network plugin
const networkPlugin = new NetworkPlugin();
await Core.installPlugin(networkPlugin);
// If local player, add input component
if (identity.bIsLocalPlayer) {
entity.addComponent(new LocalInputComponent());
// Register player prefab
networkPlugin.registerPrefab('player', (scene, spawn) => {
const entity = scene.createEntity(`player_${spawn.netId}`);
const identity = entity.addComponent(new NetworkIdentity());
identity.netId = spawn.netId;
identity.ownerId = spawn.ownerId;
identity.isLocalPlayer = spawn.ownerId === networkPlugin.networkService.localClientId;
entity.addComponent(new NetworkTransform());
// If local player, add input marker
if (identity.isLocalPlayer) {
entity.addComponent(new LocalInputComponent());
}
return entity;
});
// Connect to server
const success = await networkPlugin.connect('ws://localhost:3000', 'Player1');
if (success) {
console.log('Connected!');
} else {
console.error('Connection failed');
}
return entity;
});
// Connect to server
async function startGame() {
try {
await networkPlugin.connect('Player1');
console.log('Connected! Player ID:', networkPlugin.localPlayerId);
} catch (error) {
console.error('Connection failed:', error);
}
return networkPlugin;
}
// Game loop
function gameLoop(deltaTime: number) {
world.update(deltaTime);
Core.update(deltaTime);
}
startGame();
initGame();
```
### Handling Input
```typescript
class LocalInputHandler extends EntitySystem {
private _inputSystem: NetworkInputSystem;
private _networkPlugin: NetworkPlugin | null = null;
constructor() {
super(Matcher.all(NetworkIdentity, LocalInputComponent));
super(Matcher.empty().all(NetworkIdentity, LocalInputComponent));
}
protected onAddedToWorld(): void {
this._inputSystem = this.world.getSystem(NetworkInputSystem);
protected onAddedToScene(): void {
// Get NetworkPlugin reference
this._networkPlugin = Core.getPlugin(NetworkPlugin);
}
protected processEntity(entity: Entity, dt: number): void {
const identity = entity.getComponent(NetworkIdentity);
if (!identity.bIsLocalPlayer) return;
if (!this._networkPlugin) return;
const identity = entity.getComponent(NetworkIdentity)!;
if (!identity.isLocalPlayer) return;
// Read keyboard input
let moveX = 0;
@@ -598,11 +692,11 @@ class LocalInputHandler extends EntitySystem {
if (keyboard.isPressed('S')) moveY -= 1;
if (moveX !== 0 || moveY !== 0) {
this._inputSystem.addMoveInput(moveX, moveY);
this._networkPlugin.sendMoveInput(moveX, moveY);
}
if (keyboard.isJustPressed('Space')) {
this._inputSystem.addActionInput('jump');
this._networkPlugin.sendActionInput('jump');
}
}
}

View File

@@ -22,44 +22,112 @@ npm install @esengine/network
npm install @esengine/network-server
```
## 使用 CLI 快速创建服务端
推荐使用 ESEngine CLI 快速创建完整的游戏服务端项目:
```bash
# 创建项目目录
mkdir my-game-server && cd my-game-server
npm init -y
# 使用 CLI 初始化 Node.js 服务端
npx @esengine/cli init -p nodejs
```
CLI 会自动生成以下项目结构:
```
my-game-server/
├── src/
│ ├── index.ts # 入口文件
│ ├── server/
│ │ └── GameServer.ts # 网络服务器配置
│ └── game/
│ ├── Game.ts # ECS 游戏主类
│ ├── scenes/
│ │ └── MainScene.ts # 主场景
│ ├── components/ # ECS 组件
│ │ ├── PositionComponent.ts
│ │ └── VelocityComponent.ts
│ └── systems/ # ECS 系统
│ └── MovementSystem.ts
├── tsconfig.json
├── package.json
└── README.md
```
启动服务端:
```bash
# 开发模式(热重载)
npm run dev
# 生产模式
npm run start
```
## 快速开始
### 客户端
```typescript
import { World } from '@esengine/ecs-framework';
import { Core, Scene } from '@esengine/ecs-framework';
import {
NetworkPlugin,
NetworkIdentity,
NetworkTransform
} from '@esengine/network';
// 创建 World 并安装网络插件
const world = new World();
const networkPlugin = new NetworkPlugin({
serverUrl: 'ws://localhost:3000'
});
networkPlugin.install(world.services);
// 定义游戏场景
class GameScene extends Scene {
initialize(): void {
this.name = 'Game';
// 网络系统由 NetworkPlugin 自动添加
}
}
// 初始化 Core
Core.create({ debug: false });
const scene = new GameScene();
Core.setScene(scene);
// 安装网络插件
const networkPlugin = new NetworkPlugin();
await Core.installPlugin(networkPlugin);
// 注册预制体工厂
networkPlugin.registerPrefab('player', (netId, ownerId) => {
const entity = world.createEntity(`player_${netId}`);
entity.addComponent(new NetworkIdentity(netId, ownerId));
networkPlugin.registerPrefab('player', (scene, spawn) => {
const entity = scene.createEntity(`player_${spawn.netId}`);
const identity = entity.addComponent(new NetworkIdentity());
identity.netId = spawn.netId;
identity.ownerId = spawn.ownerId;
identity.isLocalPlayer = spawn.ownerId === networkPlugin.networkService.localClientId;
entity.addComponent(new NetworkTransform());
// 添加其他组件...
return entity;
});
// 连接服务器
await networkPlugin.connect('PlayerName');
console.log('Connected! Client ID:', networkPlugin.localPlayerId);
const success = await networkPlugin.connect('ws://localhost:3000', 'PlayerName');
if (success) {
console.log('Connected!');
}
// 游戏循环
function gameLoop(dt: number) {
Core.update(dt);
}
// 断开连接
networkPlugin.disconnect();
await networkPlugin.disconnect();
```
### 服务器端
使用 CLI 创建服务端项目后,默认生成的代码已经配置好了 GameServer
```typescript
import { GameServer } from '@esengine/network-server';
@@ -72,6 +140,7 @@ const server = new GameServer({
});
await server.start();
console.log('Server started on ws://localhost:3000');
```
## 核心概念
@@ -229,15 +298,19 @@ interface INetworkCallbacks {
### 预制体工厂
```typescript
type PrefabFactory = (netId: number, ownerId: number) => Entity;
type PrefabFactory = (scene: Scene, spawn: MsgSpawn) => Entity;
```
注册预制体工厂用于网络实体的创建:
```typescript
networkPlugin.registerPrefab('enemy', (netId, ownerId) => {
const entity = world.createEntity(`enemy_${netId}`);
entity.addComponent(new NetworkIdentity(netId, ownerId));
networkPlugin.registerPrefab('enemy', (scene, spawn) => {
const entity = scene.createEntity(`enemy_${spawn.netId}`);
const identity = entity.addComponent(new NetworkIdentity());
identity.netId = spawn.netId;
identity.ownerId = spawn.ownerId;
entity.addComponent(new NetworkTransform());
entity.addComponent(new EnemyComponent());
return entity;
@@ -264,9 +337,12 @@ class NetworkInputSystem extends EntitySystem {
使用示例:
```typescript
const inputSystem = world.getSystem(NetworkInputSystem);
// 通过 NetworkPlugin 发送输入(推荐)
networkPlugin.sendMoveInput(0, 1); // 移动
networkPlugin.sendActionInput('jump'); // 动作
// 处理键盘输入
// 或直接使用 inputSystem
const inputSystem = networkPlugin.inputSystem;
if (keyboard.isPressed('W')) {
inputSystem.addMoveInput(0, 1);
}
@@ -519,74 +595,92 @@ const networkService = services.get(NetworkServiceToken);
### 完整的多人游戏客户端
```typescript
import { World, EntitySystem, Matcher } from '@esengine/ecs-framework';
import { Core, Scene, EntitySystem, Matcher, Entity } from '@esengine/ecs-framework';
import {
NetworkPlugin,
NetworkIdentity,
NetworkTransform,
NetworkInputSystem
NetworkTransform
} from '@esengine/network';
// 创建游戏世界
const world = new World();
// 定义游戏场景
class GameScene extends Scene {
initialize(): void {
this.name = 'MultiplayerGame';
// 网络系统由 NetworkPlugin 自动添加
// 添加自定义系统
this.addSystem(new LocalInputHandler());
}
}
// 配置网络插件
const networkPlugin = new NetworkPlugin({
serverUrl: 'ws://localhost:3000'
});
networkPlugin.install(world.services);
// 初始化
async function initGame() {
Core.create({ debug: false });
// 注册玩家预制体
networkPlugin.registerPrefab('player', (netId, ownerId) => {
const entity = world.createEntity(`player_${netId}`);
const scene = new GameScene();
Core.setScene(scene);
const identity = new NetworkIdentity(netId, ownerId);
entity.addComponent(identity);
entity.addComponent(new NetworkTransform());
// 安装网络插件
const networkPlugin = new NetworkPlugin();
await Core.installPlugin(networkPlugin);
// 如果是本地玩家,添加输入组件
if (identity.bIsLocalPlayer) {
entity.addComponent(new LocalInputComponent());
// 注册玩家预制体
networkPlugin.registerPrefab('player', (scene, spawn) => {
const entity = scene.createEntity(`player_${spawn.netId}`);
const identity = entity.addComponent(new NetworkIdentity());
identity.netId = spawn.netId;
identity.ownerId = spawn.ownerId;
identity.isLocalPlayer = spawn.ownerId === networkPlugin.networkService.localClientId;
entity.addComponent(new NetworkTransform());
// 如果是本地玩家,添加输入标记
if (identity.isLocalPlayer) {
entity.addComponent(new LocalInputComponent());
}
return entity;
});
// 连接服务器
const success = await networkPlugin.connect('ws://localhost:3000', 'Player1');
if (success) {
console.log('已连接!');
} else {
console.error('连接失败');
}
return entity;
});
// 连接服务器
async function startGame() {
try {
await networkPlugin.connect('Player1');
console.log('已连接! 玩家 ID:', networkPlugin.localPlayerId);
} catch (error) {
console.error('连接失败:', error);
}
return networkPlugin;
}
// 游戏循环
function gameLoop(deltaTime: number) {
world.update(deltaTime);
Core.update(deltaTime);
}
startGame();
initGame();
```
### 处理输入
```typescript
class LocalInputHandler extends EntitySystem {
private _inputSystem: NetworkInputSystem;
private _networkPlugin: NetworkPlugin | null = null;
constructor() {
super(Matcher.all(NetworkIdentity, LocalInputComponent));
super(Matcher.empty().all(NetworkIdentity, LocalInputComponent));
}
protected onAddedToWorld(): void {
this._inputSystem = this.world.getSystem(NetworkInputSystem);
protected onAddedToScene(): void {
// 获取 NetworkPlugin 引用
this._networkPlugin = Core.getPlugin(NetworkPlugin);
}
protected processEntity(entity: Entity, dt: number): void {
const identity = entity.getComponent(NetworkIdentity);
if (!identity.bIsLocalPlayer) return;
if (!this._networkPlugin) return;
const identity = entity.getComponent(NetworkIdentity)!;
if (!identity.isLocalPlayer) return;
// 读取键盘输入
let moveX = 0;
@@ -598,11 +692,11 @@ class LocalInputHandler extends EntitySystem {
if (keyboard.isPressed('S')) moveY -= 1;
if (moveX !== 0 || moveY !== 0) {
this._inputSystem.addMoveInput(moveX, moveY);
this._networkPlugin.sendMoveInput(moveX, moveY);
}
if (keyboard.isJustPressed('Space')) {
this._inputSystem.addActionInput('jump');
this._networkPlugin.sendActionInput('jump');
}
}
}

View File

@@ -0,0 +1,15 @@
# @esengine/behavior-tree
## 1.0.3
### Patch Changes
- Updated dependencies [[`7d74623`](https://github.com/esengine/esengine/commit/7d746237100084ac3456f1af92ff664db4e50cc8)]:
- @esengine/ecs-framework@2.4.4
## 1.0.2
### Patch Changes
- Updated dependencies [[`ce2db4e`](https://github.com/esengine/esengine/commit/ce2db4e48a7cdac44265420ef16e83f6424f4dea)]:
- @esengine/ecs-framework@2.4.3

View File

@@ -1,6 +1,6 @@
{
"name": "@esengine/behavior-tree",
"version": "1.0.1",
"version": "1.0.3",
"description": "ECS-based AI behavior tree system - works with any ECS framework (ESEngine, Cocos, Laya, etc.)",
"main": "dist/index.js",
"module": "dist/index.js",

View File

@@ -0,0 +1,15 @@
# @esengine/blueprint
## 1.0.2
### Patch Changes
- Updated dependencies [[`7d74623`](https://github.com/esengine/esengine/commit/7d746237100084ac3456f1af92ff664db4e50cc8)]:
- @esengine/ecs-framework@2.4.4
## 1.0.1
### Patch Changes
- Updated dependencies [[`ce2db4e`](https://github.com/esengine/esengine/commit/ce2db4e48a7cdac44265420ef16e83f6424f4dea)]:
- @esengine/ecs-framework@2.4.3

View File

@@ -1,6 +1,6 @@
{
"name": "@esengine/blueprint",
"version": "1.0.0",
"version": "1.0.2",
"description": "Visual scripting system - works with any ECS framework (ESEngine, Cocos, Laya, etc.)",
"main": "dist/index.js",
"module": "dist/index.js",

View File

@@ -0,0 +1,16 @@
# @esengine/ecs-framework
## 2.4.4
### Patch Changes
- [`7d74623`](https://github.com/esengine/esengine/commit/7d746237100084ac3456f1af92ff664db4e50cc8) Thanks [@esengine](https://github.com/esengine)! - fix(core): 修复 npm 发布目录配置,确保从 dist 目录发布以保持与 Cocos Creator 的兼容性
## 2.4.3
### Patch Changes
- [#356](https://github.com/esengine/esengine/pull/356) [`ce2db4e`](https://github.com/esengine/esengine/commit/ce2db4e48a7cdac44265420ef16e83f6424f4dea) Thanks [@esengine](https://github.com/esengine)! - fix(core): 修复 World cleanup 在打包环境下的兼容性问题
- 使用 forEach 替代 spread + for...of 解构模式,避免某些打包工具(如 Cocos Creator转换后的兼容性问题
- 重构 World 和 WorldManager 类,提升代码质量
- 提取默认配置为常量,统一双语注释格式

View File

@@ -1,6 +1,6 @@
{
"name": "@esengine/ecs-framework",
"version": "2.4.2",
"version": "2.4.4",
"description": "用于Laya、Cocos Creator等JavaScript游戏引擎的高性能ECS框架",
"main": "dist/index.cjs",
"module": "dist/index.mjs",
@@ -70,7 +70,8 @@
},
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
"registry": "https://registry.npmjs.org/",
"directory": "dist"
},
"repository": {
"type": "git",

View File

@@ -13,7 +13,7 @@ const logger = createLogger('World');
* @zh 全局系统是在World级别运行的系统不依赖特定Scene
* @en Global systems run at World level and don't depend on specific Scene
*/
export type IGlobalSystem = {
export interface IGlobalSystem {
/**
* @zh 系统名称
* @en System name
@@ -49,7 +49,7 @@ export type IGlobalSystem = {
* @zh World配置接口
* @en World configuration interface
*/
export type IWorldConfig = {
export interface IWorldConfig {
/**
* @zh World名称
* @en World name
@@ -77,12 +77,23 @@ export type IWorldConfig = {
/**
* @zh 自动清理阈值毫秒空Scene超过此时间后将被自动清理
* @en Auto cleanup threshold (ms), empty scenes exceeding this time will be auto-cleaned
*
* @default 300000 (5 minutes)
*/
cleanupThresholdMs?: number;
}
/**
* @zh World默认配置
* @en World default configuration
*/
const DEFAULT_CONFIG: Required<IWorldConfig> = {
name: 'World',
debug: false,
maxScenes: 10,
autoCleanup: true,
cleanupThresholdMs: 5 * 60 * 1000
};
/**
* @zh World类 - ECS世界管理器
* @en World class - ECS world manager
@@ -101,67 +112,66 @@ export type IWorldConfig = {
* - World.services: World-level services (independent per World)
* - Scene.services: Scene-level services (independent per Scene)
*
* @zh 这种设计允许创建独立的游戏世界,如:
* - 游戏房间每个房间一个World
* - 不同的游戏模式
* - 独立的模拟环境
* @en This design allows creating independent game worlds like:
* - Game rooms (one World per room)
* - Different game modes
* - Independent simulation environments
*
* @example
* ```typescript
* // @zh 创建游戏房间的World | @en Create World for game room
* const roomWorld = new World({ name: 'Room_001' });
*
* // @zh 注册World级别的服务 | @en Register World-level service
* roomWorld.services.registerSingleton(RoomManager);
*
* // @zh 在World中创建Scene | @en Create Scene in World
* const gameScene = roomWorld.createScene('game', new Scene());
* const uiScene = roomWorld.createScene('ui', new Scene());
*
* // @zh 在Scene中使用World级别的服务 | @en Use World-level service in Scene
* const roomManager = roomWorld.services.resolve(RoomManager);
*
* // @zh 更新整个World | @en Update entire World
* roomWorld.update(deltaTime);
* const gameScene = roomWorld.createScene('game');
* roomWorld.setSceneActive('game', true);
* roomWorld.start();
* ```
*/
export class World {
public readonly name: string;
private readonly _config: IWorldConfig;
private readonly _scenes: Map<string, IScene> = new Map();
private readonly _activeScenes: Set<string> = new Set();
private readonly _config: Required<IWorldConfig>;
private readonly _scenes = new Map<string, IScene>();
private readonly _activeScenes = new Set<string>();
private readonly _globalSystems: IGlobalSystem[] = [];
private readonly _services: ServiceContainer;
private _isActive: boolean = false;
private _createdAt: number;
private readonly _createdAt: number;
private _isActive = false;
constructor(config: IWorldConfig = {}) {
this._config = {
name: 'World',
debug: false,
maxScenes: 10,
autoCleanup: true,
cleanupThresholdMs: 5 * 60 * 1000,
...config
};
this.name = this._config.name!;
this._config = { ...DEFAULT_CONFIG, ...config };
this.name = this._config.name;
this._createdAt = Date.now();
this._services = new ServiceContainer();
}
/**
* @zh World级别的服务容器用于管理World范围内的全局服务
* @en World-level service container for managing World-scoped global services
* @zh World级别的服务容器
* @en World-level service container
*/
public get services(): ServiceContainer {
return this._services;
}
/**
* @zh 检查World是否激活
* @en Check if World is active
*/
public get isActive(): boolean {
return this._isActive;
}
/**
* @zh 获取Scene数量
* @en Get scene count
*/
public get sceneCount(): number {
return this._scenes.size;
}
/**
* @zh 获取创建时间
* @en Get creation time
*/
public get createdAt(): number {
return this._createdAt;
}
/**
* @zh 创建并添加Scene到World
* @en Create and add Scene to World
@@ -169,32 +179,21 @@ export class World {
* @param sceneName - @zh Scene名称 @en Scene name
* @param sceneInstance - @zh Scene实例可选@en Scene instance (optional)
* @returns @zh 创建的Scene实例 @en Created Scene instance
* @throws @zh 名称为空、重复或超出限制时抛出错误 @en Throws if name is empty, duplicate, or limit exceeded
*/
public createScene<T extends IScene>(sceneName: string, sceneInstance?: T): T {
if (!sceneName || typeof sceneName !== 'string' || sceneName.trim() === '') {
throw new Error('Scene name不能为空');
}
this.validateSceneName(sceneName);
if (this._scenes.has(sceneName)) {
throw new Error(`Scene name '${sceneName}' 已存在于World '${this.name}' 中`);
}
if (this._scenes.size >= this._config.maxScenes!) {
throw new Error(`World '${this.name}' 已达到最大Scene数量限制: ${this._config.maxScenes}`);
}
const scene = sceneInstance || (new Scene() as unknown as T);
const scene = sceneInstance ?? new Scene() as unknown as T;
if (this._config.debug) {
const performanceMonitor = new PerformanceMonitor();
performanceMonitor.enable();
scene.services.registerInstance(PerformanceMonitor, performanceMonitor);
const monitor = new PerformanceMonitor();
monitor.enable();
scene.services.registerInstance(PerformanceMonitor, monitor);
}
(scene as { id?: string }).id = sceneName;
if (!scene.name) {
scene.name = sceneName;
}
scene.name ||= sceneName;
this._scenes.set(sceneName, scene);
scene.initialize();
@@ -211,9 +210,7 @@ export class World {
*/
public removeScene(sceneName: string): boolean {
const scene = this._scenes.get(sceneName);
if (!scene) {
return false;
}
if (!scene) return false;
if (this._activeScenes.has(sceneName)) {
this.setSceneActive(sceneName, false);
@@ -221,11 +218,20 @@ export class World {
scene.end();
this._scenes.delete(sceneName);
logger.info(`从World '${this.name}' 中移除Scene: ${sceneName}`);
return true;
}
/**
* @zh 移除所有Scene
* @en Remove all Scenes
*/
public removeAllScenes(): void {
this._scenes.forEach((_, name) => this.removeScene(name));
logger.info(`从World '${this.name}' 中移除所有Scene`);
}
/**
* @zh 获取Scene
* @en Get Scene
@@ -234,36 +240,31 @@ export class World {
* @returns @zh Scene实例或null @en Scene instance or null
*/
public getScene<T extends IScene>(sceneName: string): T | null {
return this._scenes.get(sceneName) as T || null;
return (this._scenes.get(sceneName) as T) ?? null;
}
/**
* 获取所有Scene ID
* @zh 获取所有Scene ID
* @en Get all Scene IDs
*/
public getSceneIds(): string[] {
return Array.from(this._scenes.keys());
}
/**
* 获取所有Scene
* @zh 获取所有Scene
* @en Get all Scenes
*/
public getAllScenes(): IScene[] {
return Array.from(this._scenes.values());
}
/**
* 移除所有Scene
*/
public removeAllScenes(): void {
const sceneNames = Array.from(this._scenes.keys());
for (const sceneName of sceneNames) {
this.removeScene(sceneName);
}
logger.info(`从World '${this.name}' 中移除所有Scene`);
}
/**
* 设置Scene激活状态
* @zh 设置Scene激活状态
* @en Set Scene active state
*
* @param sceneName - @zh Scene名称 @en Scene name
* @param active - @zh 是否激活 @en Whether to activate
*/
public setSceneActive(sceneName: string, active: boolean): void {
const scene = this._scenes.get(sceneName);
@@ -283,22 +284,27 @@ export class World {
}
/**
* 检查Scene是否激活
* @zh 检查Scene是否激活
* @en Check if Scene is active
*/
public isSceneActive(sceneName: string): boolean {
return this._activeScenes.has(sceneName);
}
/**
* 获取活跃Scene数量
* @zh 获取活跃Scene数量
* @en Get active Scene count
*/
public getActiveSceneCount(): number {
return this._activeScenes.size;
}
/**
* 添加全局System
* 全局System会在所有激活Scene之前更新
* @zh 添加全局System
* @en Add global System
*
* @param system - @zh 全局System实例 @en Global System instance
* @returns @zh 添加的System实例 @en Added System instance
*/
public addGlobalSystem<T extends IGlobalSystem>(system: T): T {
if (this._globalSystems.includes(system)) {
@@ -307,132 +313,77 @@ export class World {
this._globalSystems.push(system);
system.initialize?.();
logger.debug(`在World '${this.name}' 中添加全局System: ${system.name}`);
return system;
}
/**
* 移除全局System
* @zh 移除全局System
* @en Remove global System
*
* @param system - @zh 要移除的System @en System to remove
* @returns @zh 是否成功移除 @en Whether removal was successful
*/
public removeGlobalSystem(system: IGlobalSystem): boolean {
const index = this._globalSystems.indexOf(system);
if (index === -1) {
return false;
}
if (index === -1) return false;
this._globalSystems.splice(index, 1);
system.reset?.();
logger.debug(`从World '${this.name}' 中移除全局System: ${system.name}`);
return true;
}
/**
* 获取全局System
* @zh 获取全局System
* @en Get global System
*
* @param type - @zh System类型 @en System type
* @returns @zh System实例或null @en System instance or null
*/
public getGlobalSystem<T extends IGlobalSystem>(type: new (...args: any[]) => T): T | null {
for (const system of this._globalSystems) {
if (system instanceof type) {
return system as T;
}
}
return null;
public getGlobalSystem<T extends IGlobalSystem>(type: new (...args: unknown[]) => T): T | null {
return (this._globalSystems.find(s => s instanceof type) as T) ?? null;
}
/**
* 启动World
* @zh 启动World
* @en Start World
*/
public start(): void {
if (this._isActive) {
return;
}
if (this._isActive) return;
this._isActive = true;
for (const system of this._globalSystems) {
system.initialize?.();
}
this._globalSystems.forEach(s => s.initialize?.());
logger.info(`启动World: ${this.name}`);
}
/**
* 停止World
* @zh 停止World
* @en Stop World
*/
public stop(): void {
if (!this._isActive) {
return;
}
for (const sceneName of this._activeScenes) {
this.setSceneActive(sceneName, false);
}
for (const system of this._globalSystems) {
system.reset?.();
}
if (!this._isActive) return;
this._activeScenes.forEach(name => this.setSceneActive(name, false));
this._globalSystems.forEach(s => s.reset?.());
this._isActive = false;
logger.info(`停止World: ${this.name}`);
}
/**
* @zh 更新World中的全局System
* @en Update global systems in World
*
* @internal Called by Core.update()
*/
public updateGlobalSystems(): void {
if (!this._isActive) {
return;
}
for (const system of this._globalSystems) {
system.update?.();
}
}
/**
* @zh 更新World中的所有激活Scene
* @en Update all active scenes in World
*
* @internal Called by Core.update()
*/
public updateScenes(): void {
if (!this._isActive) {
return;
}
for (const sceneName of this._activeScenes) {
const scene = this._scenes.get(sceneName);
scene?.update?.();
}
if (this._config.autoCleanup) {
this.cleanup();
}
}
/**
* 销毁World
* @zh 销毁World
* @en Destroy World
*/
public destroy(): void {
logger.info(`销毁World: ${this.name}`);
this.stop();
this.removeAllScenes();
for (const sceneName of Array.from(this._scenes.keys())) {
this.removeScene(sceneName);
}
for (const system of this._globalSystems) {
if (system.destroy) {
system.destroy();
} else {
system.reset?.();
}
}
this._globalSystems.forEach(s => s.destroy?.() ?? s.reset?.());
this._globalSystems.length = 0;
this._services.clear();
@@ -440,10 +391,49 @@ export class World {
this._activeScenes.clear();
}
/**
* 获取World状态
* @zh 更新World中的全局System
* @en Update global systems in World
* @internal
*/
public updateGlobalSystems(): void {
if (!this._isActive) return;
this._globalSystems.forEach(s => s.update?.());
}
/**
* @zh 更新World中的所有激活Scene
* @en Update all active scenes in World
* @internal
*/
public updateScenes(): void {
if (!this._isActive) return;
this._activeScenes.forEach(name => {
this._scenes.get(name)?.update?.();
});
if (this._config.autoCleanup) {
this.cleanup();
}
}
/**
* @zh 获取World状态
* @en Get World status
*/
public getStatus() {
const scenes: Array<{ id: string; name: string; isActive: boolean }> = [];
this._scenes.forEach((scene, id) => {
scenes.push({
id,
name: scene.name || id,
isActive: this._activeScenes.has(id)
});
});
return {
name: this.name,
isActive: this._isActive,
@@ -452,46 +442,61 @@ export class World {
globalSystemCount: this._globalSystems.length,
createdAt: this._createdAt,
config: { ...this._config },
scenes: Array.from(this._scenes.keys()).map((sceneName) => ({
id: sceneName,
isActive: this._activeScenes.has(sceneName),
name: this._scenes.get(sceneName)?.name || sceneName
}))
scenes
};
}
/**
* 获取World统计信息
* @zh 获取World统计信息
* @en Get World statistics
*/
public getStats() {
const stats = {
totalEntities: 0,
totalSystems: this._globalSystems.length,
let totalEntities = 0;
let totalSystems = this._globalSystems.length;
this._scenes.forEach(scene => {
totalEntities += scene.entities?.count ?? 0;
totalSystems += scene.systems?.length ?? 0;
});
return {
totalEntities,
totalSystems,
memoryUsage: 0,
performance: {
averageUpdateTime: 0,
maxUpdateTime: 0
}
};
}
for (const scene of this._scenes.values()) {
stats.totalEntities += scene.entities?.count ?? 0;
stats.totalSystems += scene.systems?.length ?? 0;
/**
* @zh 验证Scene名称
* @en Validate Scene name
*/
private validateSceneName(sceneName: string): void {
if (!sceneName?.trim()) {
throw new Error('Scene name不能为空');
}
if (this._scenes.has(sceneName)) {
throw new Error(`Scene name '${sceneName}' 已存在于World '${this.name}' 中`);
}
if (this._scenes.size >= this._config.maxScenes) {
throw new Error(`World '${this.name}' 已达到最大Scene数量限制: ${this._config.maxScenes}`);
}
return stats;
}
/**
* @zh 检查Scene是否可以被自动清理
* @en Check if a scene is eligible for auto cleanup
*/
private _isSceneCleanupCandidate(sceneName: string, scene: IScene): boolean {
private isCleanupCandidate(sceneName: string, scene: IScene): boolean {
const elapsed = Date.now() - this._createdAt;
return !this._activeScenes.has(sceneName) &&
scene.entities != null &&
scene.entities.count === 0 &&
elapsed > this._config.cleanupThresholdMs!;
elapsed > this._config.cleanupThresholdMs;
}
/**
@@ -499,33 +504,18 @@ export class World {
* @en Execute auto cleanup operation
*/
private cleanup(): void {
const candidates = [...this._scenes.entries()]
.filter(([name, scene]) => this._isSceneCleanupCandidate(name, scene));
const toRemove: string[] = [];
for (const [sceneName] of candidates) {
this.removeScene(sceneName);
logger.debug(`自动清理空Scene: ${sceneName} from World ${this.name}`);
}
this._scenes.forEach((scene, name) => {
if (this.isCleanupCandidate(name, scene)) {
toRemove.push(name);
}
});
toRemove.forEach(name => {
this.removeScene(name);
logger.debug(`自动清理空Scene: ${name} from World ${this.name}`);
});
}
/**
* 检查World是否激活
*/
public get isActive(): boolean {
return this._isActive;
}
/**
* 获取Scene数量
*/
public get sceneCount(): number {
return this._scenes.size;
}
/**
* 获取创建时间
*/
public get createdAt(): number {
return this._createdAt;
}
}

View File

@@ -5,80 +5,85 @@ import type { IService } from '../Core/ServiceContainer';
const logger = createLogger('WorldManager');
/**
* WorldManager配置接口
* @zh WorldManager配置接口
* @en WorldManager configuration interface
*/
export type IWorldManagerConfig = {
export interface IWorldManagerConfig {
/**
* 最大World数量
* @zh 最大World数量
* @en Maximum number of worlds
*/
maxWorlds?: number;
/**
* 是否自动清理空World
* @zh 是否自动清理空World
* @en Auto cleanup empty worlds
*/
autoCleanup?: boolean;
/**
* 清理间隔(帧数)
* @zh 清理间隔(帧数)
* @en Cleanup interval in frames
*/
cleanupFrameInterval?: number;
/**
* 是否启用调试模式
* @zh 是否启用调试模式
* @en Enable debug mode
*/
debug?: boolean;
}
/**
* World管理器 - 管理所有World实例
* @zh WorldManager默认配置
* @en WorldManager default configuration
*/
const DEFAULT_CONFIG: Required<IWorldManagerConfig> = {
maxWorlds: 50,
autoCleanup: true,
cleanupFrameInterval: 1800,
debug: false
};
/**
* @zh 清理阈值(毫秒)
* @en Cleanup threshold in milliseconds
*/
const CLEANUP_THRESHOLD_MS = 10 * 60 * 1000; // 10 minutes
/**
* @zh World管理器 - 管理所有World实例
* @en World Manager - Manages all World instances
*
* WorldManager负责管理多个独立的World实例。
* @zh WorldManager负责管理多个独立的World实例。
* 每个World都是独立的ECS环境可以包含多个Scene。
* @en WorldManager is responsible for managing multiple independent World instances.
* Each World is an isolated ECS environment that can contain multiple Scenes.
*
* 适用场景:
* @zh 适用场景:
* - MMO游戏的多房间管理
* - 服务器端的多游戏实例
* - 需要完全隔离的多个游戏环境
* @en Use cases:
* - Multi-room management for MMO games
* - Multiple game instances on server-side
* - Completely isolated game environments
*
* @example
* ```typescript
* // 创建WorldManager实例
* const worldManager = new WorldManager({
* maxWorlds: 100,
* autoCleanup: true
* });
*
* // 创建游戏房间World
* const room1 = worldManager.createWorld('room_001', {
* name: 'GameRoom_001',
* maxScenes: 5
* });
* room1.setActive(true);
*
* // 游戏循环
* function gameLoop(deltaTime: number) {
* Core.update(deltaTime);
* worldManager.updateAll(); // 更新所有活跃World
* }
* const worldManager = new WorldManager({ maxWorlds: 100 });
* const room = worldManager.createWorld('room_001');
* worldManager.setWorldActive('room_001', true);
* ```
*/
export class WorldManager implements IService {
private readonly _config: Required<IWorldManagerConfig>;
private readonly _worlds: Map<string, World> = new Map();
private _isRunning: boolean = false;
private _framesSinceCleanup: number = 0;
private readonly _worlds = new Map<string, World>();
private _isRunning = true;
private _framesSinceCleanup = 0;
public constructor(config: IWorldManagerConfig = {}) {
this._config = {
maxWorlds: 50,
autoCleanup: true,
cleanupFrameInterval: 1800, // 1800帧
debug: false,
...config
};
// 默认启动运行状态
this._isRunning = true;
constructor(config: IWorldManagerConfig = {}) {
this._config = { ...DEFAULT_CONFIG, ...config };
logger.info('WorldManager已初始化', {
maxWorlds: this._config.maxWorlds,
@@ -88,27 +93,57 @@ export class WorldManager implements IService {
}
/**
* 创建新World
* @zh 获取World总数
* @en Get total world count
*/
public get worldCount(): number {
return this._worlds.size;
}
/**
* @zh 获取激活World数量
* @en Get active world count
*/
public get activeWorldCount(): number {
let count = 0;
this._worlds.forEach(world => {
if (world.isActive) count++;
});
return count;
}
/**
* @zh 检查是否正在运行
* @en Check if running
*/
public get isRunning(): boolean {
return this._isRunning;
}
/**
* @zh 获取配置
* @en Get configuration
*/
public get config(): IWorldManagerConfig {
return { ...this._config };
}
/**
* @zh 创建新World
* @en Create new World
*
* @param worldName - @zh World名称 @en World name
* @param config - @zh World配置 @en World configuration
* @returns @zh 创建的World实例 @en Created World instance
* @throws @zh 名称为空、重复或超出限制时抛出错误 @en Throws if name is empty, duplicate, or limit exceeded
*/
public createWorld(worldName: string, config?: IWorldConfig): World {
if (!worldName || typeof worldName !== 'string' || worldName.trim() === '') {
throw new Error('World name不能为空');
}
this.validateWorldName(worldName);
if (this._worlds.has(worldName)) {
throw new Error(`World name '${worldName}' 已存在`);
}
if (this._worlds.size >= this._config.maxWorlds!) {
throw new Error(`已达到最大World数量限制: ${this._config.maxWorlds}`);
}
// 优先级config.debug > WorldManager.debug > 默认
const worldConfig: IWorldConfig = {
...config,
name: worldName,
debug: config?.debug ?? this._config.debug ?? false,
...(config?.maxScenes !== undefined && { maxScenes: config.maxScenes }),
...(config?.autoCleanup !== undefined && { autoCleanup: config.autoCleanup })
debug: config?.debug ?? this._config.debug
};
const world = new World(worldConfig);
@@ -118,45 +153,56 @@ export class WorldManager implements IService {
}
/**
* 移除World
* @zh 移除World
* @en Remove World
*
* @param worldName - @zh World名称 @en World name
* @returns @zh 是否成功移除 @en Whether removal was successful
*/
public removeWorld(worldName: string): boolean {
const world = this._worlds.get(worldName);
if (!world) {
return false;
}
if (!world) return false;
// 销毁World
world.destroy();
this._worlds.delete(worldName);
logger.info(`移除World: ${worldName}`);
return true;
}
/**
* 获取World
* @zh 获取World
* @en Get World
*
* @param worldName - @zh World名称 @en World name
* @returns @zh World实例或null @en World instance or null
*/
public getWorld(worldName: string): World | null {
return this._worlds.get(worldName) || null;
return this._worlds.get(worldName) ?? null;
}
/**
* 获取所有World ID
* @zh 获取所有World ID
* @en Get all World IDs
*/
public getWorldIds(): string[] {
return Array.from(this._worlds.keys());
}
/**
* 获取所有World
* @zh 获取所有World
* @en Get all Worlds
*/
public getAllWorlds(): World[] {
return Array.from(this._worlds.values());
}
/**
* 设置World激活状态
* @zh 设置World激活状态
* @en Set World active state
*
* @param worldName - @zh World名称 @en World name
* @param active - @zh 是否激活 @en Whether to activate
*/
public setWorldActive(worldName: string, active: boolean): void {
const world = this._worlds.get(worldName);
@@ -175,204 +221,84 @@ export class WorldManager implements IService {
}
/**
* 检查World是否激活
* @zh 检查World是否激活
* @en Check if World is active
*/
public isWorldActive(worldName: string): boolean {
const world = this._worlds.get(worldName);
return world?.isActive ?? false;
return this._worlds.get(worldName)?.isActive ?? false;
}
/**
* 更新所有活跃的World
*
* 应该在每帧的游戏循环中调用。
* 会自动更新所有活跃World的全局系统和场景。
*
* @example
* ```typescript
* function gameLoop(deltaTime: number) {
* Core.update(deltaTime); // 更新全局服务
* worldManager.updateAll(); // 更新所有World
* }
* ```
*/
public updateAll(): void {
if (!this._isRunning) return;
for (const world of this._worlds.values()) {
if (world.isActive) {
// 更新World的全局System
world.updateGlobalSystems();
// 更新World中的所有Scene
world.updateScenes();
}
}
// 基于帧的自动清理
if (this._config.autoCleanup) {
this._framesSinceCleanup++;
if (this._framesSinceCleanup >= this._config.cleanupFrameInterval) {
this.cleanup();
this._framesSinceCleanup = 0; // 重置计数器
if (this._config.debug) {
logger.debug(`执行定期清理World (间隔: ${this._config.cleanupFrameInterval} 帧)`);
}
}
}
}
/**
* 获取所有激活的World
* @zh 获取所有激活的World
* @en Get all active Worlds
*/
public getActiveWorlds(): World[] {
const activeWorlds: World[] = [];
for (const world of this._worlds.values()) {
if (world.isActive) {
activeWorlds.push(world);
}
}
return activeWorlds;
const result: World[] = [];
this._worlds.forEach(world => {
if (world.isActive) result.push(world);
});
return result;
}
/**
* 启动所有World
* @zh 查找满足条件的World
* @en Find Worlds matching predicate
*
* @param predicate - @zh 过滤条件 @en Filter predicate
*/
public findWorlds(predicate: (world: World) => boolean): World[] {
const result: World[] = [];
this._worlds.forEach(world => {
if (predicate(world)) result.push(world);
});
return result;
}
/**
* @zh 根据名称查找World
* @en Find World by name
*
* @param name - @zh World名称 @en World name
*/
public findWorldByName(name: string): World | null {
let found: World | null = null;
this._worlds.forEach(world => {
if (world.name === name) found = world;
});
return found;
}
/**
* @zh 启动所有World
* @en Start all Worlds
*/
public startAll(): void {
this._isRunning = true;
for (const world of this._worlds.values()) {
world.start();
}
this._worlds.forEach(world => world.start());
logger.info('启动所有World');
}
/**
* 停止所有World
* @zh 停止所有World
* @en Stop all Worlds
*/
public stopAll(): void {
this._isRunning = false;
for (const world of this._worlds.values()) {
world.stop();
}
this._worlds.forEach(world => world.stop());
logger.info('停止所有World');
}
/**
* 查找满足条件的World
*/
public findWorlds(predicate: (world: World) => boolean): World[] {
const results: World[] = [];
for (const world of this._worlds.values()) {
if (predicate(world)) {
results.push(world);
}
}
return results;
}
/**
* 根据名称查找World
*/
public findWorldByName(name: string): World | null {
for (const world of this._worlds.values()) {
if (world.name === name) {
return world;
}
}
return null;
}
/**
* 获取WorldManager统计信息
*/
public getStats() {
const stats = {
totalWorlds: this._worlds.size,
activeWorlds: this.activeWorldCount,
totalScenes: 0,
totalEntities: 0,
totalSystems: 0,
memoryUsage: 0,
isRunning: this._isRunning,
config: { ...this._config },
worlds: [] as any[]
};
for (const [worldName, world] of this._worlds) {
const worldStats = world.getStats();
stats.totalScenes += worldStats.totalSystems; // World的getStats可能需要调整
stats.totalEntities += worldStats.totalEntities;
stats.totalSystems += worldStats.totalSystems;
stats.worlds.push({
id: worldName,
name: world.name,
isActive: world.isActive,
sceneCount: world.sceneCount,
...worldStats
});
}
return stats;
}
/**
* 获取详细状态信息
*/
public getDetailedStatus() {
return {
...this.getStats(),
worlds: Array.from(this._worlds.entries()).map(([worldName, world]) => ({
id: worldName,
isActive: world.isActive,
status: world.getStatus()
}))
};
}
/**
* 清理空World
*/
public cleanup(): number {
const worldsToRemove: string[] = [];
for (const [worldName, world] of this._worlds) {
if (this.shouldCleanupWorld(world)) {
worldsToRemove.push(worldName);
}
}
for (const worldName of worldsToRemove) {
this.removeWorld(worldName);
}
if (worldsToRemove.length > 0) {
logger.debug(`清理了 ${worldsToRemove.length} 个World`);
}
return worldsToRemove.length;
}
/**
* 销毁WorldManager
* @zh 销毁WorldManager
* @en Destroy WorldManager
*/
public destroy(): void {
logger.info('正在销毁WorldManager...');
// 停止所有World
this.stopAll();
// 销毁所有World
const worldNames = Array.from(this._worlds.keys());
for (const worldName of worldNames) {
this.removeWorld(worldName);
}
worldNames.forEach(name => this.removeWorld(name));
this._worlds.clear();
this._isRunning = false;
@@ -381,67 +307,178 @@ export class WorldManager implements IService {
}
/**
* 实现 IService 接口的 dispose 方法
* 调用 destroy 方法进行清理
* @zh 实现 IService 接口的 dispose 方法
* @en Implement IService dispose method
*/
public dispose(): void {
this.destroy();
}
/**
* 判断World是否应该被清理
* 清理策略:
* 1. World未激活
* 2. 没有Scene或所有Scene都是空的
* 3. 创建时间超过10分钟
* @zh 更新所有活跃的World
* @en Update all active Worlds
*
* @zh 应该在每帧的游戏循环中调用
* @en Should be called in each frame of game loop
*/
private shouldCleanupWorld(world: World): boolean {
if (world.isActive) {
return false;
public updateAll(): void {
if (!this._isRunning) return;
this._worlds.forEach(world => {
if (world.isActive) {
world.updateGlobalSystems();
world.updateScenes();
}
});
this.processAutoCleanup();
}
/**
* @zh 获取WorldManager统计信息
* @en Get WorldManager statistics
*/
public getStats() {
let totalScenes = 0;
let totalEntities = 0;
let totalSystems = 0;
const worldsList: Array<{
id: string;
name: string;
isActive: boolean;
sceneCount: number;
totalEntities: number;
totalSystems: number;
}> = [];
this._worlds.forEach((world, worldName) => {
const worldStats = world.getStats();
totalScenes += world.sceneCount;
totalEntities += worldStats.totalEntities;
totalSystems += worldStats.totalSystems;
worldsList.push({
id: worldName,
name: world.name,
isActive: world.isActive,
sceneCount: world.sceneCount,
...worldStats
});
});
return {
totalWorlds: this._worlds.size,
activeWorlds: this.activeWorldCount,
totalScenes,
totalEntities,
totalSystems,
memoryUsage: 0,
isRunning: this._isRunning,
config: { ...this._config },
worlds: worldsList
};
}
/**
* @zh 获取详细状态信息
* @en Get detailed status information
*/
public getDetailedStatus() {
const worlds: Array<{
id: string;
isActive: boolean;
status: ReturnType<World['getStatus']>;
}> = [];
this._worlds.forEach((world, worldName) => {
worlds.push({
id: worldName,
isActive: world.isActive,
status: world.getStatus()
});
});
return { ...this.getStats(), worlds };
}
/**
* @zh 清理空World
* @en Cleanup empty Worlds
*
* @returns @zh 清理的World数量 @en Number of cleaned up Worlds
*/
public cleanup(): number {
const toRemove: string[] = [];
this._worlds.forEach((world, worldName) => {
if (this.isCleanupCandidate(world)) {
toRemove.push(worldName);
}
});
toRemove.forEach(name => this.removeWorld(name));
if (toRemove.length > 0) {
logger.debug(`清理了 ${toRemove.length} 个World`);
}
return toRemove.length;
}
/**
* @zh 验证World名称
* @en Validate World name
*/
private validateWorldName(worldName: string): void {
if (!worldName?.trim()) {
throw new Error('World name不能为空');
}
if (this._worlds.has(worldName)) {
throw new Error(`World name '${worldName}' 已存在`);
}
if (this._worlds.size >= this._config.maxWorlds) {
throw new Error(`已达到最大World数量限制: ${this._config.maxWorlds}`);
}
}
/**
* @zh 处理自动清理
* @en Process auto cleanup
*/
private processAutoCleanup(): void {
if (!this._config.autoCleanup) return;
this._framesSinceCleanup++;
if (this._framesSinceCleanup >= this._config.cleanupFrameInterval) {
this.cleanup();
this._framesSinceCleanup = 0;
if (this._config.debug) {
logger.debug(`执行定期清理World (间隔: ${this._config.cleanupFrameInterval} 帧)`);
}
}
}
/**
* @zh 判断World是否应该被清理
* @en Check if World should be cleaned up
*
* @zh 清理策略:未激活 + (无Scene或全空Scene) + 创建超过10分钟
* @en Cleanup policy: inactive + (no scenes or all empty) + created over 10 minutes ago
*/
private isCleanupCandidate(world: World): boolean {
if (world.isActive) return false;
const age = Date.now() - world.createdAt;
const isOldEnough = age > 10 * 60 * 1000; // 10分钟
if (age <= CLEANUP_THRESHOLD_MS) return false;
if (world.sceneCount === 0) {
return isOldEnough;
}
if (world.sceneCount === 0) return true;
// 检查是否所有Scene都是空的
const allScenes = world.getAllScenes();
const hasEntities = allScenes.some((scene) => scene.entities && scene.entities.count > 0);
return !hasEntities && isOldEnough;
}
const hasEntities = world.getAllScenes().some(
scene => scene.entities && scene.entities.count > 0
);
/**
* 获取World总数
*/
public get worldCount(): number {
return this._worlds.size;
}
/**
* 获取激活World数量
*/
public get activeWorldCount(): number {
let count = 0;
for (const world of this._worlds.values()) {
if (world.isActive) count++;
}
return count;
}
/**
* 检查是否正在运行
*/
public get isRunning(): boolean {
return this._isRunning;
}
/**
* 获取配置
*/
public get config(): IWorldManagerConfig {
return { ...this._config };
return !hasEntities;
}
}

View File

@@ -1,5 +1,21 @@
# @esengine/fsm
## 1.0.3
### Patch Changes
- Updated dependencies [[`7d74623`](https://github.com/esengine/esengine/commit/7d746237100084ac3456f1af92ff664db4e50cc8)]:
- @esengine/ecs-framework@2.4.4
- @esengine/blueprint@1.0.2
## 1.0.2
### Patch Changes
- Updated dependencies [[`ce2db4e`](https://github.com/esengine/esengine/commit/ce2db4e48a7cdac44265420ef16e83f6424f4dea)]:
- @esengine/ecs-framework@2.4.3
- @esengine/blueprint@1.0.1
## 1.0.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@esengine/fsm",
"version": "1.0.1",
"version": "1.0.3",
"description": "Finite State Machine for ECS Framework / ECS 框架的有限状态机",
"type": "module",
"main": "./dist/index.js",

View File

@@ -1,5 +1,21 @@
# @esengine/pathfinding
## 1.0.3
### Patch Changes
- Updated dependencies [[`7d74623`](https://github.com/esengine/esengine/commit/7d746237100084ac3456f1af92ff664db4e50cc8)]:
- @esengine/ecs-framework@2.4.4
- @esengine/blueprint@1.0.2
## 1.0.2
### Patch Changes
- Updated dependencies [[`ce2db4e`](https://github.com/esengine/esengine/commit/ce2db4e48a7cdac44265420ef16e83f6424f4dea)]:
- @esengine/ecs-framework@2.4.3
- @esengine/blueprint@1.0.1
## 1.0.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@esengine/pathfinding",
"version": "1.0.1",
"version": "1.0.3",
"description": "寻路系统 | Pathfinding System - A*, Grid, NavMesh",
"type": "module",
"main": "./dist/index.js",

View File

@@ -1,5 +1,21 @@
# @esengine/procgen
## 1.0.3
### Patch Changes
- Updated dependencies [[`7d74623`](https://github.com/esengine/esengine/commit/7d746237100084ac3456f1af92ff664db4e50cc8)]:
- @esengine/ecs-framework@2.4.4
- @esengine/blueprint@1.0.2
## 1.0.2
### Patch Changes
- Updated dependencies [[`ce2db4e`](https://github.com/esengine/esengine/commit/ce2db4e48a7cdac44265420ef16e83f6424f4dea)]:
- @esengine/ecs-framework@2.4.3
- @esengine/blueprint@1.0.1
## 1.0.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@esengine/procgen",
"version": "1.0.1",
"version": "1.0.3",
"description": "Procedural generation tools for ECS Framework / ECS 框架的程序化生成工具",
"type": "module",
"main": "./dist/index.js",

View File

@@ -1,5 +1,21 @@
# @esengine/spatial
## 1.0.4
### Patch Changes
- Updated dependencies [[`7d74623`](https://github.com/esengine/esengine/commit/7d746237100084ac3456f1af92ff664db4e50cc8)]:
- @esengine/ecs-framework@2.4.4
- @esengine/blueprint@1.0.2
## 1.0.3
### Patch Changes
- Updated dependencies [[`ce2db4e`](https://github.com/esengine/esengine/commit/ce2db4e48a7cdac44265420ef16e83f6424f4dea)]:
- @esengine/ecs-framework@2.4.3
- @esengine/blueprint@1.0.1
## 1.0.2
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@esengine/spatial",
"version": "1.0.2",
"version": "1.0.4",
"description": "Spatial query and indexing system for ECS Framework / ECS 框架的空间查询和索引系统",
"type": "module",
"main": "./dist/index.js",

View File

@@ -1,5 +1,21 @@
# @esengine/timer
## 1.0.3
### Patch Changes
- Updated dependencies [[`7d74623`](https://github.com/esengine/esengine/commit/7d746237100084ac3456f1af92ff664db4e50cc8)]:
- @esengine/ecs-framework@2.4.4
- @esengine/blueprint@1.0.2
## 1.0.2
### Patch Changes
- Updated dependencies [[`ce2db4e`](https://github.com/esengine/esengine/commit/ce2db4e48a7cdac44265420ef16e83f6424f4dea)]:
- @esengine/ecs-framework@2.4.3
- @esengine/blueprint@1.0.1
## 1.0.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@esengine/timer",
"version": "1.0.1",
"version": "1.0.3",
"description": "Timer and cooldown system for ECS Framework / ECS 框架的定时器和冷却系统",
"type": "module",
"main": "./dist/index.js",

View File

@@ -0,0 +1,17 @@
# @esengine/network-server
## 1.0.2
### Patch Changes
- [#354](https://github.com/esengine/esengine/pull/354) [`1e240e8`](https://github.com/esengine/esengine/commit/1e240e86f2f75672c3609c9d86238a9ec08ebb4e) Thanks [@esengine](https://github.com/esengine)! - feat(cli): 增强 Node.js 服务端适配器
**@esengine/cli:**
- 添加 @esengine/network-server 依赖支持
- 生成完整的 ECS 游戏服务器项目结构
- 组件使用 @ECSComponent 装饰器注册
- tsconfig 启用 experimentalDecorators
**@esengine/network-server:**
- 支持 ESM/CJS 双格式导出
- 添加 ws@8.18.0 解决 Node.js 24 兼容性问题

View File

@@ -1,15 +1,21 @@
{
"name": "@esengine/network-server",
"version": "1.0.0",
"version": "1.0.2",
"description": "TSRPC-based network server for ESEngine",
"type": "module",
"main": "dist/index.js",
"main": "dist/index.cjs",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
}
},
"files": [
@@ -22,7 +28,8 @@
},
"dependencies": {
"@esengine/network-protocols": "workspace:*",
"tsrpc": "^3.4.15"
"tsrpc": "^3.4.15",
"ws": "^8.18.0"
},
"devDependencies": {
"tsup": "^8.5.1",

View File

@@ -2,7 +2,7 @@ import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts', 'src/main.ts'],
format: ['esm'],
format: ['esm', 'cjs'],
dts: true,
sourcemap: true,
clean: true,

View File

@@ -1,5 +1,21 @@
# @esengine/cli
## 1.3.0
### Minor Changes
- [#354](https://github.com/esengine/esengine/pull/354) [`1e240e8`](https://github.com/esengine/esengine/commit/1e240e86f2f75672c3609c9d86238a9ec08ebb4e) Thanks [@esengine](https://github.com/esengine)! - feat(cli): 增强 Node.js 服务端适配器
**@esengine/cli:**
- 添加 @esengine/network-server 依赖支持
- 生成完整的 ECS 游戏服务器项目结构
- 组件使用 @ECSComponent 装饰器注册
- tsconfig 启用 experimentalDecorators
**@esengine/network-server:**
- 支持 ESM/CJS 双格式导出
- 添加 ws@8.18.0 解决 Node.js 24 兼容性问题
## 1.2.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@esengine/cli",
"version": "1.2.1",
"version": "1.3.0",
"description": "CLI tool for adding ESEngine ECS framework to existing projects",
"type": "module",
"main": "dist/index.js",

View File

@@ -7,11 +7,12 @@ import type { FileEntry, PlatformAdapter, ProjectConfig } from './types.js';
export const nodejsAdapter: PlatformAdapter = {
id: 'nodejs',
name: 'Node.js',
description: 'Generate standalone Node.js project with ECS (for servers, CLI tools, simulations)',
description: 'Generate Node.js game server with ECS and networking',
getDependencies() {
return {
'@esengine/ecs-framework': 'latest'
'@esengine/ecs-framework': 'latest',
'@esengine/network-server': 'latest'
};
},
@@ -33,147 +34,96 @@ export const nodejsAdapter: PlatformAdapter = {
},
generateFiles(config: ProjectConfig): FileEntry[] {
const files: FileEntry[] = [];
files.push({
path: 'src/index.ts',
content: generateIndex(config)
});
files.push({
path: 'src/Game.ts',
content: generateGame(config)
});
files.push({
path: 'src/components/PositionComponent.ts',
content: generatePositionComponent()
});
files.push({
path: 'src/systems/MovementSystem.ts',
content: generateMovementSystem()
});
files.push({
path: 'tsconfig.json',
content: generateTsConfig()
});
files.push({
path: 'README.md',
content: generateReadme(config)
});
return files;
return [
{ path: 'src/index.ts', content: generateIndex(config) },
{ path: 'src/server/GameServer.ts', content: generateGameServer(config) },
{ path: 'src/game/Game.ts', content: generateGame(config) },
{ path: 'src/game/scenes/MainScene.ts', content: generateMainScene(config) },
{ path: 'src/game/components/PositionComponent.ts', content: generatePositionComponent() },
{ path: 'src/game/components/VelocityComponent.ts', content: generateVelocityComponent() },
{ path: 'src/game/systems/MovementSystem.ts', content: generateMovementSystem() },
{ path: 'tsconfig.json', content: generateTsConfig() },
{ path: 'README.md', content: generateReadme(config) }
];
}
};
function generateIndex(config: ProjectConfig): string {
return `import { Game } from './Game.js';
return `import { createGameServer } from './server/GameServer';
const game = new Game();
const PORT = Number(process.env.PORT) || 3000;
async function main() {
const server = createGameServer({ port: PORT });
await server.start();
console.log('========================================');
console.log(' ${config.name} Server');
console.log('========================================');
console.log(\` WebSocket: ws://localhost:\${PORT}\`);
console.log(' Press Ctrl+C to stop');
console.log('========================================');
}
// Handle graceful shutdown
process.on('SIGINT', () => {
console.log('\\nShutting down...');
game.stop();
process.exit(0);
});
process.on('SIGTERM', () => {
game.stop();
process.exit(0);
});
main().catch(console.error);
`;
}
// Start the game
game.start();
function generateGameServer(config: ProjectConfig): string {
return `import { GameServer, type IServerConfig } from '@esengine/network-server';
import { Game } from '../game/Game';
console.log('[${config.name}] Game started. Press Ctrl+C to stop.');
/**
* @zh 创建游戏服务器
* @en Create game server
*/
export function createGameServer(config: Partial<IServerConfig> = {}): GameServer {
const server = new GameServer({
port: config.port ?? 3000,
roomConfig: {
maxPlayers: 16,
tickRate: 20,
...config.roomConfig
}
});
// 初始化 ECS 游戏逻辑
const game = new Game();
game.start();
return server;
}
`;
}
function generateGame(config: ProjectConfig): string {
return `import { Core, Scene, type ICoreConfig } from '@esengine/ecs-framework';
import { MovementSystem } from './systems/MovementSystem.js';
return `import { Core, type ICoreConfig } from '@esengine/ecs-framework';
import { MainScene } from './scenes/MainScene';
/**
* Game configuration options
*/
export interface GameOptions {
/** @zh 调试模式 @en Debug mode */
debug?: boolean;
/** @zh 目标帧率 @en Target FPS */
targetFPS?: number;
/** @zh 远程调试配置 @en Remote debug configuration */
remoteDebug?: {
/** @zh 启用远程调试 @en Enable remote debugging */
enabled: boolean;
/** @zh WebSocket地址 @en WebSocket URL */
url: string;
/** @zh 自动重连 @en Auto reconnect */
autoReconnect?: boolean;
};
}
/**
* Game Scene - Define your game systems here
*/
class GameScene extends Scene {
initialize(): void {
this.name = '${config.name}';
this.addSystem(new MovementSystem());
// Add more systems here...
}
onStart(): void {
// Create your initial entities here
}
}
/**
* Main game class with ECS game loop
*
* Features:
* - Configurable debug mode and FPS
* - Remote debugging via WebSocket
* - Fixed timestep game loop
* - Graceful start/stop
* @zh 游戏主类
* @en Main game class
*/
export class Game {
private readonly _scene: GameScene;
private readonly _targetFPS: number;
private _scene: MainScene;
private _running = false;
private _tickInterval: ReturnType<typeof setInterval> | null = null;
private _lastTime = 0;
private _targetFPS = 60;
get scene() { return this._scene; }
get running() { return this._running; }
constructor(options: GameOptions = {}) {
const { debug = false, targetFPS = 60, remoteDebug } = options;
constructor(options: { debug?: boolean; targetFPS?: number } = {}) {
const { debug = false, targetFPS = 60 } = options;
this._targetFPS = targetFPS;
const config: ICoreConfig = { debug };
// 配置远程调试
if (remoteDebug?.enabled && remoteDebug.url) {
config.debugConfig = {
enabled: true,
websocketUrl: remoteDebug.url,
autoReconnect: remoteDebug.autoReconnect ?? true,
channels: {
entities: true,
systems: true,
performance: true,
components: true,
scenes: true
}
};
}
Core.create(config);
this._scene = new GameScene();
this._scene = new MainScene();
Core.setScene(this._scene);
}
@@ -203,11 +153,38 @@ export class Game {
`;
}
function generateMainScene(config: ProjectConfig): string {
return `import { Scene } from '@esengine/ecs-framework';
import { MovementSystem } from '../systems/MovementSystem';
/**
* @zh 主场景
* @en Main scene
*/
export class MainScene extends Scene {
initialize(): void {
this.name = '${config.name}';
// 注册系统
this.addSystem(new MovementSystem());
// 添加更多系统...
}
onStart(): void {
// 创建初始实体
console.log('[MainScene] Scene started');
}
}
`;
}
function generatePositionComponent(): string {
return `import { Component, ECSComponent } from '@esengine/ecs-framework';
/**
* Position component - stores entity position
* @zh 位置组件
* @en Position component
*/
@ECSComponent('Position')
export class PositionComponent extends Component {
@@ -219,31 +196,61 @@ export class PositionComponent extends Component {
this.x = x;
this.y = y;
}
reset(): void {
this.x = 0;
this.y = 0;
}
}
`;
}
function generateVelocityComponent(): string {
return `import { Component, ECSComponent } from '@esengine/ecs-framework';
/**
* @zh 速度组件
* @en Velocity component
*/
@ECSComponent('Velocity')
export class VelocityComponent extends Component {
vx = 0;
vy = 0;
constructor(vx = 0, vy = 0) {
super();
this.vx = vx;
this.vy = vy;
}
reset(): void {
this.vx = 0;
this.vy = 0;
}
}
`;
}
function generateMovementSystem(): string {
return `import { EntitySystem, Matcher, Entity, Time, ECSSystem } from '@esengine/ecs-framework';
import { PositionComponent } from '../components/PositionComponent.js';
return `import { EntitySystem, Matcher, Entity, Time } from '@esengine/ecs-framework';
import { PositionComponent } from '../components/PositionComponent';
import { VelocityComponent } from '../components/VelocityComponent';
/**
* Movement system - processes entities with PositionComponent
*
* Customize this system for your game logic.
* @zh 移动系统
* @en Movement system
*/
@ECSSystem('MovementSystem')
export class MovementSystem extends EntitySystem {
constructor() {
super(Matcher.empty().all(PositionComponent));
super(Matcher.empty().all(PositionComponent, VelocityComponent));
}
protected process(entities: readonly Entity[]): void {
for (const entity of entities) {
const position = entity.getComponent(PositionComponent)!;
// Update position using Time.deltaTime
// position.x += velocity.dx * Time.deltaTime;
}
protected processEntity(entity: Entity, dt: number): void {
const pos = entity.getComponent(PositionComponent)!;
const vel = entity.getComponent(VelocityComponent)!;
pos.x += vel.vx * dt;
pos.y += vel.vy * dt;
}
}
`;
@@ -253,8 +260,8 @@ function generateTsConfig(): string {
return `{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"module": "CommonJS",
"moduleResolution": "Node",
"lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./src",
@@ -263,7 +270,9 @@ function generateTsConfig(): string {
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"sourceMap": true
"sourceMap": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
@@ -274,75 +283,54 @@ function generateTsConfig(): string {
function generateReadme(config: ProjectConfig): string {
return `# ${config.name}
A Node.js project using ESEngine ECS framework.
Node.js 游戏服务器,基于 ESEngine ECS 框架。
## Quick Start
## 快速开始
\`\`\`bash
# Install dependencies
# 安装依赖
npm install
# Run in development mode (with hot reload)
# 开发模式(热重载)
npm run dev
# Build and run
# 构建并运行
npm run build:start
\`\`\`
## Project Structure
## 项目结构
\`\`\`
src/
├── index.ts # Entry point
├── Game.ts # Game loop and ECS setup
├── components/ # ECS components (data)
│ └── PositionComponent.ts
└── systems/ # ECS systems (logic)
── MovementSystem.ts
├── index.ts # 入口文件
├── server/
│ └── GameServer.ts # 网络服务器配置
└── game/
├── Game.ts # ECS 游戏主类
── scenes/
│ └── MainScene.ts # 主场景
├── components/ # ECS 组件
│ ├── PositionComponent.ts
│ └── VelocityComponent.ts
└── systems/ # ECS 系统
└── MovementSystem.ts
\`\`\`
## Creating Components
## 客户端连接
\`\`\`typescript
import { Component } from '@esengine/ecs-framework';
import { NetworkPlugin } from '@esengine/network';
export class HealthComponent extends Component {
current = 100;
max = 100;
const networkPlugin = new NetworkPlugin({
serverUrl: 'ws://localhost:3000'
});
reset(): void {
this.current = this.max;
}
}
await networkPlugin.connect('PlayerName');
\`\`\`
## Creating Systems
## 文档
\`\`\`typescript
import { EntitySystem, Matcher, Entity } from '@esengine/ecs-framework';
import { HealthComponent } from '../components/HealthComponent.js';
export class HealthSystem extends EntitySystem {
constructor() {
super(Matcher.all(HealthComponent));
}
protected processEntity(entity: Entity, dt: number): void {
const health = entity.getComponent(HealthComponent)!;
// Your logic here
}
}
\`\`\`
## Use Cases
- Game servers
- CLI tools with complex logic
- Simulations
- Automated testing
## Documentation
- [ESEngine ECS Framework](https://github.com/esengine/esengine)
- [ESEngine 文档](https://esengine.github.io/esengine/)
- [Network 模块](https://esengine.github.io/esengine/modules/network/)
`;
}

View File

@@ -1,5 +1,27 @@
# @esengine/demos
## 1.0.3
### Patch Changes
- Updated dependencies []:
- @esengine/fsm@1.0.3
- @esengine/pathfinding@1.0.3
- @esengine/procgen@1.0.3
- @esengine/spatial@1.0.4
- @esengine/timer@1.0.3
## 1.0.2
### Patch Changes
- Updated dependencies []:
- @esengine/fsm@1.0.2
- @esengine/pathfinding@1.0.2
- @esengine/procgen@1.0.2
- @esengine/spatial@1.0.3
- @esengine/timer@1.0.2
## 1.0.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@esengine/demos",
"version": "1.0.1",
"version": "1.0.3",
"private": true,
"description": "Demo tests for ESEngine modules documentation",
"type": "module",

88
pnpm-lock.yaml generated
View File

@@ -185,7 +185,7 @@ importers:
version: link:../../engine/ecs-engine-bindgen
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../framework/core
version: link:../../framework/core/dist
'@esengine/editor-core':
specifier: workspace:*
version: link:../editor-core
@@ -385,7 +385,7 @@ importers:
version: link:../plugins/asset-system-editor
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../framework/core
version: link:../../framework/core/dist
'@esengine/engine-core':
specifier: workspace:*
version: link:../../engine/engine-core
@@ -476,7 +476,7 @@ importers:
devDependencies:
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../framework/core
version: link:../../framework/core/dist
'@esengine/ecs-framework-math':
specifier: workspace:*
version: link:../../framework/math
@@ -550,7 +550,7 @@ importers:
version: link:../../../tools/build-config
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../../framework/core
version: link:../../../framework/core/dist
'@esengine/editor-core':
specifier: workspace:*
version: link:../../editor-core
@@ -596,7 +596,7 @@ importers:
version: link:../../../tools/build-config
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../../framework/core
version: link:../../../framework/core/dist
'@esengine/editor-core':
specifier: workspace:*
version: link:../../editor-core
@@ -639,7 +639,7 @@ importers:
version: link:../../../tools/build-config
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../../framework/core
version: link:../../../framework/core/dist
'@esengine/editor-core':
specifier: workspace:*
version: link:../../editor-core
@@ -676,7 +676,7 @@ importers:
version: link:../../../tools/build-config
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../../framework/core
version: link:../../../framework/core/dist
'@esengine/editor-core':
specifier: workspace:*
version: link:../../editor-core
@@ -713,7 +713,7 @@ importers:
version: link:../../../tools/build-config
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../../framework/core
version: link:../../../framework/core/dist
'@esengine/editor-core':
specifier: workspace:*
version: link:../../editor-core
@@ -756,7 +756,7 @@ importers:
version: link:../../../tools/build-config
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../../framework/core
version: link:../../../framework/core/dist
'@esengine/editor-core':
specifier: workspace:*
version: link:../../editor-core
@@ -833,7 +833,7 @@ importers:
version: link:../../../tools/build-config
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../../framework/core
version: link:../../../framework/core/dist
'@esengine/editor-core':
specifier: workspace:*
version: link:../../editor-core
@@ -876,7 +876,7 @@ importers:
version: link:../../../tools/build-config
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../../framework/core
version: link:../../../framework/core/dist
'@esengine/editor-core':
specifier: workspace:*
version: link:../../editor-core
@@ -913,7 +913,7 @@ importers:
version: link:../../../tools/build-config
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../../framework/core
version: link:../../../framework/core/dist
'@esengine/editor-core':
specifier: workspace:*
version: link:../../editor-core
@@ -953,7 +953,7 @@ importers:
version: link:../../../tools/build-config
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../../framework/core
version: link:../../../framework/core/dist
'@esengine/editor-core':
specifier: workspace:*
version: link:../../editor-core
@@ -993,7 +993,7 @@ importers:
version: link:../../../tools/build-config
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../../framework/core
version: link:../../../framework/core/dist
'@esengine/editor-core':
specifier: workspace:*
version: link:../../editor-core
@@ -1036,7 +1036,7 @@ importers:
version: link:../../../tools/build-config
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../../framework/core
version: link:../../../framework/core/dist
'@esengine/editor-core':
specifier: workspace:*
version: link:../../editor-core
@@ -1079,7 +1079,7 @@ importers:
version: link:../../tools/build-config
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../framework/core
version: link:../../framework/core/dist
'@esengine/engine-core':
specifier: workspace:*
version: link:../engine-core
@@ -1100,7 +1100,7 @@ importers:
version: link:../asset-system
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../framework/core
version: link:../../framework/core/dist
'@esengine/ecs-framework-math':
specifier: workspace:*
version: link:../../framework/math
@@ -1135,7 +1135,7 @@ importers:
dependencies:
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../framework/core
version: link:../../framework/core/dist
'@esengine/ecs-framework-math':
specifier: workspace:*
version: link:../../framework/math
@@ -1166,7 +1166,7 @@ importers:
version: link:../../tools/build-config
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../framework/core
version: link:../../framework/core/dist
'@esengine/engine-core':
specifier: workspace:*
version: link:../engine-core
@@ -1215,7 +1215,7 @@ importers:
version: link:../asset-system
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../framework/core
version: link:../../framework/core/dist
'@esengine/engine-core':
specifier: workspace:*
version: link:../engine-core
@@ -1251,7 +1251,7 @@ importers:
dependencies:
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../framework/core
version: link:../../framework/core/dist
'@esengine/platform-common':
specifier: workspace:*
version: link:../platform-common
@@ -1291,7 +1291,7 @@ importers:
version: link:../ecs-engine-bindgen
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../framework/core
version: link:../../framework/core/dist
'@esengine/ecs-framework-math':
specifier: workspace:*
version: link:../../framework/math
@@ -1329,7 +1329,7 @@ importers:
version: link:../../tools/build-config
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../framework/core
version: link:../../framework/core/dist
'@esengine/engine-core':
specifier: workspace:*
version: link:../engine-core
@@ -1357,7 +1357,7 @@ importers:
version: link:../../tools/build-config
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../core
version: link:../core/dist
'@types/jest':
specifier: ^29.5.14
version: 29.5.14
@@ -1391,7 +1391,7 @@ importers:
version: link:../../tools/build-config
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../core
version: link:../core/dist
'@types/node':
specifier: ^20.19.17
version: 20.19.27
@@ -1474,6 +1474,7 @@ importers:
typescript-eslint:
specifier: ^8.46.1
version: 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
publishDirectory: dist
packages/framework/fsm:
dependencies:
@@ -1489,7 +1490,7 @@ importers:
version: link:../../tools/build-config
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../core
version: link:../core/dist
'@types/node':
specifier: ^20.19.17
version: 20.19.27
@@ -1559,7 +1560,7 @@ importers:
version: link:../../tools/build-config
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../core
version: link:../core/dist
rimraf:
specifier: ^5.0.5
version: 5.0.10
@@ -1599,7 +1600,7 @@ importers:
version: link:../blueprint
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../core
version: link:../core/dist
'@esengine/ecs-framework-math':
specifier: workspace:*
version: link:../math
@@ -1624,7 +1625,7 @@ importers:
version: link:../../tools/build-config
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../core
version: link:../core/dist
'@types/node':
specifier: ^20.19.17
version: 20.19.27
@@ -1652,7 +1653,7 @@ importers:
version: link:../../tools/build-config
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../core
version: link:../core/dist
'@esengine/ecs-framework-math':
specifier: workspace:*
version: link:../math
@@ -1683,7 +1684,7 @@ importers:
version: link:../../tools/build-config
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../core
version: link:../core/dist
'@types/node':
specifier: ^20.19.17
version: 20.19.27
@@ -1705,6 +1706,9 @@ importers:
tsrpc:
specifier: ^3.4.15
version: 3.4.21
ws:
specifier: ^8.18.0
version: 8.18.3
devDependencies:
tsup:
specifier: ^8.5.1
@@ -1727,7 +1731,7 @@ importers:
version: link:../../tools/build-config
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../framework/core
version: link:../../framework/core/dist
'@esengine/ecs-framework-math':
specifier: workspace:*
version: link:../../framework/math
@@ -1763,7 +1767,7 @@ importers:
version: link:../../tools/build-config
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../framework/core
version: link:../../framework/core/dist
'@esengine/engine-core':
specifier: workspace:*
version: link:../../engine/engine-core
@@ -1784,7 +1788,7 @@ importers:
version: link:../../tools/build-config
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../framework/core
version: link:../../framework/core/dist
'@esengine/ecs-framework-math':
specifier: workspace:*
version: link:../../framework/math
@@ -1815,7 +1819,7 @@ importers:
version: link:../../tools/build-config
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../framework/core
version: link:../../framework/core/dist
'@types/node':
specifier: ^20.19.17
version: 20.19.27
@@ -1846,7 +1850,7 @@ importers:
version: link:../../engine/ecs-engine-bindgen
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../framework/core
version: link:../../framework/core/dist
'@esengine/engine-core':
specifier: workspace:*
version: link:../../engine/engine-core
@@ -1876,7 +1880,7 @@ importers:
version: link:../../engine/ecs-engine-bindgen
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../framework/core
version: link:../../framework/core/dist
'@esengine/engine-core':
specifier: workspace:*
version: link:../../engine/engine-core
@@ -1907,7 +1911,7 @@ importers:
version: link:../../engine/ecs-engine-bindgen
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../framework/core
version: link:../../framework/core/dist
'@esengine/ecs-framework-math':
specifier: workspace:*
version: link:../../framework/math
@@ -1937,7 +1941,7 @@ importers:
version: link:../../tools/build-config
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../framework/core
version: link:../../framework/core/dist
'@esengine/engine-core':
specifier: workspace:*
version: link:../../engine/engine-core
@@ -1971,7 +1975,7 @@ importers:
version: link:../../engine/ecs-engine-bindgen
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../framework/core
version: link:../../framework/core/dist
'@esengine/ecs-framework-math':
specifier: workspace:*
version: link:../../framework/math
@@ -2001,7 +2005,7 @@ importers:
dependencies:
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../framework/core
version: link:../../framework/core/dist
'@esengine/engine-core':
specifier: workspace:*
version: link:../../engine/engine-core
@@ -2097,7 +2101,7 @@ importers:
version: link:../../rendering/camera
'@esengine/ecs-framework':
specifier: workspace:*
version: link:../../framework/core
version: link:../../framework/core/dist
'@esengine/ecs-framework-math':
specifier: workspace:*
version: link:../../framework/math