跳到主要内容
版本:v4

UE5 SDK 开发指南

本文介绍如何在 UE5 项目中接入多人联机,覆盖 Android、iOS 和 Tap PC。

集成 SDK​

集成前准备​

  1. 参考 开发者中心配置 创建应用、配置包名与签名证书
  2. 参考 UE5 集成指南完成 TapSDK 基础接入
  3. 多人联机 SDK 依赖 TapTap 登录开发指南 ,接入前需确保已接入

在开发者中心开通多人联机并配置房间类型。等待初始化和登录成功后,再连接多人联机服务。

权限说明​

Android 权限说明​

该模块在 Android 平台需要如下权限:

权限使用目的权限申请时机
网络权限用于正常网络访问用户首次使用该功能时会申请权限

iOS 权限说明​

该模块在 iOS 平台需要如下权限:

权限使用目的权限申请时机
网络权限用于正常网络访问用户首次使用该功能时会申请权限

SDK 获取​

参考 UE5 集成指南完成 TapCore 基础接入。再从下载页下载相同版本的 TapLogin、TapBattle,放入项目的 Plugins 目录并启用。

添加模块依赖​

在游戏模块的 <GameModule>.Build.cs 中添加:

PublicDependencyModuleNames.AddRange(new string[]
{
"TapCore",
"TapLogin",
"TapBattle",
});

修改 Build.cs 后重新生成项目文件并编译。

业务流程接入​

主要业务模块及流程如下图,开发者可根据该图了解不同功能接口调用时机:

TapTap

连接管理​

注册回调​

通过实现 SDK 通知回调接口 ITapOnlineBattleCallback 来接收玩家、房间信息及同步消息回调,渲染对应场景游戏页面,示例如下:

#pragma once
#include "CoreMinimal.h"
#include "TapBattle.h"
#include "GameBattleHandler.generated.h"

UCLASS()
class UGameBattleHandler : public UObject, public ITapOnlineBattleCallback
{
GENERATED_BODY()
public:
virtual void OnDisconnected_Implementation(int32 Code, const FString& Message) override
{
UE_LOG(LogTemp, Warning, TEXT("Disconnected [%d]: %s"), Code, *Message);
// 停止发送数据,显示断线状态,重新连接成功后再恢复业务。
}
virtual void OnBattleServiceError_Implementation() override
{
// 服务异常,提示玩家并停止当前对战操作。
}
virtual void OnPlayerEntered_Implementation(
const FString& RoomId, const FTapBattlePlayerInfo& PlayerInfo) override
{
// 将 PlayerInfo 加入房间 UI;后续状态变化通过对应事件更新。
}
virtual void OnPlayerLeft_Implementation(
const FString& RoomId, const FString& RoomOwnerId, const FString& PlayerId) override
{
// 移除 PlayerId,并用 RoomOwnerId 更新房主。
}
virtual void OnPlayerOffline_Implementation(
const FString& RoomId, const FString& RoomOwnerId, const FString& PlayerId) override
{
// 标记玩家离线,并更新房主。
}
virtual void OnPlayerKicked_Implementation(const FString& RoomId, const FString& PlayerId) override
{
// 更新房间成员;本机被踢出时返回大厅。
}
virtual void OnRoomPropertiesChanged_Implementation(
const FString& RoomId, const FString& Name, const FString& CustomProperties) override
{
// 更新房间名称和自定义属性。
}
virtual void OnPlayerCustomStatusChanged_Implementation(const FString& PlayerId, int32 Status) override
{
// 按游戏约定更新准备状态等 UI。
}
virtual void OnPlayerCustomPropertiesChanged_Implementation(
const FString& PlayerId, const FString& Properties) override
{
// 按游戏约定解析并更新玩家属性。
}
virtual void OnCustomMessageReceived_Implementation(
const FString& PlayerId, const FString& Message) override
{
// 校验并解析 Message,应用 PlayerId 的操作。
}
virtual void OnFrameSyncStarted_Implementation(const FTapBattleFrameSyncInfo& FrameSyncInfo) override
{
// 保存 RoomInfo、FrameSyncId、Seed、ServerTime,进入对战状态。
}
virtual void OnFrameReceived_Implementation(const FTapBattleFrameData& FrameData) override
{
// 按 FrameData.Id 顺序处理 Inputs 中各玩家的数据。
for (const FTapBattlePlayerInputInfo& Input : FrameData.Inputs)
{
// Input.PlayerId 为玩家 ID,Input.Data 为操作数据,Input.ServerTime 为发送时间
UE_LOG(LogTemp, Log, TEXT("Frame %d player %s: %s"), FrameData.Id, *Input.PlayerId, *Input.Data);
}
}
virtual void OnFrameSyncStopped_Implementation(
const FString& RoomId, int32 FrameSyncId, int32 Reason) override
{
// Reason:0 为房主主动结束,1 为 30 分钟超时。停止发送帧输入。
}
};

在持有它的 GameInstance 等 UObject 中声明成员。初始化和登录成功后创建并注册,不再接收事件时使用同一个对象注销:

#include "TapBattle.h"

// 在头文件中声明成员,并将 GameBattleHandler.h 放在该类的生成头文件之前
UPROPERTY()
TObjectPtr<UGameBattleHandler> BattleHandler;

// 初始化和登录成功后创建并注册
BattleHandler = NewObject<UGameBattleHandler>(this);
TScriptInterface<ITapOnlineBattleCallback> BattleCallback;
BattleCallback.SetObject(BattleHandler.Get());
BattleCallback.SetInterface(Cast<ITapOnlineBattleCallback>(BattleHandler.Get()));
UTapBattle::RegisterOnlineBattleCallback(BattleCallback);

建立连接​

当玩家登录成功后,需建立连接后才能调用其他主要 API 业务接口,连接成功时会返回用户 ID 。示例如下:

#include "TapBattle.h"

UTapBattle::Connect(
FTapOnBattlePlayerIdResult::CreateLambda([](const FString& PlayerId)
{
UE_LOG(LogTemp, Log, TEXT("Connected: %s"), *PlayerId);
// 保存 PlayerId,用于和房间 OwnerId 比较,判断本机是否为房主
}),
FTapOnBattleError::CreateLambda([](int32 Code, const FString& Message)
{
UE_LOG(LogTemp, Error, TEXT("Connect failed [%d]: %s"), Code, *Message);
}));

断开连接​

当用户不再需要使用对战服务时,可调用该接口断开连接。当用户登出、重新登录时,需要自行调用 Disconnect。

UTapBattle::Disconnect(
FTapOnBattleSuccess::CreateLambda([]()
{
UE_LOG(LogTemp, Log, TEXT("Disconnected"));
// 停止发送数据,返回大厅
}),
FTapOnBattleError::CreateLambda([](int32 Code, const FString& Message)
{
UE_LOG(LogTemp, Error, TEXT("Disconnect failed [%d]: %s"), Code, *Message);
}));
// 确认不再需要事件后,用注册时的同一对象注销:
TScriptInterface<ITapOnlineBattleCallback> BattleCallback;
BattleCallback.SetObject(BattleHandler.Get());
BattleCallback.SetInterface(Cast<ITapOnlineBattleCallback>(BattleHandler.Get()));
UTapBattle::UnregisterOnlineBattleCallback(BattleCallback);

房间管理​

房间流程

  • 房间:玩家联机的虚拟空间,每个房间有唯一的房间 ID
  • 房主:创建房间的玩家,拥有踢人、修改房间属性等权限;房主离开后,服务端会自动选择房间内一个玩家为新房主
  • 监听回调:玩家进入或离开房间时,SDK 会通过回调通知房间内其他玩家

房间信息​

当通过不同方式进入房间后,SDK 会返回房间内具体信息,包括房间属性和房间内所有玩家属性,具体参数如下:

参数名类型说明
IdFString房间 ID
NameFString房间名称,如果创建或更新时未设置,默认为空字符串
TypeFString房间类型
OwnerIdFString房主 ID ,当原房主离开房间且房间内有其他玩家时,该值会变更为房间内其他玩家
Statusint320-未开始帧同步,1-帧同步中
CustomPropertiesFString自定义房间属性
MaxPlayerCountint32房间最大支持人数
PlayersTArray<FTapBattlePlayerInfo>房间内玩家信息集合
CreateTimeFString房间创建时间,1970 年开始的秒数

其中玩家信息如下:

参数名类型说明
IdFString玩家 ID
Statusint32玩家状态:0-离线、1-在线
CustomStatusint32自定义玩家状态,开发者可随意设置任何值,意义由开发者自行判断
CustomPropertiesFString自定义玩家属性,开发者可以随意设置任何数据,最大为 2048 字节

进入房间​

进入房间有三种不同的方式,创建房间、匹配房间以及获取房间列表后加入指定房间等,通过创建房间加入时会自动成为房主。

创建房间​

在创建和匹配房间时,需要设置房间配置(必选)和玩家配置(可选),具体参数如下:

房间配置​
参数名类型说明是否必选
MaxPlayerCountint32房间最大支持人数,取值范围为 1 ~ 20是
TypeFString房间类型,最大 32 字节是
NameFString房间名称,最大 64 字节,当匹配房间时,无需设置该参数否
CustomPropertiesFString自定义房间属性,最大 2048 字节否
MatchParamsTMap<FString, FString>房间匹配参数,最大支持 3 个 K/V 对, K/V 取值由开发者决定,K 最大 32 字节,V 最大 64 字节否
玩家配置​
参数名类型说明是否必选
CustomStatusint32自定义玩家状态,开发者可随意设置任何值,意义由开发者自行判断否
CustomPropertiesFString自定义玩家属性,开发者可以随意设置任何数据,最大为 2048 字节否

请求成功后,会返回对应房间的具体信息,示例如下:

// 定义匹配参数
TMap<FString, FString> MatchParams;
// 定义房间配置
FTapBattleCreateRoomConfig Config;
Config.RoomConfig.MaxPlayerCount = RoomMaxPlayerCount; // 房间最大支持人数,取值范围为[1, 20],必填
Config.RoomConfig.Type = RoomType; // 房间类型,最大32字节,必填
Config.RoomConfig.Name = RoomName; // 房间名称,最大64个字节,选填
Config.RoomConfig.CustomProperties = RoomProperty; // 自定义房间属性,最大2048字节,选填
Config.RoomConfig.MatchParams = MatchParams; // 房间匹配参数,选填,最大支持3个K/V对, K/V取值由开发者决定,K最大32字节,V最大64字节
// 玩家配置
Config.PlayerConfig.CustomStatus = PlayerCustomStatus; // 自定义玩家状态,整型,选填
Config.PlayerConfig.CustomProperties = PlayerProperty; // 自定义玩家属性,选填

UTapBattle::CreateRoom(
Config,
FTapOnBattleRoomResult::CreateLambda([](const FTapBattleRoomInfo& Room)
{
UE_LOG(LogTemp, Log, TEXT("Room: %s"), *Room.Id);
// 使用返回的房间信息进入房间界面
}),
FTapOnBattleError::CreateLambda([](int32 Code, const FString& Message)
{
UE_LOG(LogTemp, Error, TEXT("Create room failed [%d]: %s"), Code, *Message);
}));

匹配房间​

匹配房间时使用的配置参数和创建房间一致,当没有满足条件的房间存在时,会自动创建一个新房间, 匹配成功后返回对应的房间信息。

// 定义匹配参数
TMap<FString, FString> MatchParams;
// 定义房间配置
FTapBattleMatchRoomConfig Config;
Config.RoomConfig.MaxPlayerCount = RoomMaxPlayerCount; // 房间最大支持人数,取值范围为[1, 20],必填
Config.RoomConfig.Type = RoomType; // 房间类型,最大32字节,必填
Config.RoomConfig.CustomProperties = RoomProperty; // 自定义房间属性,最大2048字节,选填
Config.RoomConfig.MatchParams = MatchParams; // 房间匹配参数,选填,最大支持3个K/V对, K/V取值由开发者决定,K最大32字节,V最大64字节
// 玩家配置
Config.PlayerConfig.CustomStatus = PlayerCustomStatus; // 自定义玩家状态,整型,选填
Config.PlayerConfig.CustomProperties = PlayerProperty; // 自定义玩家属性,选填

UTapBattle::MatchRoom(
Config,
FTapOnBattleRoomResult::CreateLambda([](const FTapBattleRoomInfo& Room)
{
UE_LOG(LogTemp, Log, TEXT("Matched room: %s"), *Room.Id);
// 使用返回的房间信息进入房间界面
}),
FTapOnBattleError::CreateLambda([](int32 Code, const FString& Message)
{
UE_LOG(LogTemp, Error, TEXT("Match room failed [%d]: %s"), Code, *Message);
}));

如果此时是加入已存在的房间,房间内其他玩家会通过 SDK 的 OnPlayerEntered 回调收到通知:

virtual void OnPlayerEntered_Implementation(
const FString& RoomId, const FTapBattlePlayerInfo& PlayerInfo) override
{
// 将 PlayerInfo 加入房间 UI;后续状态变化通过对应事件更新。
}

获取房间列表​

开发者可通过具体房间类型获取对应房间列表数据,示例如下:

UTapBattle::GetRoomList(
RoomType, // 房间类型
Offset, // 请求偏移量,默认为 0
Limit, // 请求获取的房间数量,默认为 20
FTapOnBattleRoomListResult::CreateLambda([](const FTapBattleRoomListData& Data)
{
UE_LOG(LogTemp, Log, TEXT("Rooms: %d, has more: %d"), Data.Rooms.Num(), Data.bHasMore);
// 使用 Data.Rooms 刷新房间列表;Data.bHasMore 为 true 时,用 Data.Offset 请求下一页
}),
FTapOnBattleError::CreateLambda([](int32 Code, const FString& Message)
{
UE_LOG(LogTemp, Error, TEXT("Get rooms failed [%d]: %s"), Code, *Message);
}));

首次请求时,请求参数中 Offset 传 0 即可。拉取下一页房间列表时,请传递上一次请求结果中的 Offset 字段的值。 返回结果 FTapBattleRoomListData 包含三个字段 :

  • Offset:用于请求下一页房间列表的 Offset 值
  • bHasMore:表示是否还有更多房间,true 表示有更多房间,false 表示没有
  • Rooms: 房间列表数据,包含对应类型房间的信息集合

Rooms 中各房间信息字段如下:

参数名类型说明
IdFString房间 ID
NameFString房间名称,如果创建或更新时未设置,默认为空字符串
TypeFString房间类型
CustomPropertiesFString自定义房间属性
MaxPlayerCountint32房间最大支持人数
PlayerCountint32房间内当前玩家人数
CreateTimeFString房间创建时间,1970 年开始的秒数

加入房间​

使用目标房间的房间 ID 可加入对应房间,成功后,会返回包含所有玩家的具体房间信息。示例如下:

// 玩家配置
FTapBattlePlayerConfig PlayerConfig;
PlayerConfig.CustomStatus = JoinPlayerStatus; // 自定义玩家状态,整形,选填。开发者可随意设置任何值,意义由开发者自行判断
PlayerConfig.CustomProperties = JoinPlayerProperty; // 自定义玩家属性,选填。开发者可以随意设置任何数据,最大长度为2048字节

UTapBattle::JoinRoom(
JoinRoomId, // 房间ID
PlayerConfig, // 玩家配置,选填
FTapOnBattleRoomResult::CreateLambda([](const FTapBattleRoomInfo& Room)
{
UE_LOG(LogTemp, Log, TEXT("Joined room: %s"), *Room.Id);
// 使用返回的房间信息进入房间界面
}),
FTapOnBattleError::CreateLambda([](int32 Code, const FString& Message)
{
UE_LOG(LogTemp, Error, TEXT("Join room failed [%d]: %s"), Code, *Message);
}));

加入成功后其他玩家也会通过 OnPlayerEntered 回调收到通知:

virtual void OnPlayerEntered_Implementation(
const FString& RoomId, const FTapBattlePlayerInfo& PlayerInfo) override
{
// 将 PlayerInfo 加入房间 UI;后续状态变化通过对应事件更新。
}

管理房间​

更新房间信息​

只有房主可更新当前房间信息,包含名称和自定义属性等,示例如下:

UTapBattle::UpdateRoomProperties(
UpdateRoomName, // 房间名称
UpdateRoomProperty, // 房间自定义属性,最大支持 2048 字节
FTapOnBattleSuccess::CreateLambda([]()
{
UE_LOG(LogTemp, Log, TEXT("Update room success"));
// 房间内玩家会通过 OnRoomPropertiesChanged 收到新的房间信息
}),
FTapOnBattleError::CreateLambda([](int32 Code, const FString& Message)
{
UE_LOG(LogTemp, Error, TEXT("Update room failed [%d]: %s"), Code, *Message);
}));

更新成功后所有玩家会通过 SDK 的 OnRoomPropertiesChanged 回调收到通知:

virtual void OnRoomPropertiesChanged_Implementation(
const FString& RoomId, const FString& Name, const FString& CustomProperties) override
{
// 更新房间名称和自定义属性。
}

更新玩家自定义状态​

每个玩家均可更新自身的当前自定义状态,用于标识玩家在房间或对战过程中的关键状态,例如未就绪、已就绪等,示例如下:

UTapBattle::UpdatePlayerCustomStatus(
UpdatePlayerStatus, // 玩家自定义状态,例如 0 未就绪 1 已就绪
FTapOnBattleSuccess::CreateLambda([]()
{
UE_LOG(LogTemp, Log, TEXT("Update player status success"));
// 房间内玩家会通过 OnPlayerCustomStatusChanged 收到新的状态
}),
FTapOnBattleError::CreateLambda([](int32 Code, const FString& Message)
{
UE_LOG(LogTemp, Error, TEXT("Update player status failed [%d]: %s"), Code, *Message);
}));

更新成功后,房间内玩家会通过 SDK 的 OnPlayerCustomStatusChanged 回调收到通知:

virtual void OnPlayerCustomStatusChanged_Implementation(const FString& PlayerId, int32 Status) override
{
// 按游戏约定更新准备状态等 UI。
}

更新玩家自定义属性​

每个玩家均可更新自身的自定义属性数据,可用于设置玩家的基础用户信息、游戏等级等数据,示例如下:

UTapBattle::UpdatePlayerCustomProperties(
UpdatePlayerProperty, // 玩家属性,最大支持 2048 字节
FTapOnBattleSuccess::CreateLambda([]()
{
UE_LOG(LogTemp, Log, TEXT("Update player properties success"));
// 房间内玩家会通过 OnPlayerCustomPropertiesChanged 收到新的属性
}),
FTapOnBattleError::CreateLambda([](int32 Code, const FString& Message)
{
UE_LOG(LogTemp, Error, TEXT("Update player properties failed [%d]: %s"), Code, *Message);
}));

更新成功后,房间内玩家会通过 SDK 的 OnPlayerCustomPropertiesChanged 回调收到通知:

virtual void OnPlayerCustomPropertiesChanged_Implementation(
const FString& PlayerId, const FString& Properties) override
{
// 按游戏约定解析并更新玩家属性。
}

离开房间​

当房间内玩家需要离开当前房间时,可调用如下接口:

UTapBattle::LeaveRoom(
FTapOnBattleSuccess::CreateLambda([]()
{
UE_LOG(LogTemp, Log, TEXT("Leave room success"));
// 返回大厅;房间内其他玩家会通过 OnPlayerLeft 收到通知
}),
FTapOnBattleError::CreateLambda([](int32 Code, const FString& Message)
{
UE_LOG(LogTemp, Error, TEXT("Leave room failed [%d]: %s"), Code, *Message);
}));

调用成功后,房间内其他玩家通过 SDK 的 OnPlayerLeft 收到通知,如果离开的玩家是房主,则会重新指定房间内其他玩家为房主。

踢出玩家​

当房主需要将玩家踢出房间时,可调用如下接口:

UTapBattle::KickRoomPlayer(
KickPlayerId, // 玩家 ID
FTapOnBattleSuccess::CreateLambda([]()
{
UE_LOG(LogTemp, Log, TEXT("Kick player success"));
// 被踢玩家会通过 OnPlayerKicked 收到通知
}),
FTapOnBattleError::CreateLambda([](int32 Code, const FString& Message)
{
UE_LOG(LogTemp, Error, TEXT("Kick player failed [%d]: %s"), Code, *Message);
}));

调用成功后,对应玩家会通过 SDK 的 OnPlayerKicked 回调收到通知:

virtual void OnPlayerKicked_Implementation(const FString& RoomId, const FString& PlayerId) override
{
// 更新房间成员;本机被踢出时返回大厅。
}

房间内数据传输​

进入房间后,可通过发送自定义消息或发送帧输入(房主开启帧同步后)同步游戏数据。

  • 自定义消息:适合回合制、棋牌等对实时性要求不高的场景
  • 帧同步:适合格斗、MOBA、FPS 等高实时性游戏

详细用法见数据传输。