Realtime SDK接入指南
Realtime SDK接入指南
1. 安装配置 UOS Launcher
参考 Launcher 教程,安装 Launcher 后,关联 UOS APP, 开启 Multiverse 服务并安装 Multiverse SDK。
注意: 请务必确认在进行后续教程之前,已经成功完成了 Launcher 教程的安装步骤,否则可能导致后续接入无法顺利进行。
2.Sync Realtime Demo
该项目是基于 Sync Realtime 开发的一个支持多人同房间信息同步的示例程序, 展示了 SDK 基本接口的使用方式。
2.1.导入项目素材资源包
在 UOS Launcher 中点击「import sample」按钮,即可导入项目示例工程资源包。
2.2.打开 Sample 场景
导入资源包后,在 Assets/Samples/UOS Sync Realtime/X.Y.Z/Sync Realtime Sample/Scenes/Scene.unity 路径下(其中 X.Y.Z 为已安装的 Sync - Relay SDK 的版本号),可以看到示例的 SampleScene 场景并打开。
打开场景后,在弹出的窗口中,点击「Import TMP Essentials」来导入 TextMeshPro 的字体资源。
打开场景后,填写 MuninnNetworkManager 游戏对象中 RoomProfileUUID 信息。点击运行项目,创建房间。
注: 此处 RoomProfileUUID 如何获取
此处的 RoomProfileUUID 可在 UOS「Sync -> 配置」处的房间列表项内获取

你也可以点击以下链接在线体验,或者查看相关的示例程序。
3.核心类&接口
MuninnNetwork 接口
MuninnNetwork 是 Sync 为客户端提供的调用接口。
public class MuninnNetwork
{
// 创建并加入房间
public static void CreateAndJoinRoom(CreateRoomRequest request);
// 创建或加入房间(若房间存在则直接加入)
public static void CreateOrJoinRoom(CreateOrJoinRoomRequest request);
// 通过房间roomUUID加入房间,如果为私有房间需要提供joinCode
public static void JoinRoomByUUID(string roomUUID, string joinCode = "");
// 通过roomCode加入房间,如果为私有房间需要提供joinCode
public static void JoinRoomByRoomCode(string roomCode, string joinCode = "")
// 通过roomName加入房间,如果为私有房间需要提供joinCode (使用前请确保在UOS管理页面开启全局房间名称唯一功能,否则不保证结果的唯一性)
public static void JoinRoomByRoomName(string roomName, string joinCode = "")
// 获取房间列表
public static void ListRooms(ListRoomRequest request, Action<ListRoomResponse> callback);
// 主动离开房间
public static void LeaveRoom();
// 主动关闭连接
public static void Disconnect();
// 关闭房间
public static void CloseRoom(string roomUUID, Action<CloseRoomResponse> callback);
// 发送消息
public static void RaiseEvent(byte[] data, RaiseEventOptions options);
// 订阅兴趣组
// 兴趣组范围是[1, 255]之间, 0 是系统默认保留(不需要订阅)
public static void SubscribeGroups(List<uint> groups);
// 退订兴趣组
public static void UnsubscribeGroups(List<uint> groups);
// 添加 CachedEvent
public static void AddCachedEvent(string tag, byte[] data, Action<MuninnAddCachedEventResponse> callback);
// 移除 CachedEvent
public static void RemoveCachedEvent(string tag, Action<MuninnRemoveCachedEventResponse> callback);
// 设置 StickyEvent
public static void SetStickyEvent(string key, byte[] data, StickyEventScope scope, Action<MuninnSetStickyEventResponse> callback);
// 移除 StickyEvent
public static void RemoveStickyEvent(string key, StickyEventScope scope, Action<MuninnRemoveStickyEventResponse> callback);
// 查询 StickyEvent
public static void GetStickyEvent(string key, StickyEventScope scope, Action<MuninnGetStickyEventResponse> callback);
// 远程调用 MasterClient 上的服务端逻辑
public static void ServerCall(byte[] data);
// 踢走玩家
public static void KickPlayer(uint senderId);
// 更新玩家的属性
public static void UpdatePlayerInfo(MuninnPlayer player, Action<MuninnUpdatePlayerResponse> callback);
// 更新房间自定义属性
public static void UpdateRoomCustomProperties(Dictionary<string, string> properties, Action<MuninnUpdateRoomCustomPropertiesResponse> callback);
// 更新房间属性,目前仅支持更新 Visibility、JoinCode、RoomName(房间名) 和 MaxPlayers (这个需要注意当前房间的人数)
public static void UpdateRoom(string roomUUID, UpdateRoomRequest request, Action<UpdateRoomResponse> callback)
// 更新房间状态,仅支持更新 Ready 和 Running 状态
public static void ChangeRoomStatus(string roomUUID, MuninnLobbyRoomStatus status, Action<ChangeRoomStatusResponse> callback)
}MuninnBehaviour 接口
MuninnBehaviour 是 Sync 回调客户端的接口。开发者需要根据自己游戏逻辑的需要去实现这些接口。
public class MuninnBehaviour : MonoBehaviour
{
// 成功加入房间
public virtual void OnJoinedRoom(MuninnRoomView roomView);
// 加入房间失败
public virtual void OnJoinRoomFailed(MuninnError error);
// 有新玩家进入房间时通知
public virtual void OnPlayerEnteredRoom(MuninnPlayer player);
// 有玩家离开房间时通知
public virtual void OnPlayerLeftRoom(MuninnPlayer player);
// 成功离开房间
public virtual void OnLeftRoom(LeaveRoomEvent leaveRoomEvent);
// 连接关闭
public virtual void OnDisconnected();
// 收到消息
public virtual void OnEvent(MuninnEvent muninnEvent);
// 成功订阅兴趣组
public virtual void OnSubscribeGroups(MuninnSubscribeGroupResponse rsp);
// 订阅兴趣组失败
public virtual void OnSubscribeGroupsFailed(MuninnError error);
// 成功退订兴趣组
public virtual void OnUnsubscribeGroups(MuninnUnsubscribeGroupsResponse rsp);
// 退订兴趣组失败
public virtual void OnUnsubscribeGroupsFailed(MuninnError error);
// 收到Server Call请求
public virtual void OnServerCall(MuninnEvent e);
// MasterClient发生变化
public virtual void OnMasterClientChanged(uint masterClientId);
// 成功踢走玩家
public virtual void OnKickedPlayer(MuninnKickPlayerResponse rsp);
// 踢走玩家失败
public virtual void OnKickPlayerFailed(MuninnError error);
// 玩家属性更新通知
public virtual void OnPlayerInfoUpdated(MuninnPlayer player);
// 房间自定义属性更新通知
public virtual void OnRoomCustomPropertiesUpdated(Dictionary<string, string> customProperties);
// 与房间建立连接之前
public virtual void BeforeConnectRoom(JoinRoomResponse resp);
}MuninnRoomView(房间动态信息) 接口
MuninnRoomView 维护了房间的基本信息+动态信息(只读)。
public class MuninnRoomView
{
// 房间的静态数据
public MuninnRoom Room { set; get;}
// 房间的动态数据
// 自己在房间内的唯一ID
public uint SenderId {get;}
// 房间内MasterClient的ID
public uint MasterClientId {get;}
// 自己的信息
public MuninnPlayer Self() {get;}
// 自己是不是MasterClient
public bool IsMasterClient();
// 房间里的在线用户
public List<MuninnPlayer> Players {get;}
public MuninnPlayer GetPlayerBySenderId(uint senderId);
public MuninnPlayer GetPlayerByUniqueId(string uniqueId);
// 获取CachedEvent列表
public List<MuninnCachedEvent> RoomCachedEvents {get;}
// 获取自己订阅过的群组
public List<uint> MyGroups {get;}
}4.集成示例
MuninnBehaviour
public partial class MuninnBehaviour : MonoBehaviour
{
// 子类覆盖时,需要调用 base.Awake
public virtual void Awake()
{
SetupMuninnCallbacks();
}
// 子类覆盖时,需要调用 base.Update
public virtual void Update()
{
MuninnUpdate();
}
// 子类覆盖时,需要调用 base.OnDestroy
public virtual void OnDestroy()
{
// ...
}
}
// 继承MuninnBehaviour类
class MultiPlayControllerSimple : MuninnBehaviour
{
}配置信息并连接
注:此处 AppId、AppSecret 和 RoomProfileUUID,可在 UOS 网站上获取


此处的 RoomProfileUUID 可在 UOS「Sync -> 配置」处的房间列表项内获取
class MultiPlayControllerSimple : MuninnBehaviour
{
public void Start()
{
// 请在加入房间前执行
// 配置UOS App信息
MuninnSettings.UosAppId = "<AppId>";
MuninnSettings.UosAppSecret = "<AppSecret>";
MuninnSettings.RoomProfileUUID = "<RoomProfileUUID>";
// 支持WebSocket/WebSocketSecure/KCP/UTP, 默认为UTP
MuninnSettings.TransportType = MuninnTransportType.WebSocket;
// 开启开发模式, 默认不开启。
// 开启后屏蔽客户端&服务端的心跳检测, 此时建议Transport选择WebSocket/WebSocketSecure
MuninnSettings.Development = true;
// 设置本地玩家的信息
MuninnNetwork.PlayerInfo = new MuninnPlayerInfo()
{
Id = "<playerUniqueId>",
Name = "<playerName>",
Properties = new Dictionary<string, string>
{
["key1"] = "value1",
["key2"] = "value2",
},
};
}
}重载事件回调
class MultiPlayControllerSimple : MuninnBehaviour
{
public override void OnJoinedRoom(MuninnRoomView roomView)
{
// 1. 加入房间后需要处理的信息
// 房间信息
MuninnRoom curRoom = roomView.Room;
// 玩家自己的信息
MuninnPlayer owner = roomView.Self();
// 房间的在线用户列表(包含自己)
List<MuninnPlayer> players = roomView.Players;
// 加入房间时,附带的CachedEvent列表,帮助游戏快速恢复快照等等
List<MuninnCachedEvent> cachedEvents = roomView.CachedEvents;
// 2. 其他信息
// 房间给该玩家分配的Id (保证唯一)
uint senderId = roomView.SenderId;
// MasterClient对应的Id
uint masterClientId = roomView.MasterClientId;
// 快速判断自己是不是MasterClient
roomView.IsMasterClient();
}
public override void OnPlayerEnteredRoom(MuninnPlayer player)
{
Debug.LogFormat("[Muninn] Player {0} entered room", player.Id);
}
public override void OnPlayerLeftRoom(MuninnPlayer player)
{
Debug.LogFormat("[Muninn] Player {0} left room", player.Id);
}
public override void OnLeftRoom(LeaveRoomEvent e)
{
LeaveRoomReason reason = e.reason;
Debug.LogFormat("[Muninn]: OnLeftRoom() was called by Muninn. Reason : {0}", reason);
switch (reason)
{
case LeaveRoomReason.DisconnectByClient:
break;
case LeaveRoomReason.DisconnectByKick:
break;
case LeaveRoomReason.DisconnectByTimeout:
break;
case LeaveRoomReason.DisconnectByCloseRoom:
break;
}
}
public override void OnEvent(MuninnEvent e)
{
Debug.Log("[Muninn] OnEvent() : " + System.Text.Encoding.Default.GetString(e.Data));
}
}发送消息接口
var content = "Hello";
// 1.普通消息
MuninnNetwork.RaiseEvent(
Encoding.UTF8.GetBytes(content),
new RaiseEventOptions() {Target = RaiseEventTarget.TO_ALL}
);
// 2.群组消息
MuninnNetwork.RaiseEvent(
Encoding.UTF8.GetBytes(content),
new RaiseEventOptions() {
Target = RaiseEventTarget.TO_GROUPS,
TargetGroups = new List<uint> {1, 2, 3}.ToArray(),
}
);
// 3.发送给Logic Plugin处理的消息
MuninnNetwork.RaiseEvent(
Encoding.UTF8.GetBytes(content),
new RaiseEventOptions() {Target = RaiseEventTarget.TO_PLUGIN}
);
// 4.ServerCall
MuninnNetwork.ServerCall(Encoding.UTF8.GetBytes(content));房间接口
// 加入房间(三种方式)
// 1.通过房间UUID, 直接加入房间
MuninnNetwork.JoinRoom(new JoinRoomRequest() {
RoomUuid: "<roomuuid>",
JoinCode: "<joincode>", // 私有房间时需要填
});
// 2.创建新房间后进入房间
CreateRoomRequest createRoomRequest = new CreateRoomRequest()
{
Name = "<name>",
Namespace = "<namespace>",
MaxPlayers = 20,
Visibility = MuninnRoomVisibility.Public,
// 创建私有房间
// Visibility = MuninnRoomVisibility.Private,
// JoinCode = "<joincode>"
CustomProperties = new Dictionary<string, string>()
{
{"key1", "value1"},
{"key2", "value2"},
},
};
MuninnNetwork.CreateAndJoinRoom(createRoomRequest);
// 3.创建房间并进入(如果房间已存在则直接进入)
CreateOrJoinRoomRequest createOrJoinRoomRequest = new CreateOrJoinRoomRequest()
{
RoomUUID = "<roomUUID>",
Name = "<name>",
Namespace = "<namespace>",
MaxPlayers = 20,
Visibility = MuninnRoomVisibility.Public,
// 创建私有房间
// Visibility = MuninnRoomVisibility.Private,
// JoinCode = "<joincode>"
CustomProperties = new Dictionary<string, string>()
{
{"key1", "value1"},
{"key2", "value2"},
},
};
MuninnNetwork.CreateOrJoinRoom(createOrJoinRoomRequest);
// 获取房间列表
Action<ListRoomResponse> callback = (resp) =>
{
if (resp.Code == (uint)MuninnCode.OK)
{
Debug.LogFormat("List Room successfully, count: {0}", resp.TotalCount);
}
else
{
Debug.LogFormat("List room fail, errorCode: {0}", resp.Code);
}
};
ListRoomRequest request = new ListRoomRequest() { Status = MuninnLobbyRoomStatus.Ready };
MuninnNetwork.ListRooms(request, callback);
// 主动关闭房间
Action<CloseRoomResponse> closeRoomCallback = (r) =>
{
if (r.Code == (uint)MuninnCode.OK)
{
Debug.Log("close successfully");
}
};
MuninnNetwork.CloseRoom("<roomUUID>", closeRoomCallback);兴趣组接口
List<uint> groups = new List<uint>() { 1, 2, 3 };
// 1.订阅兴趣组
// 兴趣组范围是[1, 255]之间, 0 是系统默认保留(不需要订阅)
MuninnNetwork.SubscribeGroups(groups);
// 2.退订兴趣组
MuninnNetwork.UnsubscribeGroups(groups);CachedEvent接口
string tag = "<cached-event-tag>";
byte[] data = Encoding.UTF8.GetBytes("<cached-event-data>");
// 1.添加CachedEvent
Action<MuninnAddCachedEventResponse> callback = (r) =>
{
if (r.Code == (uint)MuninnCode.OK)
{
Debug.LogFormat("AddCachedEvent Successfully");
}
else
{
MuninnError err = MuninnStatusCodeHelper.Convert(r.Code);
Debug.LogFormat("AddCachedEvent Fail, err:{0}, msg: {1}", err.Code, err.Description);
}
};
MuninnNetwork.AddCachedEvent(tag, data, callback);
// 2.移除CachedEvent
Action<MuninnRemoveCachedEventResponse> callback = (r) =>
{
if (r.Code == (uint)MuninnCode.OK)
{
Debug.LogFormat("RemoveCachedEvent Successfully");
}
else
{
MuninnError err = MuninnStatusCodeHelper.Convert(r.Code);
Debug.LogFormat("RemoveCachedEvent Fail, err:{0}, msg: {1}", err.Code, err.Description);
}
};
MuninnNetwork.RemoveCachedEvent(tag, callback);StickyEvent接口
string key = "<sticky-event-key>";
byte[] data = Encoding.UTF8.GetBytes("<sticky-event-data>");
StickyEventScope scope = StickyEventScope.Room;
// 1.添加StickyEvent
Action<MuninnSetStickyEventResponse> callback = (r) =>
{
if (r.Code == (uint)MuninnCode.OK)
{
Debug.LogFormat("SetStickyEvent Successfully");
}
else
{
MuninnError err = MuninnStatusCodeHelper.Convert(r.Code);
Debug.LogFormat("SetStickyEvent Fail, err:{0}, msg: {1}", err.Code, err.Description);
}
};
MuninnNetwork.SetStickyEvent(key, data, scope, callback);
// 2.移除StickyEvent
Action<MuninnRemoveStickyEventResponse> callback = (r) =>
{
if (r.Code == (uint)MuninnCode.OK)
{
Debug.LogFormat("RemoveStickyEvent Successfully");
}
else
{
MuninnError err = MuninnStatusCodeHelper.Convert(r.Code);
Debug.LogFormat("RemoveStickyEvent Fail, err:{0}, msg: {1}", err.Code, err.Description);
}
};
MuninnNetwork.RemoveStickyEvent(key, scope, callback);
// 3.查询StickyEvent
Action<MuninnGetStickyEventResponse> callback = (r) =>
{
if (r.Code == (uint)MuninnCode.OK)
{
Debug.LogFormat("GetStickyEvent Successfully");
}
else
{
MuninnError err = MuninnStatusCodeHelper.Convert(r.Code);
Debug.LogFormat("GetStickyEvent Fail, err:{0}, msg: {1}", err.Code, err.Description);
}
};
MuninnNetwork.GetStickyEvent(key, scope, callback);用户属性接口
//1. 更新用户
Action<MuninnUpdatePlayerResponse> callback = (r) =>
{
if (r.Code == (uint)MuninnCode.OK)
{
Debug.LogFormat("UpdatePlayer Successfully");
}
else
{
MuninnError err = MuninnStatusCodeHelper.Convert(r.Code);
Debug.LogFormat("UpdatePlayer Fail, err:{0}, msg: {1}", err.Code, err.Description);
}
};
MuninnNetwork.UpdatePlayerInfo(new MuninnPlayer() {
SenderId = 2, // 房间分配的短id
Name = "<name>",
Properties = new Dictionary<string, string>(),
}, callback);
//2. 踢除用户
uint senderId = 2; // 房间分配的短id
MuninnNetwork.KickPlayer(senderId);
3. 更新房间自定义属性
Dictionary<string, string> customProperties = new Dictionary<string, string>()
{
{"key1", "val1"},
{"key2", "val2"},
};
Action<MuninnUpdateRoomCustomPropertiesResponse> callback = (r) =>
{
if (r.Code == (uint)MuninnCode.OK)
{
Debug.LogFormat("UpdateRoomCustomProperties Successfully");
}
else
{
MuninnError err = MuninnStatusCodeHelper.Convert(r.Code);
Debug.LogFormat("UpdateRoomCustomProperties Fail, err:{0}, msg: {1}", err.Code, err.Description);
}
};
MuninnNetwork.UpdateRoomCustomProperties(customProperties, callback);5.SDK错误码
// 服务端的错误[10000, INF)
OK = 0,
InvalidParam = 10000,
InternalServerError = 10001,
OperationFailed = 10002,
MaxPlayersExceeded = 20001,
MismatchedAppId = 20002,
InvalidJoinCode = 20003,
PlayerJoinedAnotherRoom = 20004,
ConnectionFailed = 20099,
UserReLoggedIn = 20101,
RoomClosed = 20102,
UnexpectedRoomError = 20103,
ClientTimedOut = 20104,
KickPlayerByMasterClient = 20105,
KickPlayerFailForSelf = 20106,
KickPlayerFailForNotFound = 20107,
KickPlayerFailForNonMasterClient = 20108,
UpdatePlayerFailForOffline = 20201,
UpdatePlayerFailForPermissionDenied = 20202,
// StickyEvent相关的错误码
MaxStickyEventsExceeded = 20300,
MismatchedStickyEventKey = 20301,
StickyEventKeyNotFound = 20302,
// 客户端定义的错误码[1, 10000)
InvalidClientAppId = 1000,
InvalidClientAppSecret = 1001,
InvalidClientPlayerId = 1002,
MissingJoinCode = 1003,
MissingRoomProfileUuid = 1004,
MissingOwnerId = 1005,
RedundantJoinCode = 1006,
// lobby相关的错误码
LobbyAccessDenied = 9000,
LobbyAuthenticationFailed = 9001,
LobbyRoomClosed = 9002,
LobbyRoomUnknown = 9003,
LobbyQueryRoomFailed = 9004,
LobbyMissingAuthToken = 9005,
LobbyCreateRoomFailed = 9006,
LobbyCreateRoomLimitExceeded = 9007,
LobbyJoinRoomFailed = 9008,
LobbyCloseRoomFailed = 9009,
LobbyChangeRoomStatusFailed = 9010,
LobbyQuickJoinRoomFailed = 9010,
LobbyQueryRoomUntilReadyFailed = 9011,
LobbyQueryRoomByRoomCodeFailed = 9012,
LobbyQueryRoomByRoomNameFailed = 9013,