feat(ecs): 添加 @NetworkEntity 装饰器,支持自动广播实体生成/销毁 (#395)

* 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
This commit is contained in:
YHH
2025-12-30 16:19:01 +08:00
committed by GitHub
parent b28169b186
commit bdbbf8a80a
31 changed files with 692 additions and 37 deletions

View File

@@ -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

View File

@@ -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",

View File

@@ -1,5 +1,12 @@
# @esengine/blueprint
## 3.0.0
### Patch Changes
- Updated dependencies []:
- @esengine/ecs-framework@2.6.0
## 2.0.1
### Patch Changes

View File

@@ -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",

View File

@@ -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

View File

@@ -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",

View File

@@ -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);

View File

@@ -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 <T extends new (...args: any[]) => 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;
}

View File

@@ -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';

View File

@@ -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

View File

@@ -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",

View File

@@ -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

View File

@@ -1,6 +1,6 @@
{
"name": "@esengine/network",
"version": "3.0.1",
"version": "4.0.0",
"description": "Network synchronization for multiplayer games",
"esengine": {
"plugin": true,

View File

@@ -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

View File

@@ -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",

View File

@@ -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

View File

@@ -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",

View File

@@ -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

View File

@@ -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": {

View File

@@ -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<TState = any, TPlayerData = Record<string, unknown
*/
private readonly _playerEntities: Map<string, Entity> = new Map();
/**
* @zh 网络实体映射(实体 ID -> prefabType
* @en Network entity mapping (entity ID -> prefabType)
*/
private readonly _networkEntities: Map<number, string> = new Map();
/**
* @zh 上次同步时间
* @en Last sync time
@@ -131,6 +150,47 @@ export abstract class ECSRoom<TState = any, TPlayerData = Record<string, unknown
this.scene = this.world.createScene('game');
this.world.setSceneActive('game', true);
this.world.start();
// 设置自动网络实体广播
if (this.ecsConfig.enableAutoNetworkEntity) {
this._setupAutoNetworkEntity();
}
}
/**
* @zh 设置自动网络实体广播
* @en Setup automatic network entity broadcasting
*/
private _setupAutoNetworkEntity(): void {
// 监听组件添加事件,自动广播 spawn
this.scene.eventSystem.on(ECSEventType.COMPONENT_ADDED, (event: any) => {
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);
}
});
}
// =========================================================================

View File

@@ -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

View File

@@ -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",

View File

@@ -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

View File

@@ -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",

View File

@@ -1,5 +1,12 @@
# @esengine/transaction
## 2.0.4
### Patch Changes
- Updated dependencies []:
- @esengine/server@3.0.0
## 2.0.3
### Patch Changes

View File

@@ -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",