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:
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
|
||||
Reference in New Issue
Block a user