跳转到内容

变更检测

v2.4.0+

框架提供了基于 epoch 的帧级变更检测机制,让系统可以只处理发生变化的实体,大幅提升性能。

  • Epoch:全局帧计数器,每帧递增
  • lastWriteEpoch:组件最后被修改时的 epoch
  • 变更检测:通过比较 epoch 判断组件是否在指定时间点后发生变化

修改组件数据后,需要标记组件为已变更。有两种方式:

方式 1:通过 Entity 辅助方法(推荐)

Section titled “方式 1:通过 Entity 辅助方法(推荐)”
// 修改组件后通过 entity.markDirty() 标记
const pos = entity.getComponent(Position)!;
pos.x = 100;
pos.y = 200;
entity.markDirty(pos);
// 可以同时标记多个组件
const vel = entity.getComponent(Velocity)!;
vel.vx = 10;
entity.markDirty(pos, vel);
class VelocityComponent extends Component {
private _vx: number = 0;
private _vy: number = 0;
// 提供修改方法,接收 epoch 参数
public setVelocity(vx: number, vy: number, epoch: number): void {
this._vx = vx;
this._vy = vy;
this.markDirty(epoch);
}
public get vx(): number { return this._vx; }
public get vy(): number { return this._vy; }
}
// 在系统中使用
const vel = entity.getComponent(VelocityComponent)!;
vel.setVelocity(10, 20, this.currentEpoch);

EntitySystem 提供了多个变更检测辅助方法:

@ECSSystem('Physics')
class PhysicsSystem extends EntitySystem {
constructor() {
super(Matcher.all(Position, Velocity));
}
protected process(entities: readonly Entity[]): void {
// 使用 forEachChanged 只处理变更的实体
// 自动保存 epoch 检查点
this.forEachChanged(entities, [Velocity], (entity) => {
const pos = this.requireComponent(entity, Position);
const vel = this.requireComponent(entity, Velocity);
// 只有 Velocity 变化时才更新位置
pos.x += vel.vx * Time.deltaTime;
pos.y += vel.vy * Time.deltaTime;
});
}
}
@ECSSystem('Transform')
class TransformSystem extends EntitySystem {
constructor() {
super(Matcher.all(Transform, RigidBody));
}
protected process(entities: readonly Entity[]): void {
// 使用 filterChanged 获取变更的实体列表
const changedEntities = this.filterChanged(entities, [RigidBody]);
for (const entity of changedEntities) {
// 处理物理状态变化的实体
this.updatePhysics(entity);
}
// 手动保存 epoch 检查点
this.saveEpoch();
}
protected updatePhysics(entity: Entity): void {
// 物理更新逻辑
}
}
protected process(entities: readonly Entity[]): void {
for (const entity of entities) {
// 检查单个实体的指定组件是否发生变更
if (this.hasChanged(entity, [Transform])) {
this.updateRenderData(entity);
}
}
}
方法说明
forEachChanged(entities, [Types], callback)遍历指定组件发生变更的实体,自动保存检查点
filterChanged(entities, [Types])返回指定组件发生变更的实体数组
hasChanged(entity, [Types])检查单个实体的指定组件是否发生变更
saveEpoch()手动保存当前 epoch 作为检查点
lastProcessEpoch获取上次保存的 epoch 检查点
currentEpoch获取当前场景的 epoch

变更检测特别适合以下场景:

只在数据变化时更新渲染:

@ECSSystem('RenderUpdate')
class RenderUpdateSystem extends EntitySystem {
constructor() {
super(Matcher.all(Transform, Sprite));
}
protected process(entities: readonly Entity[]): void {
// 只更新变化的精灵
this.forEachChanged(entities, [Transform, Sprite], (entity) => {
const transform = this.requireComponent(entity, Transform);
const sprite = this.requireComponent(entity, Sprite);
this.updateSpriteMatrix(sprite, transform);
});
}
}

只发送变化的组件数据:

@ECSSystem('NetworkSync')
class NetworkSyncSystem extends EntitySystem {
constructor() {
super(Matcher.all(NetworkComponent, Transform));
}
protected process(entities: readonly Entity[]): void {
// 只同步变化的实体,大幅减少网络流量
this.forEachChanged(entities, [Transform], (entity) => {
const transform = this.requireComponent(entity, Transform);
const network = this.requireComponent(entity, NetworkComponent);
this.sendTransformUpdate(network.id, transform);
});
}
private sendTransformUpdate(id: string, transform: Transform): void {
// 发送网络更新
}
}

只同步位置/速度发生变化的实体:

@ECSSystem('PhysicsSync')
class PhysicsSyncSystem extends EntitySystem {
constructor() {
super(Matcher.all(Transform, RigidBody));
}
protected process(entities: readonly Entity[]): void {
// 从物理引擎同步变化的实体
this.forEachChanged(entities, [RigidBody], (entity) => {
const transform = entity.getComponent(Transform)!;
const rigidBody = entity.getComponent(RigidBody)!;
// 更新 Transform
transform.position = rigidBody.getPosition();
transform.rotation = rigidBody.getRotation();
// 标记 Transform 已变更
entity.markDirty(transform);
});
}
}

只在依赖数据变化时重新计算:

@ECSSystem('PathCache')
class PathCacheSystem extends EntitySystem {
constructor() {
super(Matcher.all(PathFinder, Transform));
}
protected process(entities: readonly Entity[]): void {
// 只有位置变化时才重新计算路径
this.forEachChanged(entities, [Transform], (entity) => {
const pathFinder = entity.getComponent(PathFinder)!;
pathFinder.invalidateCache();
pathFinder.recalculatePath();
});
}
}
场景无变更检测有变更检测提升
1000 实体,10% 变化1000 次处理100 次处理10x
1000 实体,1% 变化1000 次处理10 次处理100x
网络同步全量发送增量发送带宽节省 90%+