跳转到内容

客户端使用

NetworkPlugin 是客户端网络功能的核心入口,基于 @esengine/rpc 提供类型安全的网络通信。

import { Core } from '@esengine/ecs-framework';
import { NetworkPlugin } from '@esengine/network';
// 创建并安装插件
const networkPlugin = new NetworkPlugin();
await Core.installPlugin(networkPlugin);
// 连接服务器
const success = await networkPlugin.connect({
url: 'ws://localhost:3000',
playerName: 'PlayerName',
roomId: 'room-1' // 可选
});
// 断开连接
await networkPlugin.disconnect();
class NetworkPlugin {
readonly name: string;
readonly version: string;
// 访问器
get networkService(): GameNetworkService;
get syncSystem(): NetworkSyncSystem;
get spawnSystem(): NetworkSpawnSystem;
get inputSystem(): NetworkInputSystem;
get localPlayerId(): number;
get isConnected(): boolean;
// 连接服务器
connect(options: {
url: string;
playerName: string;
roomId?: string;
}): Promise<boolean>;
// 断开连接
disconnect(): Promise<void>;
// 注册预制体工厂
registerPrefab(prefabType: string, factory: PrefabFactory): void;
// 发送输入
sendMoveInput(x: number, y: number): void;
sendActionInput(action: string): void;
}

网络标识组件,每个网络同步的实体必须拥有:

class NetworkIdentity extends Component {
netId: number; // 网络唯一 ID
ownerId: number; // 所有者客户端 ID
bIsLocalPlayer: boolean; // 是否为本地玩家
bHasAuthority: boolean; // 是否有权限控制
}

使用示例:

networkPlugin.registerPrefab('player', (scene, spawn) => {
const entity = scene.createEntity(`player_${spawn.netId}`);
const identity = entity.addComponent(new NetworkIdentity());
identity.netId = spawn.netId;
identity.ownerId = spawn.ownerId;
identity.bIsLocalPlayer = spawn.ownerId === networkPlugin.localPlayerId;
return entity;
});

网络变换组件,用于位置和旋转同步:

class NetworkTransform extends Component {
position: { x: number; y: number };
rotation: number;
velocity: { x: number; y: number };
}

处理服务器状态同步和插值:

  • 接收服务器状态快照
  • 将状态存入快照缓冲区
  • 对远程实体进行插值平滑

处理实体的网络生成和销毁:

  • 监听 Spawn/Despawn 消息
  • 使用注册的预制体工厂创建实体
  • 管理网络实体的生命周期

处理本地玩家输入的网络发送:

class NetworkInputSystem extends EntitySystem {
addMoveInput(x: number, y: number): void;
addActionInput(action: string): void;
clearInput(): void;
}

使用示例:

// 方式 1:通过 NetworkPlugin(推荐)
networkPlugin.sendMoveInput(0, 1);
networkPlugin.sendActionInput('jump');
// 方式 2:直接使用 inputSystem
const inputSystem = networkPlugin.inputSystem;
inputSystem.addMoveInput(0, 1);
inputSystem.addActionInput('jump');

预制体工厂用于创建网络实体:

type PrefabFactory = (scene: Scene, spawn: MsgSpawn) => Entity;

完整示例:

// 注册玩家预制体
networkPlugin.registerPrefab('player', (scene, spawn) => {
const entity = scene.createEntity(`player_${spawn.netId}`);
const identity = entity.addComponent(new NetworkIdentity());
identity.netId = spawn.netId;
identity.ownerId = spawn.ownerId;
identity.bIsLocalPlayer = spawn.ownerId === networkPlugin.localPlayerId;
entity.addComponent(new NetworkTransform());
// 本地玩家添加输入组件
if (identity.bIsLocalPlayer) {
entity.addComponent(new LocalInputComponent());
}
return entity;
});
// 注册敌人预制体
networkPlugin.registerPrefab('enemy', (scene, spawn) => {
const entity = scene.createEntity(`enemy_${spawn.netId}`);
const identity = entity.addComponent(new NetworkIdentity());
identity.netId = spawn.netId;
identity.ownerId = spawn.ownerId;
entity.addComponent(new NetworkTransform());
entity.addComponent(new EnemyComponent());
return entity;
});

创建自定义输入处理系统:

import { EntitySystem, Matcher, Entity } from '@esengine/ecs-framework';
import { NetworkPlugin, NetworkIdentity } from '@esengine/network';
class LocalInputHandler extends EntitySystem {
private _networkPlugin: NetworkPlugin | null = null;
constructor() {
super(Matcher.empty().all(NetworkIdentity, LocalInputComponent));
}
protected onAddedToScene(): void {
this._networkPlugin = Core.getPlugin(NetworkPlugin);
}
protected processEntity(entity: Entity, dt: number): void {
if (!this._networkPlugin) return;
const identity = entity.getComponent(NetworkIdentity)!;
if (!identity.bIsLocalPlayer) return;
// 读取键盘输入
let moveX = 0;
let moveY = 0;
if (keyboard.isPressed('A')) moveX -= 1;
if (keyboard.isPressed('D')) moveX += 1;
if (keyboard.isPressed('W')) moveY += 1;
if (keyboard.isPressed('S')) moveY -= 1;
if (moveX !== 0 || moveY !== 0) {
this._networkPlugin.sendMoveInput(moveX, moveY);
}
if (keyboard.isJustPressed('Space')) {
this._networkPlugin.sendActionInput('jump');
}
}
}

使用 GameNetworkService 的链式 API 监听消息:

const { networkService } = networkPlugin;
// 监听状态同步
networkService.onSync((data) => {
console.log('收到同步数据:', data.entities.length, '个实体');
});
// 监听实体生成
networkService.onSpawn((data) => {
console.log('生成实体:', data.prefab, 'netId:', data.netId);
});
// 监听实体销毁
networkService.onDespawn((data) => {
console.log('销毁实体:', data.netId);
});
// 通过连接选项设置回调
await networkPlugin.connect({
url: 'ws://localhost:3000',
playerName: 'Player1',
onConnect: () => console.log('已连接'),
onDisconnect: (reason) => console.log('已断开:', reason),
onError: (error) => console.error('网络错误:', error)
});
  1. 权限检查:使用 bHasAuthority 检查是否有权限修改实体

  2. 本地玩家标识:通过 bIsLocalPlayer 区分本地和远程玩家

  3. 预制体管理:为每种网络实体类型注册对应的预制体工厂

  4. 输入发送:推荐使用 NetworkPlugin.sendMoveInput()sendActionInput() 方法