From bdbbf8a80a4be62537acf71cb5e46763faecc89a Mon Sep 17 00:00:00 2001 From: YHH <359807859@qq.com> Date: Tue, 30 Dec 2025 16:19:01 +0800 Subject: [PATCH] =?UTF-8?q?feat(ecs):=20=E6=B7=BB=E5=8A=A0=20@NetworkEntit?= =?UTF-8?q?y=20=E8=A3=85=E9=A5=B0=E5=99=A8=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=B9=BF=E6=92=AD=E5=AE=9E=E4=BD=93=E7=94=9F?= =?UTF-8?q?=E6=88=90/=E9=94=80=E6=AF=81=20(#395)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: add editor-app README with setup instructions * docs: add separate EN/CN editor setup guides * feat(ecs): add @NetworkEntity decorator for auto spawn/despawn broadcasting - Add @NetworkEntity decorator to mark components for automatic network broadcasting - ECSRoom now auto-broadcasts spawn on component:added event - ECSRoom now auto-broadcasts despawn on entity:destroyed event - Entity.destroy() emits entity:destroyed event via ECSEventType - Entity active state changes emit ENTITY_ENABLED/ENTITY_DISABLED events - Add enableAutoNetworkEntity config option to ECSRoom (default true) - Update documentation for both Chinese and English --- .../content/docs/en/modules/network/sync.md | 96 ++++++++++++ docs/src/content/docs/modules/network/sync.md | 96 ++++++++++++ packages/framework/behavior-tree/CHANGELOG.md | 7 + packages/framework/behavior-tree/package.json | 2 +- packages/framework/blueprint/CHANGELOG.md | 7 + packages/framework/blueprint/package.json | 2 +- packages/framework/core/CHANGELOG.md | 43 +++++ packages/framework/core/package.json | 15 +- packages/framework/core/src/ECS/Entity.ts | 44 +++--- .../src/ECS/Sync/NetworkEntityDecorator.ts | 147 ++++++++++++++++++ packages/framework/core/src/ECS/Sync/index.ts | 10 ++ packages/framework/fsm/CHANGELOG.md | 8 + packages/framework/fsm/package.json | 2 +- packages/framework/network/CHANGELOG.md | 8 + packages/framework/network/package.json | 2 +- packages/framework/pathfinding/CHANGELOG.md | 8 + packages/framework/pathfinding/package.json | 2 +- packages/framework/procgen/CHANGELOG.md | 8 + packages/framework/procgen/package.json | 2 +- packages/framework/server/CHANGELOG.md | 48 ++++++ packages/framework/server/package.json | 4 +- packages/framework/server/src/ecs/ECSRoom.ts | 60 +++++++ packages/framework/spatial/CHANGELOG.md | 8 + packages/framework/spatial/package.json | 2 +- packages/framework/timer/CHANGELOG.md | 8 + packages/framework/timer/package.json | 2 +- packages/framework/transaction/CHANGELOG.md | 7 + packages/framework/transaction/package.json | 2 +- packages/tools/demos/CHANGELOG.md | 11 ++ packages/tools/demos/package.json | 2 +- pnpm-lock.yaml | 66 ++++++++ 31 files changed, 692 insertions(+), 37 deletions(-) create mode 100644 packages/framework/core/src/ECS/Sync/NetworkEntityDecorator.ts diff --git a/docs/src/content/docs/en/modules/network/sync.md b/docs/src/content/docs/en/modules/network/sync.md index 833befc5..a5f66050 100644 --- a/docs/src/content/docs/en/modules/network/sync.md +++ b/docs/src/content/docs/en/modules/network/sync.md @@ -3,6 +3,102 @@ title: "State Sync" description: "Component sync, interpolation, prediction and snapshot buffers" --- +## @NetworkEntity Decorator + +The `@NetworkEntity` decorator marks components for automatic spawn/despawn broadcasting. When an entity containing this component is created or destroyed, ECSRoom automatically broadcasts the corresponding message to all clients. + +### Basic Usage + +```typescript +import { Component, ECSComponent, sync, NetworkEntity } from '@esengine/ecs-framework'; + +@ECSComponent('Enemy') +@NetworkEntity('Enemy') +class EnemyComponent extends Component { + @sync('float32') x: number = 0; + @sync('float32') y: number = 0; + @sync('uint16') health: number = 100; +} +``` + +When adding this component to an entity, ECSRoom automatically broadcasts the spawn message: + +```typescript +// Server-side +const entity = scene.createEntity('Enemy'); +entity.addComponent(new EnemyComponent()); // Auto-broadcasts spawn + +// Destroying auto-broadcasts despawn +entity.destroy(); // Auto-broadcasts despawn +``` + +### Configuration Options + +```typescript +@NetworkEntity('Bullet', { + autoSpawn: true, // Auto-broadcast spawn (default true) + autoDespawn: false // Disable auto-broadcast despawn +}) +class BulletComponent extends Component { } +``` + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `autoSpawn` | `boolean` | `true` | Auto-broadcast spawn when component is added | +| `autoDespawn` | `boolean` | `true` | Auto-broadcast despawn when entity is destroyed | + +### Initialization Order + +When using `@NetworkEntity`, initialize data **before** adding the component: + +```typescript +// ✅ Correct: Initialize first, then add +const comp = new PlayerComponent(); +comp.playerId = player.id; +comp.x = 100; +comp.y = 200; +entity.addComponent(comp); // Data is correct at spawn + +// ❌ Wrong: Add first, then initialize +const comp = entity.addComponent(new PlayerComponent()); +comp.playerId = player.id; // Data has default values at spawn +``` + +### Simplified GameRoom + +With `@NetworkEntity`, GameRoom becomes much cleaner: + +```typescript +// No manual callbacks needed +class GameRoom extends ECSRoom { + private setupSystems(): void { + // Enemy spawn system (auto-broadcasts spawn) + this.addSystem(new EnemySpawnSystem()); + + // Enemy AI system + const enemyAI = new EnemyAISystem(); + enemyAI.onDeath((enemy) => { + enemy.destroy(); // Auto-broadcasts despawn + }); + this.addSystem(enemyAI); + } +} +``` + +### ECSRoom Configuration + +You can disable the auto network entity feature in ECSRoom: + +```typescript +class GameRoom extends ECSRoom { + constructor() { + super({ + enableAutoNetworkEntity: false // Disable auto-broadcasting + }); + } +} +``` + ## Component Sync System ECS component state synchronization based on `@sync` decorator. diff --git a/docs/src/content/docs/modules/network/sync.md b/docs/src/content/docs/modules/network/sync.md index 83d9d0dc..3bea47d0 100644 --- a/docs/src/content/docs/modules/network/sync.md +++ b/docs/src/content/docs/modules/network/sync.md @@ -3,6 +3,102 @@ title: "状态同步" description: "组件同步、插值、预测和快照缓冲区" --- +## @NetworkEntity 装饰器 + +`@NetworkEntity` 装饰器用于标记需要自动广播生成/销毁的组件。当包含此组件的实体被创建或销毁时,ECSRoom 会自动广播相应的消息给所有客户端。 + +### 基本用法 + +```typescript +import { Component, ECSComponent, sync, NetworkEntity } from '@esengine/ecs-framework'; + +@ECSComponent('Enemy') +@NetworkEntity('Enemy') +class EnemyComponent extends Component { + @sync('float32') x: number = 0; + @sync('float32') y: number = 0; + @sync('uint16') health: number = 100; +} +``` + +当添加此组件到实体时,ECSRoom 会自动广播 spawn 消息: + +```typescript +// 服务端 +const entity = scene.createEntity('Enemy'); +entity.addComponent(new EnemyComponent()); // 自动广播 spawn + +// 销毁时自动广播 despawn +entity.destroy(); // 自动广播 despawn +``` + +### 配置选项 + +```typescript +@NetworkEntity('Bullet', { + autoSpawn: true, // 自动广播生成(默认 true) + autoDespawn: false // 禁用自动广播销毁 +}) +class BulletComponent extends Component { } +``` + +| 选项 | 类型 | 默认值 | 描述 | +|------|------|--------|------| +| `autoSpawn` | `boolean` | `true` | 添加组件时自动广播 spawn | +| `autoDespawn` | `boolean` | `true` | 销毁实体时自动广播 despawn | + +### 初始化顺序 + +使用 `@NetworkEntity` 时,应在添加组件**之前**初始化数据: + +```typescript +// ✅ 正确:先初始化,再添加 +const comp = new PlayerComponent(); +comp.playerId = player.id; +comp.x = 100; +comp.y = 200; +entity.addComponent(comp); // spawn 时数据已正确 + +// ❌ 错误:先添加,再初始化 +const comp = entity.addComponent(new PlayerComponent()); +comp.playerId = player.id; // spawn 时数据是默认值 +``` + +### 简化 GameRoom + +使用 `@NetworkEntity` 后,GameRoom 变得更加简洁: + +```typescript +// 无需手动回调 +class GameRoom extends ECSRoom { + private setupSystems(): void { + // 敌人生成系统(自动广播 spawn) + this.addSystem(new EnemySpawnSystem()); + + // 敌人 AI 系统 + const enemyAI = new EnemyAISystem(); + enemyAI.onDeath((enemy) => { + enemy.destroy(); // 自动广播 despawn + }); + this.addSystem(enemyAI); + } +} +``` + +### ECSRoom 配置 + +可以在 ECSRoom 中禁用自动网络实体功能: + +```typescript +class GameRoom extends ECSRoom { + constructor() { + super({ + enableAutoNetworkEntity: false // 禁用自动广播 + }); + } +} +``` + ## 组件同步系统 基于 `@sync` 装饰器的 ECS 组件状态同步。 diff --git a/packages/framework/behavior-tree/CHANGELOG.md b/packages/framework/behavior-tree/CHANGELOG.md index 2f3781fc..4a4350a8 100644 --- a/packages/framework/behavior-tree/CHANGELOG.md +++ b/packages/framework/behavior-tree/CHANGELOG.md @@ -1,5 +1,12 @@ # @esengine/behavior-tree +## 3.0.0 + +### Patch Changes + +- Updated dependencies []: + - @esengine/ecs-framework@2.6.0 + ## 2.0.1 ### Patch Changes diff --git a/packages/framework/behavior-tree/package.json b/packages/framework/behavior-tree/package.json index 896c7ba8..771baaf6 100644 --- a/packages/framework/behavior-tree/package.json +++ b/packages/framework/behavior-tree/package.json @@ -1,6 +1,6 @@ { "name": "@esengine/behavior-tree", - "version": "2.0.1", + "version": "3.0.0", "description": "ECS-based AI behavior tree system - works with any ECS framework (ESEngine, Cocos, Laya, etc.)", "main": "dist/index.js", "module": "dist/index.js", diff --git a/packages/framework/blueprint/CHANGELOG.md b/packages/framework/blueprint/CHANGELOG.md index 2c4c3539..83dbc9fd 100644 --- a/packages/framework/blueprint/CHANGELOG.md +++ b/packages/framework/blueprint/CHANGELOG.md @@ -1,5 +1,12 @@ # @esengine/blueprint +## 3.0.0 + +### Patch Changes + +- Updated dependencies []: + - @esengine/ecs-framework@2.6.0 + ## 2.0.1 ### Patch Changes diff --git a/packages/framework/blueprint/package.json b/packages/framework/blueprint/package.json index 93ab96b5..c276d691 100644 --- a/packages/framework/blueprint/package.json +++ b/packages/framework/blueprint/package.json @@ -1,6 +1,6 @@ { "name": "@esengine/blueprint", - "version": "2.0.1", + "version": "3.0.0", "description": "Visual scripting system - works with any ECS framework (ESEngine, Cocos, Laya, etc.)", "main": "dist/index.js", "module": "dist/index.js", diff --git a/packages/framework/core/CHANGELOG.md b/packages/framework/core/CHANGELOG.md index 7ce24bee..b65180cd 100644 --- a/packages/framework/core/CHANGELOG.md +++ b/packages/framework/core/CHANGELOG.md @@ -1,5 +1,48 @@ # @esengine/ecs-framework +## 2.6.0 + +### Minor Changes + +- feat(ecs): 添加 @NetworkEntity 装饰器,支持自动广播实体生成/销毁 + + ### 新功能 + + **@NetworkEntity 装饰器** + - 标记组件为网络实体,自动广播 spawn/despawn 消息 + - 支持 `autoSpawn` 和 `autoDespawn` 配置选项 + - 通过事件系统(`ECSEventType.COMPONENT_ADDED` / `ECSEventType.ENTITY_DESTROYED`)实现 + + **ECSRoom 增强** + - 新增 `enableAutoNetworkEntity` 配置选项(默认启用) + - 自动监听组件添加和实体销毁事件 + - 简化 GameRoom 实现,无需手动回调 + + ### 改进 + + **Entity 事件** + - `Entity.destroy()` 现在发出 `entity:destroyed` 事件 + - `Entity.active` 变化时发出 `entity:enabled` / `entity:disabled` 事件 + - 使用 `ECSEventType` 常量替代硬编码字符串 + + ### 使用示例 + + ```typescript + import { Component, ECSComponent, sync, NetworkEntity } from '@esengine/ecs-framework'; + + @ECSComponent('Enemy') + @NetworkEntity('Enemy') + class EnemyComponent extends Component { + @sync('float32') x: number = 0; + @sync('float32') y: number = 0; + } + + // 服务端 + const entity = scene.createEntity('Enemy'); + entity.addComponent(new EnemyComponent()); // 自动广播 spawn + entity.destroy(); // 自动广播 despawn + ``` + ## 2.5.1 ### Patch Changes diff --git a/packages/framework/core/package.json b/packages/framework/core/package.json index a917dfe5..6b9c3782 100644 --- a/packages/framework/core/package.json +++ b/packages/framework/core/package.json @@ -1,16 +1,18 @@ { "name": "@esengine/ecs-framework", - "version": "2.5.1", + "version": "2.6.0", "description": "用于Laya、Cocos Creator等JavaScript游戏引擎的高性能ECS框架", "main": "dist/index.cjs", "module": "dist/index.mjs", "types": "dist/index.d.ts", "unpkg": "dist/index.umd.js", + "sideEffects": false, "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.mjs", - "require": "./dist/index.cjs" + "require": "./dist/index.cjs", + "source": "./src/index.ts" } }, "files": [ @@ -50,23 +52,24 @@ "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", "@babel/plugin-transform-optional-chaining": "^7.27.1", "@babel/preset-env": "^7.28.3", + "@eslint/js": "^9.37.0", + "@jest/globals": "^29.7.0", "@rollup/plugin-babel": "^6.0.4", "@rollup/plugin-commonjs": "^28.0.3", "@rollup/plugin-node-resolve": "^16.0.1", "@rollup/plugin-terser": "^0.4.4", - "@jest/globals": "^29.7.0", "@types/jest": "^29.5.14", "@types/node": "^20.19.17", - "@eslint/js": "^9.37.0", "eslint": "^9.37.0", - "typescript-eslint": "^8.46.1", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "rimraf": "^5.0.0", "rollup": "^4.42.0", "rollup-plugin-dts": "^6.2.1", + "rollup-plugin-sourcemaps": "^0.6.3", "ts-jest": "^29.4.0", - "typescript": "^5.8.3" + "typescript": "^5.8.3", + "typescript-eslint": "^8.46.1" }, "publishConfig": { "access": "public", diff --git a/packages/framework/core/src/ECS/Entity.ts b/packages/framework/core/src/ECS/Entity.ts index de616bfb..cb322f02 100644 --- a/packages/framework/core/src/ECS/Entity.ts +++ b/packages/framework/core/src/ECS/Entity.ts @@ -7,14 +7,7 @@ import { getComponentInstanceTypeName, getComponentTypeName } from './Decorators import { generateGUID } from '../Utils/GUID'; import type { IScene } from './IScene'; import { EntityHandle, NULL_HANDLE } from './Core/EntityHandle'; - -/** - * @zh 组件活跃状态变化接口 - * @en Interface for component active state change - */ -interface IActiveChangeable { - onActiveChanged(): void; -} +import { ECSEventType } from './CoreEvents'; /** * @zh 比较两个实体的优先级 @@ -482,7 +475,7 @@ export class Entity { } if (this.scene.eventSystem) { - this.scene.eventSystem.emitSync('component:added', { + this.scene.eventSystem.emitSync(ECSEventType.COMPONENT_ADDED, { timestamp: Date.now(), source: 'Entity', entityId: this.id, @@ -639,7 +632,7 @@ export class Entity { component.entityId = null; if (this.scene?.eventSystem) { - this.scene.eventSystem.emitSync('component:removed', { + this.scene.eventSystem.emitSync(ECSEventType.COMPONENT_REMOVED, { timestamp: Date.now(), source: 'Entity', entityId: this.id, @@ -770,19 +763,23 @@ export class Entity { } /** - * 活跃状态改变时的回调 + * @zh 活跃状态改变时的回调 + * @en Callback when active state changes + * + * @zh 通过事件系统发出 ENTITY_ENABLED 或 ENTITY_DISABLED 事件, + * 组件可以通过监听这些事件来响应实体状态变化。 + * @en Emits ENTITY_ENABLED or ENTITY_DISABLED event through the event system. + * Components can listen to these events to respond to entity state changes. */ private onActiveChanged(): void { - for (const component of this.components) { - if ('onActiveChanged' in component && typeof component.onActiveChanged === 'function') { - (component as IActiveChangeable).onActiveChanged(); - } - } + if (this.scene?.eventSystem) { + const eventType = this._active + ? ECSEventType.ENTITY_ENABLED + : ECSEventType.ENTITY_DISABLED; - if (this.scene && this.scene.eventSystem) { - this.scene.eventSystem.emitSync('entity:activeChanged', { + this.scene.eventSystem.emitSync(eventType, { entity: this, - active: this._active + scene: this.scene, }); } } @@ -801,6 +798,15 @@ export class Entity { this._isDestroyed = true; + // 在清理之前发出销毁事件(组件仍然可访问) + if (this.scene?.eventSystem) { + this.scene.eventSystem.emitSync(ECSEventType.ENTITY_DESTROYED, { + entity: this, + entityId: this.id, + scene: this.scene, + }); + } + if (this.scene && this.scene.referenceTracker) { this.scene.referenceTracker.clearReferencesTo(this.id); this.scene.referenceTracker.unregisterEntityScene(this.id); diff --git a/packages/framework/core/src/ECS/Sync/NetworkEntityDecorator.ts b/packages/framework/core/src/ECS/Sync/NetworkEntityDecorator.ts new file mode 100644 index 00000000..38d8d7ce --- /dev/null +++ b/packages/framework/core/src/ECS/Sync/NetworkEntityDecorator.ts @@ -0,0 +1,147 @@ +/** + * @zh 网络实体装饰器 + * @en Network entity decorator + * + * @zh 提供 @NetworkEntity 装饰器,用于标记需要自动广播生成/销毁的组件 + * @en Provides @NetworkEntity decorator to mark components for automatic spawn/despawn broadcasting + */ + +/** + * @zh 网络实体元数据的 Symbol 键 + * @en Symbol key for network entity metadata + */ +export const NETWORK_ENTITY_METADATA = Symbol('NetworkEntityMetadata'); + +/** + * @zh 网络实体元数据 + * @en Network entity metadata + */ +export interface NetworkEntityMetadata { + /** + * @zh 预制体类型名称(用于客户端重建实体) + * @en Prefab type name (used by client to reconstruct entity) + */ + prefabType: string; + + /** + * @zh 是否自动广播生成 + * @en Whether to auto-broadcast spawn + * @default true + */ + autoSpawn: boolean; + + /** + * @zh 是否自动广播销毁 + * @en Whether to auto-broadcast despawn + * @default true + */ + autoDespawn: boolean; +} + +/** + * @zh 网络实体装饰器配置选项 + * @en Network entity decorator options + */ +export interface NetworkEntityOptions { + /** + * @zh 是否自动广播生成 + * @en Whether to auto-broadcast spawn + * @default true + */ + autoSpawn?: boolean; + + /** + * @zh 是否自动广播销毁 + * @en Whether to auto-broadcast despawn + * @default true + */ + autoDespawn?: boolean; +} + +/** + * @zh 网络实体装饰器 + * @en Network entity decorator + * + * @zh 标记组件类为网络实体。当包含此组件的实体被创建或销毁时, + * ECSRoom 会自动广播相应的 spawn/despawn 消息给所有客户端。 + * @en Marks a component class as a network entity. When an entity containing + * this component is created or destroyed, ECSRoom will automatically broadcast + * the corresponding spawn/despawn messages to all clients. + * + * @param prefabType - @zh 预制体类型名称 @en Prefab type name + * @param options - @zh 可选配置 @en Optional configuration + * + * @example + * ```typescript + * import { Component, ECSComponent, NetworkEntity, sync } from '@esengine/ecs-framework'; + * + * @ECSComponent('Enemy') + * @NetworkEntity('Enemy') + * class EnemyComponent extends Component { + * @sync('float32') x: number = 0; + * @sync('float32') y: number = 0; + * @sync('uint16') health: number = 100; + * } + * + * // 当添加此组件到实体时,ECSRoom 会自动广播 spawn + * const enemy = scene.createEntity('Enemy'); + * enemy.addComponent(new EnemyComponent()); // 自动广播给所有客户端 + * + * // 当实体销毁时,自动广播 despawn + * enemy.destroy(); // 自动广播给所有客户端 + * ``` + * + * @example + * ```typescript + * // 只自动广播生成,销毁由手动控制 + * @ECSComponent('Bullet') + * @NetworkEntity('Bullet', { autoDespawn: false }) + * class BulletComponent extends Component { + * @sync('float32') x: number = 0; + * @sync('float32') y: number = 0; + * } + * ``` + */ +export function NetworkEntity(prefabType: string, options?: NetworkEntityOptions) { + return function any>(target: T): T { + const metadata: NetworkEntityMetadata = { + prefabType, + autoSpawn: options?.autoSpawn ?? true, + autoDespawn: options?.autoDespawn ?? true, + }; + + (target as any)[NETWORK_ENTITY_METADATA] = metadata; + + return target; + }; +} + +/** + * @zh 获取组件类的网络实体元数据 + * @en Get network entity metadata for a component class + * + * @param componentClass - @zh 组件类或组件实例 @en Component class or instance + * @returns @zh 网络实体元数据,如果不存在则返回 null @en Network entity metadata, or null if not exists + */ +export function getNetworkEntityMetadata(componentClass: any): NetworkEntityMetadata | null { + if (!componentClass) { + return null; + } + + const constructor = typeof componentClass === 'function' + ? componentClass + : componentClass.constructor; + + return constructor[NETWORK_ENTITY_METADATA] || null; +} + +/** + * @zh 检查组件是否标记为网络实体 + * @en Check if a component is marked as a network entity + * + * @param component - @zh 组件类或组件实例 @en Component class or instance + * @returns @zh 如果是网络实体返回 true @en Returns true if is a network entity + */ +export function isNetworkEntity(component: any): boolean { + return getNetworkEntityMetadata(component) !== null; +} diff --git a/packages/framework/core/src/ECS/Sync/index.ts b/packages/framework/core/src/ECS/Sync/index.ts index 2d8de88f..b6727259 100644 --- a/packages/framework/core/src/ECS/Sync/index.ts +++ b/packages/framework/core/src/ECS/Sync/index.ts @@ -51,5 +51,15 @@ export { hasChanges } from './decorators'; +// Network Entity Decorator +export { + NetworkEntity, + getNetworkEntityMetadata, + isNetworkEntity, + NETWORK_ENTITY_METADATA, + type NetworkEntityMetadata, + type NetworkEntityOptions +} from './NetworkEntityDecorator'; + // Encoding export * from './encoding'; diff --git a/packages/framework/fsm/CHANGELOG.md b/packages/framework/fsm/CHANGELOG.md index e8fa01d6..a9b88d74 100644 --- a/packages/framework/fsm/CHANGELOG.md +++ b/packages/framework/fsm/CHANGELOG.md @@ -1,5 +1,13 @@ # @esengine/fsm +## 3.0.0 + +### Patch Changes + +- Updated dependencies []: + - @esengine/ecs-framework@2.6.0 + - @esengine/blueprint@3.0.0 + ## 2.0.1 ### Patch Changes diff --git a/packages/framework/fsm/package.json b/packages/framework/fsm/package.json index dec15598..c6a7e78d 100644 --- a/packages/framework/fsm/package.json +++ b/packages/framework/fsm/package.json @@ -1,6 +1,6 @@ { "name": "@esengine/fsm", - "version": "2.0.1", + "version": "3.0.0", "description": "Finite State Machine for ECS Framework / ECS 框架的有限状态机", "type": "module", "main": "./dist/index.js", diff --git a/packages/framework/network/CHANGELOG.md b/packages/framework/network/CHANGELOG.md index b010182a..58a70a85 100644 --- a/packages/framework/network/CHANGELOG.md +++ b/packages/framework/network/CHANGELOG.md @@ -1,5 +1,13 @@ # @esengine/network +## 4.0.0 + +### Patch Changes + +- Updated dependencies []: + - @esengine/ecs-framework@2.6.0 + - @esengine/blueprint@3.0.0 + ## 3.0.1 ### Patch Changes diff --git a/packages/framework/network/package.json b/packages/framework/network/package.json index 7e792fdc..ba23b42f 100644 --- a/packages/framework/network/package.json +++ b/packages/framework/network/package.json @@ -1,6 +1,6 @@ { "name": "@esengine/network", - "version": "3.0.1", + "version": "4.0.0", "description": "Network synchronization for multiplayer games", "esengine": { "plugin": true, diff --git a/packages/framework/pathfinding/CHANGELOG.md b/packages/framework/pathfinding/CHANGELOG.md index 0fc9f963..8d46ee4c 100644 --- a/packages/framework/pathfinding/CHANGELOG.md +++ b/packages/framework/pathfinding/CHANGELOG.md @@ -1,5 +1,13 @@ # @esengine/pathfinding +## 3.0.0 + +### Patch Changes + +- Updated dependencies []: + - @esengine/ecs-framework@2.6.0 + - @esengine/blueprint@3.0.0 + ## 2.0.1 ### Patch Changes diff --git a/packages/framework/pathfinding/package.json b/packages/framework/pathfinding/package.json index c1a156a2..67cf0cbf 100644 --- a/packages/framework/pathfinding/package.json +++ b/packages/framework/pathfinding/package.json @@ -1,6 +1,6 @@ { "name": "@esengine/pathfinding", - "version": "2.0.1", + "version": "3.0.0", "description": "寻路系统 | Pathfinding System - A*, Grid, NavMesh", "type": "module", "main": "./dist/index.js", diff --git a/packages/framework/procgen/CHANGELOG.md b/packages/framework/procgen/CHANGELOG.md index 09f5f17f..b3c925a4 100644 --- a/packages/framework/procgen/CHANGELOG.md +++ b/packages/framework/procgen/CHANGELOG.md @@ -1,5 +1,13 @@ # @esengine/procgen +## 3.0.0 + +### Patch Changes + +- Updated dependencies []: + - @esengine/ecs-framework@2.6.0 + - @esengine/blueprint@3.0.0 + ## 2.0.1 ### Patch Changes diff --git a/packages/framework/procgen/package.json b/packages/framework/procgen/package.json index db72746f..66bff2f0 100644 --- a/packages/framework/procgen/package.json +++ b/packages/framework/procgen/package.json @@ -1,6 +1,6 @@ { "name": "@esengine/procgen", - "version": "2.0.1", + "version": "3.0.0", "description": "Procedural generation tools for ECS Framework / ECS 框架的程序化生成工具", "type": "module", "main": "./dist/index.js", diff --git a/packages/framework/server/CHANGELOG.md b/packages/framework/server/CHANGELOG.md index 4d4c3aee..aadb083d 100644 --- a/packages/framework/server/CHANGELOG.md +++ b/packages/framework/server/CHANGELOG.md @@ -1,5 +1,53 @@ # @esengine/server +## 3.0.0 + +### Minor Changes + +- feat(ecs): 添加 @NetworkEntity 装饰器,支持自动广播实体生成/销毁 + + ### 新功能 + + **@NetworkEntity 装饰器** + - 标记组件为网络实体,自动广播 spawn/despawn 消息 + - 支持 `autoSpawn` 和 `autoDespawn` 配置选项 + - 通过事件系统(`ECSEventType.COMPONENT_ADDED` / `ECSEventType.ENTITY_DESTROYED`)实现 + + **ECSRoom 增强** + - 新增 `enableAutoNetworkEntity` 配置选项(默认启用) + - 自动监听组件添加和实体销毁事件 + - 简化 GameRoom 实现,无需手动回调 + + ### 改进 + + **Entity 事件** + - `Entity.destroy()` 现在发出 `entity:destroyed` 事件 + - `Entity.active` 变化时发出 `entity:enabled` / `entity:disabled` 事件 + - 使用 `ECSEventType` 常量替代硬编码字符串 + + ### 使用示例 + + ```typescript + import { Component, ECSComponent, sync, NetworkEntity } from '@esengine/ecs-framework'; + + @ECSComponent('Enemy') + @NetworkEntity('Enemy') + class EnemyComponent extends Component { + @sync('float32') x: number = 0; + @sync('float32') y: number = 0; + } + + // 服务端 + const entity = scene.createEntity('Enemy'); + entity.addComponent(new EnemyComponent()); // 自动广播 spawn + entity.destroy(); // 自动广播 despawn + ``` + +### Patch Changes + +- Updated dependencies []: + - @esengine/ecs-framework@2.6.0 + ## 2.0.0 ### Minor Changes diff --git a/packages/framework/server/package.json b/packages/framework/server/package.json index 41e82244..dc67fc56 100644 --- a/packages/framework/server/package.json +++ b/packages/framework/server/package.json @@ -1,6 +1,6 @@ { "name": "@esengine/server", - "version": "2.0.0", + "version": "3.0.0", "description": "Game server framework for ESEngine with file-based routing", "type": "module", "main": "./dist/index.js", @@ -51,7 +51,7 @@ "peerDependencies": { "ws": ">=8.0.0", "jsonwebtoken": ">=9.0.0", - "@esengine/ecs-framework": ">=2.5.1" + "@esengine/ecs-framework": ">=2.6.0" }, "peerDependenciesMeta": { "jsonwebtoken": { diff --git a/packages/framework/server/src/ecs/ECSRoom.ts b/packages/framework/server/src/ecs/ECSRoom.ts index 78406bb6..b556824c 100644 --- a/packages/framework/server/src/ecs/ECSRoom.ts +++ b/packages/framework/server/src/ecs/ECSRoom.ts @@ -20,6 +20,11 @@ import { encodeSpawn, encodeDespawn, initChangeTracker, + // Network Entity + NETWORK_ENTITY_METADATA, + type NetworkEntityMetadata, + // Events + ECSEventType, } from '@esengine/ecs-framework'; import { Room, type RoomOptions } from '../room/Room.js'; @@ -45,11 +50,19 @@ export interface ECSRoomConfig { * @en Whether to enable delta sync */ enableDeltaSync: boolean; + + /** + * @zh 是否启用自动网络实体广播(基于 @NetworkEntity 装饰器) + * @en Whether to enable automatic network entity broadcasting (based on @NetworkEntity decorator) + * @default true + */ + enableAutoNetworkEntity: boolean; } const DEFAULT_ECS_CONFIG: ECSRoomConfig = { syncInterval: 50, // 20 Hz enableDeltaSync: true, + enableAutoNetworkEntity: true, }; /** @@ -116,6 +129,12 @@ export abstract class ECSRoom = new Map(); + /** + * @zh 网络实体映射(实体 ID -> prefabType) + * @en Network entity mapping (entity ID -> prefabType) + */ + private readonly _networkEntities: Map = new Map(); + /** * @zh 上次同步时间 * @en Last sync time @@ -131,6 +150,47 @@ export abstract class ECSRoom { + const { entity, component } = event; + const metadata: NetworkEntityMetadata | undefined = + (component.constructor as any)[NETWORK_ENTITY_METADATA]; + + if (metadata?.autoSpawn) { + // 避免重复广播同一实体 + if (!this._networkEntities.has(entity.id)) { + this._networkEntities.set(entity.id, metadata.prefabType); + this.broadcastSpawn(entity, metadata.prefabType); + } + } + + // 记录需要自动 despawn 的实体 + if (metadata?.autoDespawn && !this._networkEntities.has(entity.id)) { + this._networkEntities.set(entity.id, metadata.prefabType); + } + }); + + // 监听实体销毁事件,自动广播 despawn + this.scene.eventSystem.on(ECSEventType.ENTITY_DESTROYED, (event: any) => { + const { entityId } = event; + if (this._networkEntities.has(entityId)) { + const despawnData = encodeDespawn(entityId); + this.broadcastBinary(despawnData); + this._networkEntities.delete(entityId); + } + }); } // ========================================================================= diff --git a/packages/framework/spatial/CHANGELOG.md b/packages/framework/spatial/CHANGELOG.md index 108ed53c..f9ef7e9d 100644 --- a/packages/framework/spatial/CHANGELOG.md +++ b/packages/framework/spatial/CHANGELOG.md @@ -1,5 +1,13 @@ # @esengine/spatial +## 3.0.0 + +### Patch Changes + +- Updated dependencies []: + - @esengine/ecs-framework@2.6.0 + - @esengine/blueprint@3.0.0 + ## 2.0.1 ### Patch Changes diff --git a/packages/framework/spatial/package.json b/packages/framework/spatial/package.json index 58b5c91e..3224485c 100644 --- a/packages/framework/spatial/package.json +++ b/packages/framework/spatial/package.json @@ -1,6 +1,6 @@ { "name": "@esengine/spatial", - "version": "2.0.1", + "version": "3.0.0", "description": "Spatial query and indexing system for ECS Framework / ECS 框架的空间查询和索引系统", "type": "module", "main": "./dist/index.js", diff --git a/packages/framework/timer/CHANGELOG.md b/packages/framework/timer/CHANGELOG.md index 4994f670..57b270ca 100644 --- a/packages/framework/timer/CHANGELOG.md +++ b/packages/framework/timer/CHANGELOG.md @@ -1,5 +1,13 @@ # @esengine/timer +## 3.0.0 + +### Patch Changes + +- Updated dependencies []: + - @esengine/ecs-framework@2.6.0 + - @esengine/blueprint@3.0.0 + ## 2.0.1 ### Patch Changes diff --git a/packages/framework/timer/package.json b/packages/framework/timer/package.json index 80b4d362..01d04b5e 100644 --- a/packages/framework/timer/package.json +++ b/packages/framework/timer/package.json @@ -1,6 +1,6 @@ { "name": "@esengine/timer", - "version": "2.0.1", + "version": "3.0.0", "description": "Timer and cooldown system for ECS Framework / ECS 框架的定时器和冷却系统", "type": "module", "main": "./dist/index.js", diff --git a/packages/framework/transaction/CHANGELOG.md b/packages/framework/transaction/CHANGELOG.md index 325c7a86..f731f867 100644 --- a/packages/framework/transaction/CHANGELOG.md +++ b/packages/framework/transaction/CHANGELOG.md @@ -1,5 +1,12 @@ # @esengine/transaction +## 2.0.4 + +### Patch Changes + +- Updated dependencies []: + - @esengine/server@3.0.0 + ## 2.0.3 ### Patch Changes diff --git a/packages/framework/transaction/package.json b/packages/framework/transaction/package.json index a606dbfe..0f5cefd8 100644 --- a/packages/framework/transaction/package.json +++ b/packages/framework/transaction/package.json @@ -1,6 +1,6 @@ { "name": "@esengine/transaction", - "version": "2.0.3", + "version": "2.0.4", "description": "Game transaction system with distributed support | 游戏事务系统,支持分布式事务", "type": "module", "main": "./dist/index.js", diff --git a/packages/tools/demos/CHANGELOG.md b/packages/tools/demos/CHANGELOG.md index 1be4aba1..715af51f 100644 --- a/packages/tools/demos/CHANGELOG.md +++ b/packages/tools/demos/CHANGELOG.md @@ -1,5 +1,16 @@ # @esengine/demos +## 1.0.7 + +### Patch Changes + +- Updated dependencies []: + - @esengine/fsm@3.0.0 + - @esengine/pathfinding@3.0.0 + - @esengine/procgen@3.0.0 + - @esengine/spatial@3.0.0 + - @esengine/timer@3.0.0 + ## 1.0.6 ### Patch Changes diff --git a/packages/tools/demos/package.json b/packages/tools/demos/package.json index 093cd24e..7859b01b 100644 --- a/packages/tools/demos/package.json +++ b/packages/tools/demos/package.json @@ -1,6 +1,6 @@ { "name": "@esengine/demos", - "version": "1.0.6", + "version": "1.0.7", "private": true, "description": "Demo tests for ESEngine modules documentation", "type": "module", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b3182879..8223a265 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1492,6 +1492,9 @@ importers: rollup-plugin-dts: specifier: ^6.2.1 version: 6.3.0(rollup@4.54.0)(typescript@5.9.3) + rollup-plugin-sourcemaps: + specifier: ^0.6.3 + version: 0.6.3(@types/node@20.19.27)(rollup@4.54.0) ts-jest: specifier: ^29.4.0 version: 29.4.6(@babel/core@7.28.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.5))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.27)(ts-node@10.9.2(@swc/core@1.15.7(@swc/helpers@0.5.18))(@swc/wasm@1.15.7)(@types/node@20.19.27)(typescript@5.9.3)))(typescript@5.9.3) @@ -4490,6 +4493,12 @@ packages: rollup: optional: true + '@rollup/pluginutils@3.1.0': + resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==} + engines: {node: '>= 8.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0 + '@rollup/pluginutils@5.3.0': resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} engines: {node: '>=14.0.0'} @@ -5156,6 +5165,9 @@ packages: '@types/estree-jsx@1.0.5': resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + '@types/estree@0.0.39': + resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -5797,6 +5809,11 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + atob@2.1.2: + resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==} + engines: {node: '>= 4.5.0'} + hasBin: true + axios@1.13.2: resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==} @@ -6440,6 +6457,10 @@ packages: decode-named-character-reference@1.2.0: resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} + decode-uri-component@0.2.2: + resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} + engines: {node: '>=0.10'} + dedent@1.5.3: resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} peerDependencies: @@ -6844,6 +6865,9 @@ packages: estree-util-visit@2.0.0: resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} + estree-walker@1.0.1: + resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==} + estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} @@ -9850,6 +9874,16 @@ packages: rollup: ^3.29.4 || ^4 typescript: ^4.5 || ^5.0 + rollup-plugin-sourcemaps@0.6.3: + resolution: {integrity: sha512-paFu+nT1xvuO1tPFYXGe+XnQvg4Hjqv/eIhG8i5EspfYYPBKL57X7iVbfv55aNVASg3dzWvES9dmWsL2KhfByw==} + engines: {node: '>=10.0.0'} + peerDependencies: + '@types/node': '>=10.0.0' + rollup: '>=0.31.2' + peerDependenciesMeta: + '@types/node': + optional: true + rollup@4.54.0: resolution: {integrity: sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -10049,6 +10083,10 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map-resolve@0.6.0: + resolution: {integrity: sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==} + deprecated: See https://github.com/lydell/source-map-resolve#deprecated + source-map-support@0.5.13: resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} @@ -14134,6 +14172,13 @@ snapshots: optionalDependencies: rollup: 4.54.0 + '@rollup/pluginutils@3.1.0(rollup@4.54.0)': + dependencies: + '@types/estree': 0.0.39 + estree-walker: 1.0.1 + picomatch: 2.3.1 + rollup: 4.54.0 + '@rollup/pluginutils@5.3.0(rollup@4.54.0)': dependencies: '@types/estree': 1.0.8 @@ -14884,6 +14929,8 @@ snapshots: dependencies: '@types/estree': 1.0.8 + '@types/estree@0.0.39': {} + '@types/estree@1.0.8': {} '@types/express-serve-static-core@5.1.0': @@ -15751,6 +15798,8 @@ snapshots: asynckit@0.4.0: {} + atob@2.1.2: {} + axios@1.13.2: dependencies: follow-redirects: 1.15.11 @@ -16430,6 +16479,8 @@ snapshots: dependencies: character-entities: 2.0.2 + decode-uri-component@0.2.2: {} + dedent@1.5.3: {} dedent@1.7.1: {} @@ -16880,6 +16931,8 @@ snapshots: '@types/estree-jsx': 1.0.5 '@types/unist': 3.0.3 + estree-walker@1.0.1: {} + estree-walker@2.0.2: {} estree-walker@3.0.3: @@ -20702,6 +20755,14 @@ snapshots: optionalDependencies: '@babel/code-frame': 7.27.1 + rollup-plugin-sourcemaps@0.6.3(@types/node@20.19.27)(rollup@4.54.0): + dependencies: + '@rollup/pluginutils': 3.1.0(rollup@4.54.0) + rollup: 4.54.0 + source-map-resolve: 0.6.0 + optionalDependencies: + '@types/node': 20.19.27 + rollup@4.54.0: dependencies: '@types/estree': 1.0.8 @@ -20996,6 +21057,11 @@ snapshots: source-map-js@1.2.1: {} + source-map-resolve@0.6.0: + dependencies: + atob: 2.1.2 + decode-uri-component: 0.2.2 + source-map-support@0.5.13: dependencies: buffer-from: 1.1.2