跳转到内容

状态增量压缩

状态增量压缩通过只发送变化的字段来减少网络带宽。对于频繁同步的游戏状态,这可以显著降低数据传输量。

StateDeltaCompressor 类用于压缩和解压状态增量。

import { createStateDeltaCompressor, type SyncData } from '@esengine/network';
// 创建压缩器
const compressor = createStateDeltaCompressor({
positionThreshold: 0.01, // 位置变化阈值
rotationThreshold: 0.001, // 旋转变化阈值(弧度)
velocityThreshold: 0.1, // 速度变化阈值
fullSnapshotInterval: 60, // 完整快照间隔(帧数)
});
// 压缩同步数据
const syncData: SyncData = {
frame: 100,
timestamp: Date.now(),
entities: [
{ netId: 1, pos: { x: 100, y: 200 }, rot: 0 },
{ netId: 2, pos: { x: 300, y: 400 }, rot: 1.5 },
],
};
const deltaData = compressor.compress(syncData);
// deltaData 只包含变化的字段
// 解压增量数据
const fullData = compressor.decompress(deltaData);
属性类型默认值描述
positionThresholdnumber0.01位置变化阈值
rotationThresholdnumber0.001旋转变化阈值(弧度)
velocityThresholdnumber0.1速度变化阈值
fullSnapshotIntervalnumber60完整快照间隔(帧数)

使用位标志表示哪些字段发生了变化:

import { DeltaFlags } from '@esengine/network';
// 位标志定义
DeltaFlags.NONE // 0 - 无变化
DeltaFlags.POSITION // 1 - 位置变化
DeltaFlags.ROTATION // 2 - 旋转变化
DeltaFlags.VELOCITY // 4 - 速度变化
DeltaFlags.ANGULAR_VELOCITY // 8 - 角速度变化
DeltaFlags.CUSTOM // 16 - 自定义数据变化
interface EntitySyncState {
netId: number;
pos?: { x: number; y: number };
rot?: number;
vel?: { x: number; y: number };
angVel?: number;
custom?: Record<string, unknown>;
}
interface EntityDeltaState {
netId: number;
flags: number; // 变化标志位
pos?: { x: number; y: number }; // 仅在 POSITION 标志时存在
rot?: number; // 仅在 ROTATION 标志时存在
vel?: { x: number; y: number }; // 仅在 VELOCITY 标志时存在
angVel?: number; // 仅在 ANGULAR_VELOCITY 标志时存在
custom?: Record<string, unknown>; // 仅在 CUSTOM 标志时存在
}
帧 1 (完整快照):
Entity 1: pos=(100, 200), rot=0
帧 2 (增量):
Entity 1: flags=POSITION, pos=(101, 200) // 只有 X 变化
帧 3 (增量):
Entity 1: flags=0 // 无变化,不发送
帧 4 (增量):
Entity 1: flags=POSITION|ROTATION, pos=(105, 200), rot=0.5
帧 60 (强制完整快照):
Entity 1: pos=(200, 300), rot=1.0, vel=(5, 0)
import { createStateDeltaCompressor } from '@esengine/network';
class GameServer {
private compressor = createStateDeltaCompressor();
// 广播状态更新
broadcastState(entities: EntitySyncState[]) {
const syncData: SyncData = {
frame: this.currentFrame,
timestamp: Date.now(),
entities,
};
// 压缩数据
const deltaData = this.compressor.compress(syncData);
// 发送增量数据
this.broadcast('sync', deltaData);
}
// 玩家离开时清理
onPlayerLeave(netId: number) {
this.compressor.removeEntity(netId);
}
}
class GameClient {
private compressor = createStateDeltaCompressor();
// 接收增量数据
onSyncReceived(deltaData: DeltaSyncData) {
// 解压为完整状态
const fullData = this.compressor.decompress(deltaData);
// 应用状态
for (const entity of fullData.entities) {
this.applyEntityState(entity);
}
}
}

假设每个实体有以下数据:

字段大小(字节)
netId4
pos.x8
pos.y8
rot8
vel.x8
vel.y8
angVel8
总计52

使用增量压缩:

场景原始压缩后节省
只有位置变化524+1+16 = 2160%
只有旋转变化524+1+8 = 1375%
静止不动520100%
位置+旋转变化524+1+24 = 2944%

某些情况下需要发送完整快照:

// 新玩家加入时
compressor.forceFullSnapshot();
const data = compressor.compress(syncData);
// 这次会发送完整状态
// 重连时
compressor.clear(); // 清除历史状态
compressor.forceFullSnapshot();

支持同步自定义游戏数据:

const syncData: SyncData = {
frame: 100,
timestamp: Date.now(),
entities: [
{
netId: 1,
pos: { x: 100, y: 200 },
custom: {
health: 80,
mana: 50,
buffs: ['speed', 'shield'],
},
},
],
};
// 自定义数据也会进行增量压缩
const deltaData = compressor.compress(syncData);
// 高精度游戏(如竞技游戏)
const compressor = createStateDeltaCompressor({
positionThreshold: 0.001,
rotationThreshold: 0.0001,
});
// 普通游戏
const compressor = createStateDeltaCompressor({
positionThreshold: 0.1,
rotationThreshold: 0.01,
});
// 高可靠性(网络不稳定)
fullSnapshotInterval: 30, // 每 30 帧发送完整快照
// 低带宽优先
fullSnapshotInterval: 120, // 每 120 帧发送完整快照
// 先用 AOI 过滤,再用增量压缩
const filteredEntities = aoiSystem.filterSyncData(playerId, allEntities);
const syncData = { frame, timestamp, entities: filteredEntities };
const deltaData = compressor.compress(syncData);
// 实体销毁时清理压缩器状态
function onEntityDespawn(netId: number) {
compressor.removeEntity(netId);
}
┌─────────────────┐
│ 游戏状态 │
└────────┬────────┘
┌────────▼────────┐
│ AOI 过滤 │ ← 只处理视野内实体
└────────┬────────┘
┌────────▼────────┐
│ 增量压缩 │ ← 只发送变化的字段
└────────┬────────┘
┌────────▼────────┐
│ 网络传输 │
└─────────────────┘
const compressor = createStateDeltaCompressor();
// 检查压缩效果
const original = syncData;
const compressed = compressor.compress(original);
console.log('Original entities:', original.entities.length);
console.log('Compressed entities:', compressed.entities.length);
console.log('Is full snapshot:', compressed.isFullSnapshot);
// 查看每个实体的变化
for (const delta of compressed.entities) {
console.log(`Entity ${delta.netId}:`, {
hasPosition: !!(delta.flags & DeltaFlags.POSITION),
hasRotation: !!(delta.flags & DeltaFlags.ROTATION),
hasVelocity: !!(delta.flags & DeltaFlags.VELOCITY),
hasCustom: !!(delta.flags & DeltaFlags.CUSTOM),
});
}