feat(rpc,network): 新增 RPC 库并迁移网络模块 (#364)

* feat(rpc,network): 新增 RPC 库并迁移网络模块

## @esengine/rpc (新增)
- 新增类型安全的 RPC 库,支持 WebSocket 通信
- 新增 RpcClient 类:connect/disconnect, call/send/on/off/once 方法
- 新增 RpcServer 类:Node.js WebSocket 服务端
- 新增编解码系统:支持 JSON 和 MessagePack
- 新增 TextEncoder/TextDecoder polyfill,兼容微信小游戏平台
- 新增 WebSocketAdapter 接口,支持跨平台 WebSocket 抽象

## @esengine/network (重构)
- 重构 NetworkService:拆分为 RpcService 基类和 GameNetworkService
- 新增 gameProtocol:类型安全的 API 和消息定义
- 新增类型安全便捷方法:sendInput(), onSync(), onSpawn(), onDespawn()
- 更新 NetworkPlugin 使用新的服务架构
- 移除 TSRPC 依赖,改用 @esengine/rpc

## 文档
- 新增 RPC 模块文档(中英文)
- 更新 Network 模块文档(中英文)
- 更新侧边栏导航

* fix(network,cli): 修复 CI 构建和更新 CLI 适配器

## 修复
- 在 tsconfig.build.json 添加 rpc 引用,修复类型声明生成

## CLI 更新
- 更新 nodejs 适配器使用新的 @esengine/rpc
- 生成的服务器代码使用 RpcServer 替代旧的 GameServer
- 添加 ws 和 @types/ws 依赖
- 更新 README 模板中的客户端连接示例

* chore: 添加 CLI changeset

* fix(ci): add @esengine/rpc to build and check scripts

- Add rpc package to CI build step (must build before network)
- Add rpc to type-check:framework, lint:framework, test:ci:framework

* fix(rpc,network): fix tsconfig for declaration generation

- Remove composite mode from rpc (not needed, causes CI issues)
- Remove rpc from network project references (resolves via node_modules)
- Remove unused references from network tsconfig.build.json
This commit is contained in:
YHH
2025-12-28 10:54:51 +08:00
committed by GitHub
parent 8605888f11
commit 7940f581a6
39 changed files with 3505 additions and 784 deletions

View File

@@ -251,7 +251,14 @@ export default defineConfig({
],
},
{
label: '网络',
label: 'RPC 通信',
translations: { en: 'RPC' },
items: [
{ label: '概述', slug: 'modules/rpc', translations: { en: 'Overview' } },
],
},
{
label: '网络同步',
translations: { en: 'Network' },
items: [
{ label: '概述', slug: 'modules/network', translations: { en: 'Overview' } },

View File

@@ -1,41 +1,38 @@
---
title: "Network System"
description: "TSRPC-based multiplayer game network synchronization solution"
description: "Type-safe multiplayer game network synchronization based on @esengine/rpc"
---
`@esengine/network` provides a TSRPC-based client-server network synchronization solution for multiplayer games, including entity synchronization, input handling, and state interpolation.
`@esengine/network` provides a type-safe network synchronization solution based on `@esengine/rpc` for multiplayer games, including entity synchronization, input handling, and state interpolation.
## Overview
The network module consists of three packages:
The network module consists of two core packages:
| Package | Description |
|---------|-------------|
| `@esengine/network` | Client-side ECS plugin |
| `@esengine/network-protocols` | Shared protocol definitions |
| `@esengine/network-server` | Server-side implementation |
| `@esengine/rpc` | Type-safe RPC communication library |
| `@esengine/network` | RPC-based game networking plugin |
## Installation
```bash
# Client
npm install @esengine/network
# Server
npm install @esengine/network-server
```
> `@esengine/rpc` is automatically installed as a dependency.
## Architecture
```
Client Server
┌────────────────┐ ┌────────────────┐
│ NetworkPlugin │◄──── WS ────► │ GameServer │
│ ├─ Service │ ├─ Room
│ ├─ SyncSystem │ └─ Players
│ ├─ SpawnSystem └────────────────┘
│ └─ InputSystem │
└────────────────┘
┌────────────────────┐ ┌────────────────┐
│ NetworkPlugin │◄── WS ───►│ RpcServer
│ ├─ NetworkService │ ├─ Protocol
│ ├─ SyncSystem │ └─ Handlers │
│ ├─ SpawnSystem └────────────────┘
│ └─ InputSystem
└────────────────────
```
## Quick Start
@@ -69,61 +66,81 @@ networkPlugin.registerPrefab('player', (scene, spawn) => {
const identity = entity.addComponent(new NetworkIdentity());
identity.netId = spawn.netId;
identity.ownerId = spawn.ownerId;
identity.bIsLocalPlayer = spawn.ownerId === networkPlugin.networkService.clientId;
identity.bIsLocalPlayer = spawn.ownerId === networkPlugin.localPlayerId;
entity.addComponent(new NetworkTransform());
return entity;
});
// Connect to server
const success = await networkPlugin.connect('ws://localhost:3000', 'PlayerName');
const success = await networkPlugin.connect({
url: 'ws://localhost:3000',
playerName: 'Player1',
roomId: 'room-1' // optional
});
if (success) {
console.log('Connected!');
console.log('Connected! Player ID:', networkPlugin.localPlayerId);
}
```
### Server
```typescript
import { GameServer } from '@esengine/network-server';
import { RpcServer } from '@esengine/rpc/server';
import { gameProtocol } from '@esengine/network';
const server = new GameServer({
const server = new RpcServer(gameProtocol, {
port: 3000,
roomConfig: {
maxPlayers: 16,
tickRate: 20
}
onStart: (port) => console.log(`Server running on ws://localhost:${port}`)
});
// Register API handlers
server.handle('join', async (input, ctx) => {
const playerId = generatePlayerId();
return { playerId, roomId: input.roomId ?? 'default' };
});
server.handle('leave', async (input, ctx) => {
// Handle player leaving
});
await server.start();
console.log('Server started on ws://localhost:3000');
```
## Quick Setup with CLI
## Custom Protocol
We recommend using ESEngine CLI to quickly create a complete game server project:
You can define your own protocol using `@esengine/rpc`:
```bash
mkdir my-game-server && cd my-game-server
npm init -y
npx @esengine/cli init -p nodejs
```
```typescript
import { rpc } from '@esengine/rpc';
Generated project structure:
// Define custom protocol
export const myProtocol = rpc.define({
api: {
login: rpc.api<{ username: string }, { token: string }>(),
getData: rpc.api<{ id: number }, { data: object }>(),
},
msg: {
chat: rpc.msg<{ from: string; text: string }>(),
notification: rpc.msg<{ type: string; content: string }>(),
},
});
```
my-game-server/
├── src/
│ ├── index.ts
│ ├── server/
│ │ └── GameServer.ts
│ └── game/
│ ├── Game.ts
│ ├── scenes/
│ ├── components/
│ └── systems/
├── tsconfig.json
└── package.json
// Create service with custom protocol
import { RpcService } from '@esengine/network';
const service = new RpcService(myProtocol);
await service.connect({ url: 'ws://localhost:3000' });
// Type-safe API calls
const result = await service.call('login', { username: 'test' });
console.log(result.token);
// Type-safe message listening
service.on('chat', (data) => {
console.log(`${data.from}: ${data.text}`);
});
```
## Documentation

View File

@@ -0,0 +1,165 @@
---
title: "RPC Library"
description: "Type-safe WebSocket RPC communication library"
---
`@esengine/rpc` is a lightweight, type-safe RPC (Remote Procedure Call) library designed for games and real-time applications.
## Features
- **Type-Safe**: Complete TypeScript type inference for API calls and message handling
- **Bidirectional**: Supports both request-response (API) and publish-subscribe (message) patterns
- **Flexible Codecs**: Built-in JSON and MessagePack codecs
- **Cross-Platform**: Works in browsers, Node.js, and WeChat Mini Games
## Installation
```bash
npm install @esengine/rpc
```
## Quick Start
### 1. Define Protocol
```typescript
import { rpc } from '@esengine/rpc';
// Define protocol (shared between client and server)
export const chatProtocol = rpc.define({
api: {
// Request-response APIs
login: rpc.api<{ username: string }, { userId: string; token: string }>(),
sendMessage: rpc.api<{ text: string }, { messageId: string }>(),
},
msg: {
// One-way messages (publish-subscribe)
newMessage: rpc.msg<{ from: string; text: string; time: number }>(),
userJoined: rpc.msg<{ username: string }>(),
userLeft: rpc.msg<{ username: string }>(),
},
});
export type ChatProtocol = typeof chatProtocol;
```
### 2. Create Client
```typescript
import { RpcClient } from '@esengine/rpc/client';
import { chatProtocol } from './protocol';
// Create client
const client = new RpcClient(chatProtocol, 'ws://localhost:3000', {
onConnect: () => console.log('Connected'),
onDisconnect: (reason) => console.log('Disconnected:', reason),
onError: (error) => console.error('Error:', error),
});
// Connect to server
await client.connect();
// Call API (type-safe)
const { userId, token } = await client.call('login', { username: 'player1' });
console.log('Logged in:', userId);
// Listen to messages (type-safe)
client.on('newMessage', (data) => {
console.log(`${data.from}: ${data.text}`);
});
// Send message
await client.call('sendMessage', { text: 'Hello!' });
```
### 3. Create Server
```typescript
import { RpcServer } from '@esengine/rpc/server';
import { chatProtocol } from './protocol';
// Create server
const server = new RpcServer(chatProtocol, {
port: 3000,
onStart: (port) => console.log(`Server started: ws://localhost:${port}`),
});
// Register API handlers
server.handle('login', async (input, ctx) => {
const userId = generateUserId();
const token = generateToken();
// Broadcast user joined
server.broadcast('userJoined', { username: input.username });
return { userId, token };
});
server.handle('sendMessage', async (input, ctx) => {
const messageId = generateMessageId();
// Broadcast new message
server.broadcast('newMessage', {
from: ctx.clientId,
text: input.text,
time: Date.now(),
});
return { messageId };
});
// Start server
await server.start();
```
## Core Concepts
### Protocol Definition
A protocol is the contract between client and server:
```typescript
const protocol = rpc.define({
api: {
// API: Request-response pattern, client initiates, server handles and returns
methodName: rpc.api<InputType, OutputType>(),
},
msg: {
// Message: Publish-subscribe pattern, either side can send, other side listens
messageName: rpc.msg<DataType>(),
},
});
```
### API vs Messages
| Feature | API | Message |
|---------|-----|---------|
| Pattern | Request-Response | Publish-Subscribe |
| Return Value | Yes (Promise) | No |
| Confirmation | Waits for response | Fire-and-forget |
| Use Cases | Login, queries, operations | Real-time notifications, state sync |
## Codecs
Two built-in codecs are available:
```typescript
import { json, msgpack } from '@esengine/rpc/codec';
// JSON (default, human-readable)
const client = new RpcClient(protocol, url, {
codec: json(),
});
// MessagePack (binary, more efficient)
const client = new RpcClient(protocol, url, {
codec: msgpack(),
});
```
## Documentation
- [Client API](/en/modules/rpc/client/) - Detailed RpcClient API
- [Server API](/en/modules/rpc/server/) - Detailed RpcServer API
- [Codecs](/en/modules/rpc/codec/) - Custom codecs

View File

@@ -13,10 +13,11 @@ class NetworkPlugin implements IPlugin {
readonly version: string;
// 访问器
get networkService(): NetworkService;
get networkService(): GameNetworkService;
get syncSystem(): NetworkSyncSystem;
get spawnSystem(): NetworkSpawnSystem;
get inputSystem(): NetworkInputSystem;
get localPlayerId(): number;
get isConnected(): boolean;
// 生命周期
@@ -24,7 +25,10 @@ class NetworkPlugin implements IPlugin {
uninstall(): void;
// 连接管理
connect(serverUrl: string, playerName: string, roomId?: string): Promise<boolean>;
connect(options: NetworkServiceOptions & {
playerName: string;
roomId?: string;
}): Promise<boolean>;
disconnect(): Promise<void>;
// 预制体注册
@@ -36,36 +40,65 @@ class NetworkPlugin implements IPlugin {
}
```
## NetworkService
## RpcService
网络服务,管理 WebSocket 连接
通用 RPC 服务基类,支持自定义协议
```typescript
class NetworkService {
class RpcService<P extends ProtocolDef> {
// 访问器
get state(): ENetworkState;
get state(): NetworkState;
get isConnected(): boolean;
get clientId(): number;
get roomId(): string;
get client(): RpcClient<P> | null;
constructor(protocol: P);
// 连接管理
connect(serverUrl: string, playerName: string, roomId?: string): Promise<boolean>;
disconnect(): Promise<void>;
connect(options: NetworkServiceOptions): Promise<void>;
disconnect(): void;
// 输入发送
sendInput(input: IPlayerInput): void;
// RPC 调用
call<K extends ApiNames<P>>(
name: K,
input: ApiInput<P['api'][K]>
): Promise<ApiOutput<P['api'][K]>>;
// 回调设置
setCallbacks(callbacks: INetworkCallbacks): void;
// 消息发送
send<K extends MsgNames<P>>(name: K, data: MsgData<P['msg'][K]>): void;
// 消息监听
on<K extends MsgNames<P>>(name: K, handler: (data: MsgData<P['msg'][K]>) => void): this;
off<K extends MsgNames<P>>(name: K, handler?: (data: MsgData<P['msg'][K]>) => void): this;
once<K extends MsgNames<P>>(name: K, handler: (data: MsgData<P['msg'][K]>) => void): this;
}
```
## GameNetworkService
游戏网络服务,继承自 RpcService提供游戏特定的便捷方法。
```typescript
class GameNetworkService extends RpcService<GameProtocol> {
// 发送玩家输入
sendInput(input: PlayerInput): void;
// 监听状态同步(链式调用)
onSync(handler: (data: SyncData) => void): this;
// 监听实体生成
onSpawn(handler: (data: SpawnData) => void): this;
// 监听实体销毁
onDespawn(handler: (data: DespawnData) => void): this;
}
```
## 枚举类型
### ENetworkState
### NetworkState
```typescript
const enum ENetworkState {
const enum NetworkState {
Disconnected = 0,
Connecting = 1,
Connected = 2
@@ -74,15 +107,21 @@ const enum ENetworkState {
## 接口类型
### INetworkCallbacks
### NetworkServiceOptions
```typescript
interface INetworkCallbacks {
onConnected?: (clientId: number, roomId: string) => void;
onDisconnected?: () => void;
onSync?: (msg: MsgSync) => void;
onSpawn?: (msg: MsgSpawn) => void;
onDespawn?: (msg: MsgDespawn) => void;
interface NetworkServiceOptions extends RpcClientOptions {
url: string;
}
```
### RpcClientOptions
```typescript
interface RpcClientOptions {
codec?: Codec;
onConnect?: () => void;
onDisconnect?: (reason?: string) => void;
onError?: (error: Error) => void;
}
```
@@ -90,15 +129,15 @@ interface INetworkCallbacks {
### PrefabFactory
```typescript
type PrefabFactory = (scene: Scene, spawn: MsgSpawn) => Entity;
type PrefabFactory = (scene: Scene, spawn: SpawnData) => Entity;
```
### IPlayerInput
### PlayerInput
```typescript
interface IPlayerInput {
seq?: number;
moveDir?: Vec2;
interface PlayerInput {
frame: number;
moveDir?: { x: number; y: number };
actions?: string[];
}
```
@@ -170,86 +209,128 @@ const networkService = services.get(NetworkServiceToken);
## 服务器端 API
### GameServer
### RpcServer
```typescript
class GameServer {
constructor(config: IGameServerConfig);
class RpcServer<P extends ProtocolDef> {
constructor(protocol: P, options: RpcServerOptions);
// 启动/停止服务器
start(): Promise<void>;
stop(): Promise<void>;
stop(): void;
getOrCreateRoom(roomId: string): Room;
getRoom(roomId: string): Room | undefined;
destroyRoom(roomId: string): void;
// 注册 API 处理器
handle<K extends ApiNames<P>>(
name: K,
handler: (input: ApiInput<P['api'][K]>, ctx: RpcContext) => Promise<ApiOutput<P['api'][K]>>
): void;
// 广播消息
broadcast<K extends MsgNames<P>>(name: K, data: MsgData<P['msg'][K]>): void;
// 发送给特定客户端
sendTo<K extends MsgNames<P>>(clientId: string, name: K, data: MsgData<P['msg'][K]>): void;
}
```
### Room
### RpcServerOptions
```typescript
class Room {
readonly id: string;
readonly playerCount: number;
readonly isFull: boolean;
addPlayer(name: string, connection: Connection): IPlayer | null;
removePlayer(clientId: number): void;
getPlayer(clientId: number): IPlayer | undefined;
handleInput(clientId: number, input: IPlayerInput): void;
destroy(): void;
interface RpcServerOptions {
port: number;
codec?: Codec;
onStart?: (port: number) => void;
onConnection?: (clientId: string) => void;
onDisconnection?: (clientId: string, reason?: string) => void;
onError?: (error: Error) => void;
}
```
### IPlayer
### RpcContext
```typescript
interface IPlayer {
clientId: number;
name: string;
connection: Connection;
netId: number;
interface RpcContext {
clientId: string;
}
```
## 协议消息
## 协议定义
### MsgSync
### gameProtocol
默认游戏协议,包含加入/离开 API 和状态同步消息:
```typescript
interface MsgSync {
const gameProtocol = rpc.define({
api: {
join: rpc.api<JoinRequest, JoinResponse>(),
leave: rpc.api<void, void>(),
},
msg: {
input: rpc.msg<PlayerInput>(),
sync: rpc.msg<SyncData>(),
spawn: rpc.msg<SpawnData>(),
despawn: rpc.msg<DespawnData>(),
},
});
```
## 协议消息类型
### SyncData
```typescript
interface SyncData {
time: number;
entities: IEntityState[];
}
```
### MsgSpawn
### SpawnData
```typescript
interface MsgSpawn {
interface SpawnData {
netId: number;
ownerId: number;
prefab: string;
pos: Vec2;
rot: number;
position: { x: number; y: number };
rotation: number;
}
```
### MsgDespawn
### DespawnData
```typescript
interface MsgDespawn {
interface DespawnData {
netId: number;
}
```
### IEntityState
### JoinRequest
```typescript
interface IEntityState {
interface JoinRequest {
playerName: string;
roomId?: string;
}
```
### JoinResponse
```typescript
interface JoinResponse {
playerId: number;
roomId: string;
}
```
### EntitySyncState
```typescript
interface EntitySyncState {
netId: number;
pos?: Vec2;
rot?: number;
position?: { x: number; y: number };
rotation?: number;
}
```

View File

@@ -5,7 +5,7 @@ description: "NetworkPlugin、组件和系统的客户端使用指南"
## NetworkPlugin
NetworkPlugin 是客户端网络功能的核心入口。
NetworkPlugin 是客户端网络功能的核心入口,基于 `@esengine/rpc` 提供类型安全的网络通信
### 基本用法
@@ -18,7 +18,11 @@ const networkPlugin = new NetworkPlugin();
await Core.installPlugin(networkPlugin);
// 连接服务器
const success = await networkPlugin.connect('ws://localhost:3000', 'PlayerName');
const success = await networkPlugin.connect({
url: 'ws://localhost:3000',
playerName: 'PlayerName',
roomId: 'room-1' // 可选
});
// 断开连接
await networkPlugin.disconnect();
@@ -32,14 +36,19 @@ class NetworkPlugin {
readonly version: string;
// 访问器
get networkService(): NetworkService;
get networkService(): GameNetworkService;
get syncSystem(): NetworkSyncSystem;
get spawnSystem(): NetworkSpawnSystem;
get inputSystem(): NetworkInputSystem;
get localPlayerId(): number;
get isConnected(): boolean;
// 连接服务器
connect(serverUrl: string, playerName: string, roomId?: string): Promise<boolean>;
connect(options: {
url: string;
playerName: string;
roomId?: string;
}): Promise<boolean>;
// 断开连接
disconnect(): Promise<void>;
@@ -77,7 +86,7 @@ networkPlugin.registerPrefab('player', (scene, spawn) => {
const identity = entity.addComponent(new NetworkIdentity());
identity.netId = spawn.netId;
identity.ownerId = spawn.ownerId;
identity.bIsLocalPlayer = spawn.ownerId === networkPlugin.networkService.clientId;
identity.bIsLocalPlayer = spawn.ownerId === networkPlugin.localPlayerId;
return entity;
});
@@ -156,7 +165,7 @@ networkPlugin.registerPrefab('player', (scene, spawn) => {
const identity = entity.addComponent(new NetworkIdentity());
identity.netId = spawn.netId;
identity.ownerId = spawn.ownerId;
identity.bIsLocalPlayer = spawn.ownerId === networkPlugin.networkService.clientId;
identity.bIsLocalPlayer = spawn.ownerId === networkPlugin.localPlayerId;
entity.addComponent(new NetworkTransform());
@@ -230,18 +239,33 @@ class LocalInputHandler extends EntitySystem {
## 连接状态监听
使用 `GameNetworkService` 的链式 API 监听消息:
```typescript
networkPlugin.networkService.setCallbacks({
onConnected: (clientId, roomId) => {
console.log(`已连接: 客户端 ${clientId}, 房间 ${roomId}`);
},
onDisconnected: () => {
console.log('已断开');
// 处理重连逻辑
},
onError: (error) => {
console.error('网络错误:', error);
}
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)
});
```

View File

@@ -1,41 +1,38 @@
---
title: "网络同步系统 (Network)"
description: "基于 TSRPC 的多人游戏网络同步解决方案"
description: "基于 @esengine/rpc 的多人游戏网络同步解决方案"
---
`@esengine/network` 提供基于 TSRPC 的客户端-服务器网络同步解决方案,用于多人游戏的实体同步、输入处理和状态插值。
`@esengine/network` 提供基于 `@esengine/rpc` 的类型安全网络同步解决方案,用于多人游戏的实体同步、输入处理和状态插值。
## 概述
网络模块由三个包组成:
网络模块由两个核心包组成:
| 包名 | 描述 |
|------|------|
| `@esengine/network` | 客户端 ECS 插件 |
| `@esengine/network-protocols` | 共享协议定义 |
| `@esengine/network-server` | 服务器端实现 |
| `@esengine/rpc` | 类型安全的 RPC 通信库 |
| `@esengine/network` | 基于 RPC 的游戏网络插件 |
## 安装
```bash
# 客户端
npm install @esengine/network
# 服务器端
npm install @esengine/network-server
```
> `@esengine/rpc` 会作为依赖自动安装。
## 架构
```
客户端 服务器
┌────────────────┐ ┌────────────────┐
│ NetworkPlugin │◄──── WS ────► │ GameServer │
│ ├─ Service │ ├─ Room
│ ├─ SyncSystem │ └─ Players
│ ├─ SpawnSystem └────────────────┘
│ └─ InputSystem │
└────────────────┘
┌────────────────────┐ ┌────────────────┐
│ NetworkPlugin │◄── WS ───►│ RpcServer
│ ├─ NetworkService │ ├─ Protocol
│ ├─ SyncSystem │ └─ Handlers │
│ ├─ SpawnSystem └────────────────┘
│ └─ InputSystem
└────────────────────
```
## 快速开始
@@ -69,61 +66,81 @@ networkPlugin.registerPrefab('player', (scene, spawn) => {
const identity = entity.addComponent(new NetworkIdentity());
identity.netId = spawn.netId;
identity.ownerId = spawn.ownerId;
identity.bIsLocalPlayer = spawn.ownerId === networkPlugin.networkService.clientId;
identity.bIsLocalPlayer = spawn.ownerId === networkPlugin.localPlayerId;
entity.addComponent(new NetworkTransform());
return entity;
});
// 连接服务器
const success = await networkPlugin.connect('ws://localhost:3000', 'PlayerName');
const success = await networkPlugin.connect({
url: 'ws://localhost:3000',
playerName: 'Player1',
roomId: 'room-1' // 可选
});
if (success) {
console.log('Connected!');
console.log('Connected! Player ID:', networkPlugin.localPlayerId);
}
```
### 服务器端
```typescript
import { GameServer } from '@esengine/network-server';
import { RpcServer } from '@esengine/rpc/server';
import { gameProtocol } from '@esengine/network';
const server = new GameServer({
const server = new RpcServer(gameProtocol, {
port: 3000,
roomConfig: {
maxPlayers: 16,
tickRate: 20
}
onStart: (port) => console.log(`Server running on ws://localhost:${port}`)
});
// 注册 API 处理器
server.handle('join', async (input, ctx) => {
const playerId = generatePlayerId();
return { playerId, roomId: input.roomId ?? 'default' };
});
server.handle('leave', async (input, ctx) => {
// 处理玩家离开
});
await server.start();
console.log('Server started on ws://localhost:3000');
```
## 使用 CLI 快速创建
## 自定义协议
推荐使用 ESEngine CLI 快速创建完整的游戏服务端项目
你可以基于 `@esengine/rpc` 定义自己的协议
```bash
mkdir my-game-server && cd my-game-server
npm init -y
npx @esengine/cli init -p nodejs
```
```typescript
import { rpc } from '@esengine/rpc';
生成的项目结构:
// 定义自定义协议
export const myProtocol = rpc.define({
api: {
login: rpc.api<{ username: string }, { token: string }>(),
getData: rpc.api<{ id: number }, { data: object }>(),
},
msg: {
chat: rpc.msg<{ from: string; text: string }>(),
notification: rpc.msg<{ type: string; content: string }>(),
},
});
```
my-game-server/
├── src/
│ ├── index.ts
│ ├── server/
│ │ └── GameServer.ts
│ └── game/
│ ├── Game.ts
│ ├── scenes/
│ ├── components/
│ └── systems/
├── tsconfig.json
└── package.json
// 使用自定义协议创建服务
import { RpcService } from '@esengine/network';
const service = new RpcService(myProtocol);
await service.connect({ url: 'ws://localhost:3000' });
// 类型安全的 API 调用
const result = await service.call('login', { username: 'test' });
console.log(result.token);
// 类型安全的消息监听
service.on('chat', (data) => {
console.log(`${data.from}: ${data.text}`);
});
```
## 文档导航

View File

@@ -0,0 +1,165 @@
---
title: "RPC 通信库"
description: "类型安全的 WebSocket RPC 通信库"
---
`@esengine/rpc` 是一个轻量级、类型安全的 RPC远程过程调用专为游戏和实时应用设计。
## 特性
- **类型安全**:完整的 TypeScript 类型推断API 调用和消息处理都有类型检查
- **双向通信**:支持请求-响应API和发布-订阅(消息)两种模式
- **编解码灵活**:内置 JSON 和 MessagePack 编解码器
- **跨平台**支持浏览器、Node.js 和微信小游戏
## 安装
```bash
npm install @esengine/rpc
```
## 快速开始
### 1. 定义协议
```typescript
import { rpc } from '@esengine/rpc';
// 定义协议(共享于客户端和服务器)
export const chatProtocol = rpc.define({
api: {
// 请求-响应 API
login: rpc.api<{ username: string }, { userId: string; token: string }>(),
sendMessage: rpc.api<{ text: string }, { messageId: string }>(),
},
msg: {
// 单向消息(发布-订阅)
newMessage: rpc.msg<{ from: string; text: string; time: number }>(),
userJoined: rpc.msg<{ username: string }>(),
userLeft: rpc.msg<{ username: string }>(),
},
});
export type ChatProtocol = typeof chatProtocol;
```
### 2. 创建客户端
```typescript
import { RpcClient } from '@esengine/rpc/client';
import { chatProtocol } from './protocol';
// 创建客户端
const client = new RpcClient(chatProtocol, 'ws://localhost:3000', {
onConnect: () => console.log('已连接'),
onDisconnect: (reason) => console.log('已断开:', reason),
onError: (error) => console.error('错误:', error),
});
// 连接服务器
await client.connect();
// 调用 API类型安全
const { userId, token } = await client.call('login', { username: 'player1' });
console.log('登录成功:', userId);
// 监听消息(类型安全)
client.on('newMessage', (data) => {
console.log(`${data.from}: ${data.text}`);
});
// 发送消息
await client.call('sendMessage', { text: 'Hello!' });
```
### 3. 创建服务器
```typescript
import { RpcServer } from '@esengine/rpc/server';
import { chatProtocol } from './protocol';
// 创建服务器
const server = new RpcServer(chatProtocol, {
port: 3000,
onStart: (port) => console.log(`服务器启动: ws://localhost:${port}`),
});
// 注册 API 处理器
server.handle('login', async (input, ctx) => {
const userId = generateUserId();
const token = generateToken();
// 广播用户加入
server.broadcast('userJoined', { username: input.username });
return { userId, token };
});
server.handle('sendMessage', async (input, ctx) => {
const messageId = generateMessageId();
// 广播新消息
server.broadcast('newMessage', {
from: ctx.clientId,
text: input.text,
time: Date.now(),
});
return { messageId };
});
// 启动服务器
await server.start();
```
## 核心概念
### 协议定义
协议是客户端和服务器之间通信的契约:
```typescript
const protocol = rpc.define({
api: {
// API请求-响应模式,客户端发起,服务器处理并返回结果
methodName: rpc.api<InputType, OutputType>(),
},
msg: {
// 消息:发布-订阅模式,任意一方发送,对方监听
messageName: rpc.msg<DataType>(),
},
});
```
### API vs 消息
| 特性 | API | 消息 |
|------|-----|------|
| 模式 | 请求-响应 | 发布-订阅 |
| 返回值 | 有Promise | 无 |
| 确认 | 等待响应 | 发送即忘 |
| 用途 | 登录、查询、操作 | 实时通知、状态同步 |
## 编解码器
内置两种编解码器:
```typescript
import { json, msgpack } from '@esengine/rpc/codec';
// JSON默认可读性好
const client = new RpcClient(protocol, url, {
codec: json(),
});
// MessagePack二进制更高效
const client = new RpcClient(protocol, url, {
codec: msgpack(),
});
```
## 文档导航
- [客户端 API](/modules/rpc/client/) - RpcClient 详细 API
- [服务器 API](/modules/rpc/server/) - RpcServer 详细 API
- [编解码器](/modules/rpc/codec/) - 自定义编解码器