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:
@@ -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);
|
||||
|
||||
147
packages/framework/core/src/ECS/Sync/NetworkEntityDecorator.ts
Normal file
147
packages/framework/core/src/ECS/Sync/NetworkEntityDecorator.ts
Normal 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;
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user