数据传输
详细接口文档
- 本文主要介绍基于多人联机 SDK 实现状态同步和帧同步的方法,以及它们的适用场景。
- 完整的接口说明和枚举、请求/响应结构体定义等,请阅读多人联机 API 参考。
状态同步
使用自定义消息在房间内同步玩家操作或状态,适合回合制、棋牌等对实时性要求不高的场景,流程可概括为:一方发送自定义消息 → 服务端转发 → 房间内对应玩家在回调中收到并解析更新游戏状态。

适用游戏类型
使用自定义消息进行状态同步,适合不需要高实时性数据更新的回合制或简单休闲游戏,例如:
- 棋牌类:五子棋、象棋、斗地主、麻将等
- 回合制:战棋、策略、回合制 RPG 等
- 休闲类:你画我猜、狼人杀、问答等
- 合作类:简单多人闯关、合作解谜等
发送自定义消息
调用限频
该接口与更新玩家自定义状态、更新玩家自定义属性、更新房间属性共享每秒 15 次调用限制。
- 将玩家操作或状态封装为 UTF-8 字符串(最大 2048 字节),通过
TapOnlineGame_AsyncSendCustomMessage发送;接收方在TapOnlineGameEventID_CustomMessageNotification中解析并更新游戏状态。
TapOnlineGameSendCustomMessageRequest req{};
req.msg = "{\"x\":1,\"y\":0}"; // 自定义格式,UTF-8,最大 2048 字节
req.receiver_type = 0; // 0-房间内所有人(不含发送者),1-指定玩家
req.receiver_count = 0; // receiver_type 为 1 时填写,最多 20 个
req.receivers = nullptr; // receiver_type 为 1 时填写玩家 ID 数组
TapOnlineGame_AsyncSendCustomMessage(TapOnlineGame(), ++gRequestID, &req);
// 结果在 TapOnlineGameEventID_SendCustomMessageResponse
- 指定玩家示例(receiver_type = 1):
const char* ids[] = { "player_id_1", "player_id_2" };
TapOnlineGameSendCustomMessageRequest req{};
req.msg = "{\"action\":\"ready\"}";
req.receiver_type = 1;
req.receiver_count = 2;
req.receivers = ids;
TapOnlineGame_AsyncSendCustomMessage(TapOnlineGame(), ++gRequestID, &req);
接收自定义消息
处理回调事件TapOnlineGameEventID_CustomMessageNotification,把event_data强转为TapOnlineGameCustomMessageNotification*,解析msg并更新对战状态。
case TapOnlineGameEventID_CustomMessageNotification: {
auto* n = static_cast<const TapOnlineGameCustomMessageNotification*>(event->event_data);
// n->player_id:发送者玩家 ID
// n->msg:自定义消息内容
// 解析 n->msg,渲染对战状态或其 他信息
break;
}
帧同步
高实时性游戏,如格斗、MOBA,可使用帧同步:房主开始帧同步后,各端通过发送帧输入同步操作,房主停止帧同步结束本局。

- 开始对战:房主调用开始帧同步,房间内所有玩家进入帧同步状态
- 对战进行中:玩家发送帧输入进行帧数据同步
- 结束对战:房主调用停止帧同步,或超时后服务端强制结束
使用限制
- 帧率 30 FPS
- 每玩家每帧最多发送 5 次操作数据
- 开始后最 长 30 分钟,超时服务端会强制结束
适用游戏类型
帧同步是针对高实时性竞技游戏采用的一种同步技术,主要用于需要实时进行数据同步的对战游戏,如格斗、MOBA(多人在线战术竞技类游戏)和FPS(第一人称射击类游戏)等。
开始帧同步(仅房主)
- 房间内玩家就绪后,房主调用开始帧同步
- 房间内所有玩家会收到
TapOnlineGameEventID_FrameSyncStartNotification,其中包含本次帧同步的room_info、frame_sync_id、seed(用于创建一致性随机数生成器)、server_tms等
TapOnlineGame_AsyncStartFrameSync(TapOnlineGame(), ++gRequestID);
// 结果在 TapOnlineGameEventID_StartFrameSyncResponse
- 处理开始帧同步通知(含随机种子):
case TapOnlineGameEventID_FrameSyncStartNotification: {
auto* n = static_cast<const TapOnlineGameFrameSyncStartNotification*>(event->event_data);
// n->room_info, n->frame_sync_id, n->seed, n->server_tms
// 使用 n->seed 初始化一致性随机数生成器,详见下文
break;
}
发送帧输入
- 对战过程中,可将玩家操作序列化为
UTF-8字符串(最大 1024 字节)发送 - 房间内所有玩家会在
TapOnlineGameEventID_FrameNotification中收到整帧数据(含多玩家输入)。
TapOnlineGameSendFrameInputRequest req{};
req.data = "{\"x\":1,\"y\":0}"; // UTF-8 字符串,最大 1024 字节
TapOnlineGame_AsyncSendFrameInput(TapOnlineGame(), ++gRequestID, &req);
// 结果在 TapOnlineGameEventID_SendFrameInputResponse
接收帧数据
- 回调事件为
TapOnlineGameEventID_FrameNotification,event_data为TapOnlineGameFrame*,包含当前帧 ID及本帧所有玩家的操作列表。 - 操作列表按服务端收到的顺序排序。
case TapOnlineGameEventID_FrameNotification: {
auto* frame = static_cast<const TapOnlineGameFrame*>(event->event_data);
// frame->id:当前帧 ID,从 1 递增
// frame->input_count:本帧玩家操作数量
// frame->inputs:TapOnlineGameFrameInput 数组,每条含 player_id、data、server_tms
for (uint32_t i = 0; i < frame->input_count; ++i) {
const TapOnlineGameFrameInput& input = frame->inputs[i];
// input.player_id, input.data, input.server_tms
}
break;
}
停止帧同步(仅房主)
- 房主可调用
停止帧同步来结束对战。 - 房间内所有人会收到
TapOnlineGameEventID_FrameSyncStopNotification,其中reason为 0 表示房主主动结束,1 表示因 30 分钟超时被强制结束。
TapOnlineGame_AsyncStopFrameSync(TapOnlineGame(), ++gRequestID);
// 结果在 TapOnlineGameEventID_StopFrameSyncResponse
一致性随机数
- 为保证各端随机结果一致(如掉落、暴击),应在收到
TapOnlineGameEventID_FrameSyncStartNotification后,使用其中的seed创建随机数生成器。 - 开发者可按需自行实现一致性随机数生成器,或使用本 SDK 提供的一致性随机数接口:
TapSDK_CreateRandomNumberGenerator(seed):创建生成器,返回生成器 IDTapSDK_RandomInt(rand_generator_id):生成随机整数TapSDK_DestroyRandomNumberGenerator(rand_generator_id):销毁生成器
建议在帧同步开始回调中创建,在对战结束或不再需要时销毁。详见一致性随机数生成器。
case TapOnlineGameEventID_FrameSyncStartNotification: {
auto* n = static_cast<const TapOnlineGameFrameSyncStartNotification*>(event->event_data);
int64_t randGenID = TapSDK_CreateRandomNumberGenerator(n->seed);
if (randGenID != 0) {
// 对战逻辑中使用 TapSDK_RandomInt(randGenID)
// 对战结束后 TapSDK_DestroyRandomNumberGenerator(randGenID);
}
break;
}