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

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

View File

@@ -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 组件状态同步。

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

View File

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

View File

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

66
pnpm-lock.yaml generated
View File

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