Skip to content

格斗游戏帧同步

本文面向 在您的产品中集成 xymj-colyseus-server 的开发者,说明如何在本框架约定的 game_room 上落地 1v1 / 小队格斗 类玩法:服务端提供 固定帧 Tick、统一收集 input、Schema 广播与重连契约判定逻辑、碰撞箱、硬直表与客户端预测 / 回滚 由您在引擎侧实现,本仓库不绑定具体美术资源或玩法规则。

本框架为您承担的部分

能力说明
帧驱动game_room 按配置的 FPS(默认 20,可在 app.configdefine 中调整)执行 onFrameUpdate,每帧消费各席位的最新 input
输入通道客户端每逻辑帧或按操作发送 input,建议 body 含 inputs(任意 JSON 可序列化结构)与可选 frame;详见 帧同步与游戏房
状态广播经对局房间类(默认示例为 GameRoomExample)处理后,通过 Schema(MyRoomState 等)与消息(如 playerAction) 将结果同步到各端;具体字段以您二次开发后的实现为准。
入房与重连匹配与开房match:found 进入对局时,请携带 matchIdseatIndexreconnectKey 与 JWT;断线窗口由 MATCH_RECONNECT_WINDOW_MS 控制。

您需要自行实现的部分

  • 输入语义:将摇杆 / 按键映射为稳定、可版本化的结构(如 dirbuttons 位掩码),并保证各端与回放使用同一套编码。
  • 确定性与防作弊策略:锁步或回滚由客户端与工具链决定;服务端当前以 收齐每帧输入再推进 的模型为主,若需服裁或反作弊,请在对局房间类(如 fork 自 GameRoomExample)中扩展校验。
  • 网络层体验:插值、预测、回滚、卡输入显示等均在 Cocos / Unity / Godot 等客户端完成;官方 Colyseus SDK 与 游戏引擎接入 中的示例仅作进房与发消息基线。

推荐集成路径

  1. 鉴权:HTTP 登录取 access token,进房 options 带 token(与全站约定一致,见 JWT 与房间鉴权)。
  2. 组局:使用 matchmaker_room,为格斗模式设置独立 modeId(例如 fighting_1v1),playersPerMatch: 2;或 Party 满 2 人由房主 party:start。收到 match:foundjoinById 进入 game_room,并 合并 joinOptionstoken
  3. 对局内:订阅 frameSync 对齐逻辑帧;每帧向服务端发送 input;处理 playerOffline / reconnect:ok 以支持断线重连与 UI 表现。
  4. 调参:若您需要更高操作解析度,可在不突破 Colyseus 与网络负载的前提下,在 app.config 中提高 game_roomfps;请同步调整客户端 fixed timestep 与服务器负载评估。

建议的 input 结构(示例约定)

下表为 推荐字段形态,便于多引擎共享协议;非强制,您可扩展,但需保证各端与您服务端 processPlayerInput(默认示例见 GameRoomExample)一致。

字段类型说明
inputs.dir{ x: number, y: number } 或归一化向量八方向 / 模拟摇杆,服务端可做量化(如 -1/0/1)以防浮点分歧。
inputs.buttonsnumber(位掩码)轻拳 / 中拳 / 重拳 / 脚 / 格挡等按位编码,避免字符串比对开销。
inputs.seqnumber客户端单调递增序号,便于丢包检测与调试(可选)。
framenumber可选;若不传,服务端使用当前权威帧。

客户端示例:发送格斗向 input

以下示例假设您已取得 gamegame_roomRoom 实例);进房方式见 帧同步与游戏房 · 客户端示例代码匹配与开房

(客户端代码)

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 对齐版本。