格斗游戏帧同步
本文面向 在您的产品中集成 xymj-colyseus-server 的开发者,说明如何在本框架约定的 game_room 上落地 1v1 / 小队格斗 类玩法:服务端提供 固定帧 Tick、统一收集 input、Schema 广播与重连契约;判定逻辑、碰撞箱、硬直表与客户端预测 / 回滚 由您在引擎侧实现,本仓库不绑定具体美术资源或玩法规则。
本框架为您承担的部分
| 能力 | 说明 |
|---|---|
| 帧驱动 | game_room 按配置的 FPS(默认 20,可在 app.config 的 define 中调整)执行 onFrameUpdate,每帧消费各席位的最新 input。 |
| 输入通道 | 客户端每逻辑帧或按操作发送 input,建议 body 含 inputs(任意 JSON 可序列化结构)与可选 frame;详见 帧同步与游戏房。 |
| 状态广播 | 经对局房间类(默认示例为 GameRoomExample)处理后,通过 Schema(MyRoomState 等)与消息(如 playerAction) 将结果同步到各端;具体字段以您二次开发后的实现为准。 |
| 入房与重连 | 经 匹配与开房 的 match:found 进入对局时,请携带 matchId、seatIndex、reconnectKey 与 JWT;断线窗口由 MATCH_RECONNECT_WINDOW_MS 控制。 |
您需要自行实现的部分
- 输入语义:将摇杆 / 按键映射为稳定、可版本化的结构(如
dir、buttons位掩码),并保证各端与回放使用同一套编码。 - 确定性与防作弊策略:锁步或回滚由客户端与工具链决定;服务端当前以 收齐每帧输入再推进 的模型为主,若需服裁或反作弊,请在对局房间类(如 fork 自
GameRoomExample)中扩展校验。 - 网络层体验:插值、预测、回滚、卡输入显示等均在 Cocos / Unity / Godot 等客户端完成;官方 Colyseus SDK 与 游戏引擎接入 中的示例仅作进房与发消息基线。
推荐集成路径
- 鉴权:HTTP 登录取 access token,进房 options 带
token(与全站约定一致,见 JWT 与房间鉴权)。 - 组局:使用
matchmaker_room,为格斗模式设置独立modeId(例如fighting_1v1),playersPerMatch: 2;或 Party 满 2 人由房主party:start。收到match:found后joinById进入game_room,并 合并joinOptions与token。 - 对局内:订阅
frameSync对齐逻辑帧;每帧向服务端发送input;处理playerOffline/reconnect:ok以支持断线重连与 UI 表现。 - 调参:若您需要更高操作解析度,可在不突破 Colyseus 与网络负载的前提下,在
app.config中提高game_room的 fps;请同步调整客户端 fixed timestep 与服务器负载评估。
建议的 input 结构(示例约定)
下表为 推荐字段形态,便于多引擎共享协议;非强制,您可扩展,但需保证各端与您服务端 processPlayerInput(默认示例见 GameRoomExample)一致。
| 字段 | 类型 | 说明 |
|---|---|---|
inputs.dir | { x: number, y: number } 或归一化向量 | 八方向 / 模拟摇杆,服务端可做量化(如 -1/0/1)以防浮点分歧。 |
inputs.buttons | number(位掩码) | 轻拳 / 中拳 / 重拳 / 脚 / 格挡等按位编码,避免字符串比对开销。 |
inputs.seq | number | 客户端单调递增序号,便于丢包检测与调试(可选)。 |
frame | number | 可选;若不传,服务端使用当前权威帧。 |
客户端示例:发送格斗向 input
以下示例假设您已取得 game(game_room 的 Room 实例);进房方式见 帧同步与游戏房 · 客户端示例代码 与 匹配与开房。
(客户端代码)
ts
// 建议:在本地 fixed timestep 中与渲染分离调用
const BTN_LIGHT = 1 << 0;
const BTN_MEDIUM = 1 << 1;
const BTN_HEAVY = 1 << 2;
const BTN_KICK = 1 << 3;
const BTN_GUARD = 1 << 4;
export function sendFightingInput(
game: import("colyseus.js").Room,
dirX: number,
dirY: number,
buttons: number,
clientSeq: number
) {
game.send("input", {
inputs: {
dir: { x: dirX, y: dirY },
buttons,
seq: clientSeq,
},
});
}
// 例:本帧按下轻拳 + 前
sendFightingInput(game, 1, 0, BTN_LIGHT, 42);csharp
// Unity / C#:在 FixedUpdate 或自研帧循环中调用
public static void SendFightingInput(
Colyseus.Room<DynamicSchema> game,
int dirX, int dirY,
int buttons,
int clientSeq)
{
game.Send("input", new Dictionary<string, object> {
{ "inputs", new Dictionary<string, object> {
{ "dir", new Dictionary<string, object> { { "x", dirX }, { "y", dirY } } },
{ "buttons", buttons },
{ "seq", clientSeq },
}},
});
}gdscript
const BTN_LIGHT := 1 << 0
func send_fighting_input(game: Colyseus.Room, dir_x: int, dir_y: int, buttons: int, seq: int) -> void:
game.send_message("input", {
"inputs": {
"dir": {"x": dir_x, "y": dir_y},
"buttons": buttons,
"seq": seq,
},
})来自框架侧的提示
若同一帧内多次调用 send,服务端通常只保留 该客户端在该帧窗口内合并策略下的有效输入(以 FrameSyncRoom / 当前对局房间实现为准)。格斗场景建议在客户端 每权威帧最多发一条 input,或在本地合并按键状态后再发送。
与演示页的关系
服务端静态页 FrameSync.html(仓库 src/public)用于快速验证 连接、input、帧事件;格斗专用手感与命中请在您的产品工程中迭代。
延伸阅读
| 主题 | 入口 |
|---|---|
| 帧同步契约与消息表 | 帧同步与游戏房 |
匹配 modeId、人数与 Redis | 匹配与开房、多 V 多自动匹配 |
| Schema 状态字段 | 状态同步(Schema) |
| 全量消息名 | 消息协议 |
若您在二次开发中扩展了对局房间的状态或下行消息名,请在团队内维护一份 与本文档并列的「项目协议增量」,便于客户端与 QA 对齐版本。