跳转到内容

状态同步

@NetworkEntity 装饰器用于标记需要自动广播生成/销毁的组件。当包含此组件的实体被创建或销毁时,ECSRoom 会自动广播相应的消息给所有客户端。

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 消息:

// 服务端
const entity = scene.createEntity('Enemy');
entity.addComponent(new EnemyComponent()); // 自动广播 spawn
// 销毁时自动广播 despawn
entity.destroy(); // 自动广播 despawn
@NetworkEntity('Bullet', {
autoSpawn: true, // 自动广播生成(默认 true)
autoDespawn: false // 禁用自动广播销毁
})
class BulletComponent extends Component { }
选项类型默认值描述
autoSpawnbooleantrue添加组件时自动广播 spawn
autoDespawnbooleantrue销毁实体时自动广播 despawn

使用 @NetworkEntity 时,应在添加组件之前初始化数据:

// ✅ 正确:先初始化,再添加
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 时数据是默认值

使用 @NetworkEntity 后,GameRoom 变得更加简洁:

// 无需手动回调
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 中禁用自动网络实体功能:

class GameRoom extends ECSRoom {
constructor() {
super({
enableAutoNetworkEntity: false // 禁用自动广播
});
}
}

基于 @sync 装饰器的 ECS 组件状态同步。

import { Component, ECSComponent, sync } from '@esengine/ecs-framework';
@ECSComponent('Player')
class PlayerComponent extends Component {
@sync("string") name: string = "";
@sync("uint16") score: number = 0;
@sync("float32") x: number = 0;
@sync("float32") y: number = 0;
// 不带 @sync 的字段不会同步
localData: any;
}
import { ComponentSyncSystem } from '@esengine/network';
const syncSystem = new ComponentSyncSystem({}, true);
scene.addSystem(syncSystem);
// 编码所有实体(首次连接)
const fullData = syncSystem.encodeAllEntities(true);
sendToClient(fullData);
// 编码增量(只发送变更)
const deltaData = syncSystem.encodeDelta();
if (deltaData) {
broadcast(deltaData);
}
const syncSystem = new ComponentSyncSystem();
scene.addSystem(syncSystem);
// 注册组件类型
syncSystem.registerComponent(PlayerComponent);
// 监听同步事件
syncSystem.addSyncListener((event) => {
if (event.type === 'entitySpawned') {
console.log('New entity:', event.entityId);
}
});
// 应用状态
syncSystem.applySnapshot(data);
类型描述字节数
"boolean"布尔值1
"int8" / "uint8"8位整数1
"int16" / "uint16"16位整数2
"int32" / "uint32"32位整数4
"float32"32位浮点4
"float64"64位浮点8
"string"字符串变长

用于存储服务器状态快照并进行插值:

import { createSnapshotBuffer, type IStateSnapshot } from '@esengine/network';
const buffer = createSnapshotBuffer<IStateSnapshot>({
maxSnapshots: 30, // 最大快照数
interpolationDelay: 100 // 插值延迟 (ms)
});
// 添加快照
buffer.addSnapshot({
time: serverTime,
entities: states
});
// 获取插值状态
const interpolated = buffer.getInterpolatedState(clientTime);
属性类型描述
maxSnapshotsnumber缓冲区最大快照数
interpolationDelaynumber插值延迟(毫秒)

适用于简单的位置插值:

import { createTransformInterpolator } from '@esengine/network';
const interpolator = createTransformInterpolator();
// 添加状态
interpolator.addState(time, { x: 0, y: 0, rotation: 0 });
// 获取插值结果
const state = interpolator.getInterpolatedState(currentTime);

使用 Hermite 样条实现更平滑的插值,适合需要考虑速度的场景:

import { createHermiteTransformInterpolator } from '@esengine/network';
const interpolator = createHermiteTransformInterpolator({
bufferSize: 10
});
// 添加带速度的状态
interpolator.addState(time, {
x: 100,
y: 200,
rotation: 0,
vx: 5,
vy: 0
});
// 获取平滑的插值结果
const state = interpolator.getInterpolatedState(currentTime);
类型优点缺点适用场景
线性插值简单、计算快可能不平滑简单移动
Hermite 插值平滑、考虑速度计算量较大高速移动

实现客户端预测和服务器校正,减少输入延迟:

import { createClientPrediction } from '@esengine/network';
const prediction = createClientPrediction({
maxPredictedInputs: 60,
reconciliationThreshold: 0.1
});
// 预测输入
const seq = prediction.predict(inputState, currentState, (state, input) => {
// 应用输入到状态
return applyInput(state, input);
});
// 服务器校正
const corrected = prediction.reconcile(
serverState,
serverSeq,
(state, input) => applyInput(state, input)
);
属性类型描述
maxPredictedInputsnumber最大预测输入数
reconciliationThresholdnumber校正阈值
客户端 服务器
│ │
├─ 1. 本地预测输入 ──────────────────►
│ │
├─ 2. 发送输入到服务器 │
│ │
│ ├─ 3. 处理输入
│ │
◄──────────────────── 4. 返回权威状态
│ │
├─ 5. 校正本地状态 │
│ │
  • 低延迟网络(局域网):50-100ms
  • 普通网络:100-150ms
  • 高延迟网络:150-200ms
const buffer = createSnapshotBuffer({
interpolationDelay: 100 // 根据网络情况调整
});

对于本地玩家使用客户端预测:

// 本地玩家:预测 + 校正
if (identity.bIsLocalPlayer) {
const predicted = prediction.predict(input, state, applyInput);
// 使用预测状态渲染
}
// 远程玩家:纯插值
if (!identity.bIsLocalPlayer) {
const interpolated = interpolator.getInterpolatedState(time);
// 使用插值状态渲染
}
  1. 合理设置插值延迟:太小会导致抖动,太大会增加延迟感

  2. 客户端预测仅用于本地玩家:远程玩家使用插值

  3. 校正阈值:根据游戏精度需求设置合适的阈值

  4. 快照数量:保持足够的快照以应对网络抖动