feat(network): 基于 TSRPC 的网络同步模块 (#318)

- network-protocols: 共享协议包,使用 TSRPC CLI 生成完整类型验证
- network: 浏览器客户端,提供 NetworkPlugin、NetworkService 和同步系统
- network-server: Node.js 服务端,提供 GameServer 和房间管理
This commit is contained in:
YHH
2025-12-24 22:49:29 +08:00
committed by GitHub
parent dbc6793dc4
commit 235c432edb
35 changed files with 2727 additions and 88 deletions

View File

@@ -0,0 +1,104 @@
import { EntitySystem, Matcher, Time, type Entity } from '@esengine/ecs-framework';
import type { MsgSync } from '@esengine/network-protocols';
import { NetworkIdentity } from '../components/NetworkIdentity';
import { NetworkTransform } from '../components/NetworkTransform';
import type { NetworkService } from '../services/NetworkService';
/**
* 网络同步系统
* Network sync system
*
* 处理网络实体的状态同步和插值。
* Handles state synchronization and interpolation for networked entities.
*/
export class NetworkSyncSystem extends EntitySystem {
private _networkService: NetworkService;
private _netIdToEntity: Map<number, number> = new Map();
constructor(networkService: NetworkService) {
super(Matcher.all(NetworkIdentity, NetworkTransform));
this._networkService = networkService;
}
protected override onInitialize(): void {
this._networkService.setCallbacks({
onSync: this._handleSync.bind(this)
});
}
/**
* 处理实体列表
* Process entities
*/
protected override process(entities: readonly Entity[]): void {
const deltaTime = Time.deltaTime;
for (const entity of entities) {
const transform = this.requireComponent(entity, NetworkTransform);
const identity = this.requireComponent(entity, NetworkIdentity);
// 只有非本地玩家需要插值
// Only non-local players need interpolation
if (!identity.bHasAuthority && transform.bInterpolate) {
this._interpolate(transform, deltaTime);
}
}
}
/**
* 注册网络实体
* Register network entity
*/
public registerEntity(netId: number, entityId: number): void {
this._netIdToEntity.set(netId, entityId);
}
/**
* 注销网络实体
* Unregister network entity
*/
public unregisterEntity(netId: number): void {
this._netIdToEntity.delete(netId);
}
/**
* 根据网络 ID 获取实体 ID
* Get entity ID by network ID
*/
public getEntityId(netId: number): number | undefined {
return this._netIdToEntity.get(netId);
}
private _handleSync(msg: MsgSync): void {
for (const state of msg.entities) {
const entityId = this._netIdToEntity.get(state.netId);
if (entityId === undefined) continue;
const entity = this.scene?.findEntityById(entityId);
if (!entity) continue;
const transform = entity.getComponent(NetworkTransform);
if (transform && state.pos) {
transform.setTarget(state.pos.x, state.pos.y, state.rot);
}
}
}
private _interpolate(transform: NetworkTransform, deltaTime: number): void {
const t = Math.min(1, transform.lerpSpeed * deltaTime);
transform.currentX += (transform.targetX - transform.currentX) * t;
transform.currentY += (transform.targetY - transform.currentY) * t;
// 角度插值需要处理环绕
// Angle interpolation needs to handle wrap-around
let angleDiff = transform.targetRotation - transform.currentRotation;
while (angleDiff > Math.PI) angleDiff -= Math.PI * 2;
while (angleDiff < -Math.PI) angleDiff += Math.PI * 2;
transform.currentRotation += angleDiff * t;
}
protected override onDestroy(): void {
this._netIdToEntity.clear();
}
}