古语有云 “烽火连三月,家书抵万金”,在通信技术匮乏的古代,信息传递艰难险阻重重,一封家书寄托着人们对消息及时、准确的深切期盼。而在当今蓬勃发展的数字游戏世界中, 游戏内实时通信 恰似维系玩家社交生态的 “烽火台” 与 “家书”,已然成为提升玩家沉浸感与社交体验的核心关键要素。

无论是组队协作时精准下达的战术指令、跨服活动中实时发布的重要公告,还是好友之间私密亲切的对话交流,游戏开发者都迫切需要一套高效、灵活且稳定可靠的游戏内消息推送系统,以此保障信息能够快速、准确地传递到每一位玩家手中。Unity Online Services(UOS)推出的 Push 应运而生,它凭借轻量化的集成方式和强大完备的功能模块,为游戏内的实时聊天、事件通知及多样化的社交互动,提供了覆盖全流程、全方位的解决方案。

本教程将基于 UOS Push 服务搭建的一个在线聊天室的示例程序,来讲解 Push 是如何实现游戏内实时消息推送的。大家可以进入下方的 UOS 官网链接,进行在线体验哦!

https://uos.unity.cn/playground/push

教程中涉及 UOS 服务包括:

  • 服务器推送服务 Push:可实现游戏内实时消息推送

服务器推送服务 Push

UOS Push 是 Unity 资深团队打造的专业游戏内实时信息推送服务,它是一款支持数十万人甚至上百万玩家同时在线的分布式游戏内实时消息推送系统,具备强大的技术实力与卓越的性能表现。

“实时” 是 UOS Push 的核心优势之一。游戏系统能够借助 Push 服务即时地向玩家发送各类相关的消息和通知,无论是游戏内的实时聊天系统(涵盖世界聊天、团队聊天、群组聊天、一对一的私聊),还是系统发布的公告通知,都能在瞬间传达给目标玩家,为玩家带来近乎零延迟的信息交互体验。

面对高并发的场景,UOS Push 同样表现出色。在游戏活动、赛事等玩家活跃度极高的时刻,大量玩家同时发送和接收消息,UOS Push 能够凭借其分布式的架构和算法,高效处理海量的消息请求,确保消息的准确、及时推送,不会出现消息丢失或延迟过长的情况,为玩家打造流畅、稳定的信息交互环境。

UOS Push 的应用场景丰富多元,在游戏社交与运营中发挥关键作用:

  • 游戏内聊天:在多人在线游戏中,玩家需要实时沟通策略、分享游戏体验。UOS Push 支持即时文字传输,无论是组队刷副本时快速交流战术细节,还是在世界频道与其他玩家畅聊游戏趣事,都能瞬间送达,打破玩家间的沟通壁垒,让社交互动更流畅自然,显著提升玩家的参与感和游戏粘性。

  • 游戏内实时公告:游戏官方推出限时活动、版本更新、服务器维护等重要信息时,UOS Push 可将公告精准、快速地推送给所有玩家。即使玩家处于游戏内不同界面,对于未在线的玩家,UOS Push 会将消息进行缓存,一旦玩家上线,缓存的消息就会即刻送达,避免错过关键内容,确保玩家及时了解游戏动态,提升活动参与度和游戏运营效率。

  • 好友对战邀请:当玩家向好友发起对战邀请时,UOS Push 能立即将邀请信息发送至对方设备。在收到推送提醒后,一键点击接受邀请,可无缝切入对战环节,简化操作流程,让好友间的即时对战更加便捷,增强游戏的社交竞技乐趣 。

教程视频

教程学习大纲

  1. 创建项目,开启 Push 服务并安装 SDK

  2. 在项目中接入 Push SDK

  3. 订阅频道(公共频道/非公共频道)

  4. 发送频道消息

  5. SDK 状态管理与断开连接后的处理

教程示例程序

教程内学习用到的项目素材资源,是通过在 UOS Launcher 窗口中直接在线安装的。大家也可以通过下面提供的链接查看 Push SDK 的示例程序。

Push SDK代码仓库的地址:

https://unitychina.coding.net/public/uos/PushSDK/git/files/master/Sample~

教程操作步骤

接下来让我们来看看 UOS Push 在项目中的具体用法吧!

1. 创建项目,开启 Push 服务并安装 SDK

1.1 创建项目工程

打开 Unity Hub,选择创建「新项目」,教程这里使用的 Unity 编辑器版本是 2022.3.55f1c1 版本,大家可以自行选择电脑上已安装的版本。项目模板可以选择「3D(Built-in)」,自定义项目名称和位置。可以勾选选项「启用游戏云服务」,勾选后会自动为你的项目工程安装 UOS Launcher 的。

最后点击「创建项目」,等待项目的创建和加载。

1.2 绑定 UOS App

温馨提示:当前项目中已安装好 UOS Launcher,不需要再次安装了。大家可以参考之前的的公众号文章教程,来为你当前的项目绑定你创建好的 UOS App 。

点击 Launcher 面板的「LinkApp」按钮,在弹出窗口中选择「By Unity project」,在「Select organization」这里选择一个自己的项目组织,然后在「Select project 」选项这里,我们选择「Create a new project 」,自行设置修改项目名字「Project name」,最后点击「LinkApp」按钮即可。

教程中,我们就先选择绑定创建好的 UOSPushDemo 应用了。

1.3 开启 Push 服务并安装 Push SDK

在编辑器内 Unity Online Services 窗口的下拉服务列表中,找到「Push」,点击「Enable」开启服务,并安装 Push SDK。

1.4 导入项目示例资源包,打开 Sample 场景

在 UOS Launcher 中点击「import sample」按钮,即可导入项目示例工程资源包。

该项目是基于 UOS Push 服务搭建的一个在线聊天室的示例程序, 展示了 SDK 基本接口的使用方式。

导入资源包后,在如图所示的路径下,找到 Scene.unity 示例场景并打开。

在弹出的窗口中,点击「Import TMP Essentials」来导入 TextMeshPro 的字体资源。

游戏的 Game 窗口打开界面如下:

2. 在项目中接入 Push SDK

Push 客户端 SDK 接入指南参考链接:

https://uos.unity.cn/doc/push/client-sdk

2.1 初始化 Push SDK 和 SDK 鉴权

在线聊天室的示例程序的核心控制代码脚本是 PushSample.cs,已经挂载在了场景中的空对象 PushSample 上。

在脚本的 Awake 方法中,调用PushSDK.InitializeAsync 方法来初始化 Push SDK,当前传入了三个参数:

  • OnConnected:连接成功或失败后的回调方法;

  • OnMessage:收到消息的回调方法;

  • OnDisconnected:心跳检测失败或是其他异常问题导致客户端连接断开时,会

    响应该方法,响应该方法后,等于退出连接,此前订阅的频道信息将被清空。

到后面我们再详细讲解这三个回调方法。大家根据自己的需求,初始化时还可以自行选择是否传入订阅频道的回调方法、用户被异地登录时的回调方法、离线玩家消息的回调方法,以及所使用的传输协议类型。

  • TransportProtocol:传输协议, 可选项为 Websocket 和 KCP。

    • Websocket 能更实时检测连接的开始和关闭,能提供更加稳定的连接;

    • KCP 能提供更低延迟的传输效率。微信小游戏请使用 KCP 协议,如需在小游

      戏里使用 Websocket 协议, 请联系我们!

在没有使用 Passport Login SDK 的情况下,调用ExternalLogin方法,使用外部账号登录的方式来进行 SDK 鉴权以获取 AccessToken。



public class PushSample : MonoBehaviour
{
    private readonly string _userID = Guid.NewGuid().ToString();
    private string _userName = "uos_test";
    private async void Awake()
    {
        //......

        // 初始化PushSDK
        await PushSDK.InitializeAsync(OnConnected, OnMessage, OnDisconnected);
        // 登陆
        await AuthTokenManager.ExternalLogin(_userID, _userID, _userName);
    }
    //......
}

2.2 游戏登录界面初始化用户名

游戏 Demo 运行后,在弹出的 UI 窗口中,会先随机一个用户名,然后点击「进入」按钮即可加入聊天室。

登录界面上的 UI 控件,对应场景中的游戏对象 SigninUI,它身上已经绑定了 SigninUI.cs 脚本。

在 SigninUI.cs 脚本的 Start 方法中,会调用 SetRandomName 方法来随机得到一个用户名。



public class SigninUI : MonoBehaviour
{
    [SerializeField] private TMP_InputField usernameInputField;
    [SerializeField] private Button signinButton;
    private List

 usernameList = new List

 ()     {         "MightyWarrior",         "SilentShadow",         //......     };     private void Start()     {         _instance = this;         SetRandomName();         Show(true);     }     private void SetRandomName()     {         var index = Random.Range(0, usernameList.Count);         usernameInputField.text = usernameList[index];     }     //......  }  

2.3 连接至 Push 服务器,进入聊天室

场景中的「进入」按钮对象上,已经注册绑定了 PushSample.cs 脚本中的 JoinRoom 方法。

当点击 “进入” 按钮时,会执行 JoinRoom 方法。通过调用 PushSDK 提供的 ConnectAsync 方法,来连接到 Push Server 端,传入两个参数:

  • 玩家 ID:其他的玩家将通过此 ID 向你发送消息;

  • 玩家用户名:当发送消息时,我们将使用之前输入框里面的用户名,作为聊天时对方可显示的你的玩家名。



public async void JoinRoom()
{
    SigninUI.SetLoading(true);
    _userName = SigninUI.GetUsername();
    try
    {
        // 连接到PushServer
        await PushSDK.Instance.ConnectAsync(_userID,_userName);
    }
    catch (PushSDKClientException e)
    {
        Debug.LogErrorFormat("Failed to init pushSdk and connect server. clientEx {0}", e);
    }
    catch (PushSDKServerException e)
    {
        Debug.LogErrorFormat("Failed to init pushSdk and connect server. serverEx {0}", e);
    }
    catch (AuthException e)
    {
        Debug.LogErrorFormat("Failed to init pushSdk and connect server. authEx {0}", e);
    }

    LoadingUI.Show(true);
}

  3. 订阅频道(公共频道/非公共频道)
3.1 频道的概念

频道是 UOS Push 系统中用于推送和收取信息最小单位,频道可分为公共频道和非公共频道:

  • 公共频道:任何人都可以加入、查看和参与的频道,可用于世界服聊天和对所有玩家发布系统通知。公共频道需预先配置,无法通过客户端 SDK 创建公共频道。

  • 非公共频道:无需预先配置,可通过客户端 SDK 创建和订阅。这种频道通常可用于对特定的团队或群体开放,例如在线游戏中的团队/小队聊天频道、公会聊天频道等。

对于频道消息,在线且订阅频道的玩家应该实时收到频道消息,同时支持频道消息缓存。

3.2 创建公共频道

进入 UOS 网页端的「Push -> 频道管理」页面,点击「创建公共频道」按钮。

在弹出的窗口中,输入:频道名称(名称不能重复),并点击「创建」按钮。

然后就可以看到创建好的公共频道 TestChannel 了。如果用户订阅了当前频道,还可以看到当前频道的在线人数。

3.3 封装订阅频道的方法

当连接 Push Server 成功后,可以订阅需要订阅的频道。在这里,我们封装一个 SubscribeChannel 方法来实现,方法内需要传入要订阅的频道的名字(channelName),以及该频道是否为公共频道(isPublic)。

在方法内会做出以下的处理:

  • 将调用 Push SDK 提供的 SubscribeChannels 方法来实现订阅频道;

  • 同时将订阅过的频道加入集合列表(AllChannels)中进行保存;

  • 并将该频道添加到 UI 组件上进行显示出来;

  • 当订阅需要订阅的频道时,调用 ChatRoomUI.cs 脚本中的 ShowMessage 方法,在聊天室界面上显示 “连接聊天室成功” 的系统消息。



// 所有频道的集合
private static readonly List

 AllChannels = new(); //订阅频道的方法 private static void SubscribeChannel(string channelName, bool isPublic) {     if (AllChannels.Contains(channelName))     {         return;     }     // 订阅频道     PushSDK.Instance.SubscribeChannels(new List

 (){channelName}, isPublic);     // 添加到 UI 组件用于显示     AllChannels.Add(channelName);     ChatRoomUI.Instance.AddChannel(channelName, isPublic);     ChatRoomUI.ShowMessage(new List

 (){channelName},"连接聊天室成功", (uint)_userID.GetHashCode(), UIMessageType.System); } 


3.4 连接 Push Server 后的回调方法(OnConnected )

当客户端连接 Push 服务器后,会自动响应初始化 Push SDK 时传入的 OnConnected 回调方法,来判断连接的结果(ConnectResult)是成功还是失败。

3.4.1 连接成功后订阅频道

连接成功后,会订阅需要订阅的频道。

温馨提醒:频道名称不可以重复,需要全局唯一,公共频道和非公共频道的名称也不可以重复。

  • 订阅非公共频道:

先定义变量 ChannelGuild001 来模拟公会频道,变量 ChannelGroup001 来模拟群组频道。

然后添加判断,如果连接结果(ConnectResult )中的变量(Success)为 true,说明连接成功了。则在 SubscribeChannels 方法中传入 false 来订阅这些非公共频道(ChannelGuild001 、ChannelGroup001 )。

 

private const string ChannelGuild001 = "Guild001";private const string ChannelGroup001 = "Group001";/// /// 连接push server 完成的回调方法/// ///  连接结果private async void OnConnected(ConnectResult connectResult){    if (connectResult.Success)    {        // 连接成功后,订阅需要订阅的频道        // 订阅一些团队频道,比如加入的公会,群组等。        SubscribeChannel(ChannelGroup001, false);        SubscribeChannel(ChannelGuild001, false);        //......    }    //......}
  • 订阅公共频道:

订阅公共频道时,需要先调用 Push SDK 提供的 ListPublicChannelInfoAsync 方法,返回值将记录获取到的当前所有可用的公共频道信息。

然后我们将遍历 UOS 网页端创建的每一个公共频道,然后在SubscribeChannels 方法中传入 true 来订阅这些公共频道。



private async void OnConnected(ConnectResult connectResult)
{
    if (connectResult.Success)
    {
        // 订阅一些团队频道,比如加入的公会,群组等
        // ......

        // 查看并订阅当前所有的公共频道
        try
        {
            var list = await PushSDK.Instance.ListPublicChannelInfoAsync();
            foreach (var item in list)
            {
                SubscribeChannel(item.Name, true);
            }
        }
        catch (PushSDKClientException e)
        {
            Debug.LogErrorFormat("Failed to list channels. clientEx {0}", e);
        }
        catch (PushSDKServerException e)
        {
            Debug.LogErrorFormat("Failed to list channels. serverEx {0}", e);
        }

        SigninUI.Show(false);
        LoadingUI.Show(false);
        ChatRoomUI.Instance.ActiveFirstChannel();
    }

并隐藏登录界面(SigninUI)和加载界面(LoadingUI),同时 UI 界面上会激活显示频道列表中的第一个频道面板。

3.4.2 连接失败时的处理

当连接 Push Server 失败时,会在聊天室界面上显示 “连接聊天室失败” 的系统消息。同时也会显示出登录界面(SigninUI),允许用户重新尝试登录。



private async void OnConnected(ConnectResult connectResult)
{
    if (connectResult.Success)
    {
        //......
    }
    else
    {
        ChatRoomUI.ShowMessage(AllChannels.ToList(), "连接聊天室失败", (uint)_userID.GetHashCode(), UIMessageType.System);
        SigninUI.Show(true);
        LoadingUI.Show(false);
    }
}

3.5 运行项目查看日志信息

此时运行项目后, Game 窗口的界面为:

在 Console 控制台窗口,输出的也会有相关的频道订阅日志:

4. 发送频道消息

频道消息可用于团队、世界服聊天。

4.1 场景中发送消息的 UI 按钮

在聊天室界面上,选择了频道后,可以点击发送消息。「发送」的按钮上,已经注册绑定了当前频道对象 (ChannelPanel(Clone))上的脚本 ChannelPanel.cs 中的方法 SendMessage。

在 SendMessage 方法中,会判断如果输入消息框中内容不为空,会调用 PushSample.cs 脚本中的 SendMessage 方法来发送消息,同时会清空消息输入框的文字。



public class ChannelPanel : MonoBehaviour
{
    //......
    [SerializeField] private TMP_InputField inputMessage;

    public void SendMessage()
    {
        var msg = inputMessage.text;
        if (!string.IsNullOrEmpty(msg))
        {
            PushSample.SendMessage(_channelIndex, inputMessage.text);
            inputMessage.text = "";
        }
    }
}

4.2 发送频道消息

在 PushSample.cs 脚本的 SendMessage 方法中,会调用 Push SDK 提供的 SendChannelMessage 方法来向指定的频道发送消息。

传入两个参数:频道名称、发送的消息内容。频道名称可通过频道索引从 AllChannels 集合中获取到。



public class PushSample : MonoBehaviour
{
    //......
    public static void SendMessage(int index, string message)
    {
        PushSDK.Instance.SendChannelMessage(AllChannels[index], System.Text.Encoding.UTF8.GetBytes(message));
    }
}

4.3 收到消息的回调方法(OnMessage)

收到消息后,会自动回调在初始化 Push SDK 时传入的 OnMessage 方法来处理接收到的推送消息,并将其正确解析后显示到聊天界面上。

  • 方法的参数 PushMessage 会记录:消息发送方 Id、消息发送方名字、收到频道消息时的频道名、收到消息所属的消息类型「公共频道消息/其他频道消息/玩家消息」、消息内容、消息发送时间戳「UTC 时间」。

  • parsedMessage表示解析后的消息内容;

  • 然后判断下如果消息发送者的 Id 和当前用户的 Id 一样,则说明该消息是当前用户自己发送的;否则是其它用户发送的消息。同时根据消息发送方的不同,会显示不同的 UI 界面的。

/// 

/// 收到消息的回调方法
/// 
///  收到的消息
private void OnMessage(PushMessage message)
{
    Debug.Log("收到消息的回调方法,对应频道是:" + message.ChannelName);
    var parsedMessage = System.Text.Encoding.UTF8.GetString(message.Data);
    var ourSide = message.SenderId == _userID;
    ChatRoomUI.ShowMessage(new List

 (){message.ChannelName},parsedMessage, (uint)_userID.GetHashCode(), ourSide ? UIMessageType.OurSide: UIMessageType.OtherSide, message.SenderName); } 
4.4 测试收发消息

我们可以选择一个在编辑器中 Play 模式下测试,另一个提前 Build 成 exe 文件运行游戏。

构建 Build 文件

将当前场景添加到File → BuildSettings列表中,即可点击「Build」按钮,生成一个可执行文件。

同时运行两个客户端 exe 文件,游戏界面如下:

4.5 缓存消息处理

当新上线的玩家订阅同一个频道后,会看到该频道内之前的消息,Game 窗口的 UI 界面如下所示:

对于试用用户来说,频道消息缓存限制为 5 条,也就是说当新用户加入同一个频道后,会显示最新的未读消息,最多为 5 条,我们在 UI 界面上可以看到数量和消息内容。

同时,查看 Console 控制台窗口的日志消息:可以看到新用户在订阅了对应频道(Group001/Guild001/TestChannel)后,会自动触发 OnMessage 回调方法,方法调用的次数和未读消息的数量是一致的。

回调方法 OnMessage 之前已经讲解过,这里不再详细阐述。

/// 

/// 收到消息的回调方法
/// 
///  收到的消息
private void OnMessage(PushMessage message)
{
    Debug.Log("收到消息的回调方法,对应频道是:" + message.ChannelName );
    var parsedMessage = System.Text.Encoding.UTF8.GetString(message.Data);
    var ourSide = message.SenderId == _userID;
    ChatRoomUI.ShowMessage(new List

 (){message.ChannelName},parsedMessage, (uint)_userID.GetHashCode(), ourSide ? UIMessageType.OurSide: UIMessageType.OtherSide, message.SenderName); } 

大家可以自行查看下 ChatRoomUI.cs 脚本中的 ShowMessage 方法,在方法中会遍历所有订阅的频道,然后在对应频道的面板中显示消息。

如果是来自其他玩家的消息(UIMessageType.OtherSide),还会调用 ChannelTab.cs 脚本中的 AddUnreadMessageCount 方法,在标签页上增加未读消息计数,同时将消息内容同步显示在 UI 界面上。



public class ChatRoomUI: MonoBehaviour
{
    //......
    public static void ShowMessage(List

 channels, string text, uint playerID, UIMessageType type, string playerName = "")     {         for (var i = 0; i < channels.Count; i += 1)         {             var channel = channels[i];             var index = ChannelNameToIndex[channel];             var targetPanel = _instance._channelPanels[index];             targetPanel.Add(text, playerID, type, playerName);             if (type == UIMessageType.OtherSide)             {                 var targetTab = _instance._channelTabs[index];                 targetTab.AddUnreadMessageCount();             }         }     } 

  5. SDK 状态管理与断开连接后的处理
5.1 SDK 状态管理

Push SDK Instance 存在以下 5 种状态:

  • init (初始状态):Push SDK 初始化成功后就是该状态。

  • connecting (正在连接中):调用 ConnectAsync 方法开始连接,或断线后自动重连阶段,处于该状态。

  • connected (连接成功):成功连上 Push Server 处于该状态。

  • connectFailure (连接失败):因网络故障等原因无法连接到 Push Server 时会处于该状态。

  • disconnected (已断开连接):客户端主动调用 Disconnect 方法或是断线重连失败后会处于该状态。



public class PushSDK : MonoBehaviour, IPushSDK
{
    public const string StatusInit = "init"; // 初始状态
    public const string StatusConnected = "connected"; // 连接成功
    public const string StatusConnectFailure = "connectFailure"; // 连接失败
    public const string StatusDisconnected = "disconnected"; // 已断开连接
    public const string StatusConnecting = "connecting"; // 正在连接中

    //......
}

Push SDK Instance 状态转换关系如下图:

5.2 断开连接

当和 Push Server 断开连接时,会自动响应初始化 Push SDK 时传入的 OnDisconnected 方法。

  • 正常断开连接时(IsNormalClosed 为 true):会重新显示登录界面,如果程序还在运行,就在所有订阅频道上显示一条系统消息“已退出聊天室”。

  • 异常断开连接时:会调用 ConnectAsync 方法自动尝试重新连接。

 

/// /// 断开连接的回调方法/// /// 是否正常断开连接private void OnDisconnected(DisconnectResult disconnectResult){    if (disconnectResult.IsNormalClosed)    {        // 正常断开连接        SigninUI.Show(true);        if (!Application.isPlaying) return;        ChatRoomUI.ShowMessage(AllChannels.ToList(),"已退出聊天室", (uint)_userID.GetHashCode(), UIMessageType.System);    }    else    {        // 异常断开连接,需要重连        PushSDK.Instance.ConnectAsync(_userID, _userName);    }}
  • 当用户退出应用程序时:自动回调 OnApplicationQuit 方法,如果当前处于已连接状态(StatusConnected),就调用 Disconnect 方法主动断开连接,释放网络连接资源。



private void OnApplicationQuit()
{
    // 程序退出时断开连接
    if (PushSDK.Instance.ConnectionStatus() == PushSDK.StatusConnected)
    {
        PushSDK.Instance.Disconnect();
    }
}

寄语

通过本次教程搭建的在线聊天室示例,相信大家对 UOS Push 的实时消息推送能力已有直观了解。期待开发者们利用 UOS Push,打造出更多充满社交活力与互动乐趣的精彩游戏!

如果想了解客户端接入 Push SDK 时更多 API 的使用方法,欢迎访问 UOS 网页端进行查看:

https://uos.unity.cn/doc/push/client-sdk

本期教程我们先讲解到这里,大家赶快下载 Demo 项目试试吧!我们下一期再见哦!

Unity Online Services (UOS) 是一个专为游戏开发者设计的一站式游戏云服务平台,提供覆盖游戏全生命周期的开发、运营和推广支持。

了解更多 UOS 相关信息:

官网:https://uos.unity.cn

技术交流 QQ 群:823878269

公众号:UOS游戏云服务

Unity 官方微信

第一时间了解Unity引擎动向,学习进阶开发技能

每一个“点赞”、“在看”,都是我们前进的动力