使用示例:向加入大厅的所有玩家宣布升级

此使用示例演示了如何使用 Triggers 向玩家所在大厅中的所有其他玩家宣布该玩家升级了。此示例使用了 Cloud Save 服务发出的 key-saved 事件。此示例通过推送消息将玩家升级消息告知与升级玩家位于同一大厅(请参阅 Lobby)的玩家。

触发器使用过滤器来评估事件有效负载,仅当玩家升级且玩家实体发出 Cloud Save 事件时才会触发 Cloud Code 模块。

注意:只能将推送消息与 Cloud Code 模块配合使用。

**注意:**UGS CLI 不支持带有过滤器的触发器。此示例使用 Triggers Admin API 来创建触发器。

先决条件

必须首先创建具有所需访问角色的服务帐户。

使用服务帐户进行身份验证

在调用 Triggers 服务之前,必须使用服务帐户进行身份验证。

  1. 导航到 Unity Dashboard
  2. 选择 Administration(管理)> Service Accounts(服务帐户)
  3. 选择 **New(新建)**按钮并输入服务帐户的名称和描述。
  4. 选择 Create(创建)

添加产品角色并创建密钥:

  1. 选择 Manage product roles(管理产品角色)
  2. 将以下角色添加到服务帐户:
    • 从 LiveOps 下拉选单中,选择 **Triggers Configuration Editor(Triggers 配置编辑者)**和 Triggers Configuration Viewer(Triggers 配置查看者)
    • 从 Admin(管理)下拉选单中,选择 Unity Environments Viewer(Unity 环境查看者)
  3. 选择 Save(保存)
  4. 选择 Add Key(添加密钥)
  5. 使用 base64 编码方式对 **Key ID(密钥 ID)**和 **Secret key(密钥)**进行编码。格式为“key_id:secret_key”。请记下此值。

如需了解更多信息,请参阅身份验证

检查 key-saved 事件

当保存 Cloud Save 键时,Cloud Save 服务会发出 key-saved 事件。事件有效负载如下所示:

{
  "id": "7LpyhpsIvEczGkDI1r8J6gHhHezL",
  "idType": "player",
  "key": "LEVEL",
  "value": 1,
  "valueIncluded": true,
  "writeLock": "7b8920a57912509f6b5cbb183eb7fcb0",
  "accessClass": "default",
  "modifiedDate": "2021-03-04T09:00:00Z"
}

该事件会将事件有效负载作为参数传递给 Cloud Code。

请参阅 Cloud Save:保存键以了解更多信息。

设置 Cloud Code

定义一个模块终端,当玩家升级时,向大厅中的所有玩家发送推送消息。

创建一个包含如下内容的 AnnounceLevelUp 模块函数:

C#

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Unity.Services.CloudCode.Apis;
using Unity.Services.CloudCode.Core;
using Unity.Services.CloudCode.Shared;
using Unity.Services.Lobby.Model;

namespace AnnounceLevelUp;

public class AnnounceLevelUp
{
    private const string ServiceId = "cloud-code";
    private readonly ILogger<AnnounceLevelUp> _logger;

    public AnnounceLevelUp(ILogger<AnnounceLevelUp> logger)
    {
        _logger = logger;
    }

    [CloudCodeFunction("AnnounceLevelUp")]
    public async Task Announce(IExecutionContext ctx, PushClient pushClient, IGameApiClient gameApiClient,
        string id)
    {
        try
        {
            var playerList = await GetPlayersFromLobbies(ctx, gameApiClient, id);
            if (playerList.Count == 0)
            {
                _logger.LogInformation("The player {playerId} has not joined any lobbies", id);
                return;
            }

            await SendPushMessage(ctx, pushClient, playerList);
        }
        catch (ApiException e)
        {
            _logger.LogError("Failed to announce level up for player {playerId}. Error: {Error}", id,
                e.Message);
            throw new Exception($"Failed to announce level up for player {id}. Error: {e.Message}");
        }
    }

    public async Task SendPushMessage(IExecutionContext ctx,  PushClient pushClient, List<Player> lobbyPlayers)
    {

        foreach (var lobbyPlayer in lobbyPlayers)
        {
            try
            {
                var message = $"The player {lobbyPlayer.Id} has leveled up!";

                _logger.LogInformation("Sending push notification to player {lobbyPlayer}", lobbyPlayer.Id);
                await pushClient.SendPlayerMessageAsync(ctx, message, "Level up!", lobbyPlayer.Id);
            }
            catch (ApiException e)
            {
                _logger.LogError("Failed to send push notification to player {playerId}. Error: {Error}", lobbyPlayer.Id,
                    e.Message);
            }
        }
    }

    public async Task<List<Player>> GetPlayersFromLobbies(IExecutionContext ctx, IGameApiClient gameApiClient, string playerId)
    {
        var playersToNotify = new List<Player>();
        try
        {
            var lobbyList = await gameApiClient.Lobby.GetJoinedLobbiesAsync(ctx, ctx.ServiceToken, ServiceId,playerId );
            foreach (var lobby in lobbyList.Data)
            {
                var players = await GetLobbyData(ctx, gameApiClient, lobby);
                playersToNotify.AddRange(players);

            }

            return playersToNotify;
        }
        catch (Exception e)
        {
            _logger.LogError("Failed to get players from lobbies. Error: {Error}",  e.Message);
            throw new Exception($"Failed to get players from lobbies. Error: {e.Message}");
        }
    }

    public async Task<List<Player>> GetLobbyData(IExecutionContext ctx, IGameApiClient gameApiClient, string lobbyId)
    {
        try
        {
            var response = await gameApiClient.Lobby.GetLobbyAsync(ctx, ctx.ServiceToken, lobbyId, "cloud-code");
            return response.Data.Players;
        }
        catch (Exception e)
        {
            _logger.LogError("Failed to get players from lobby: {lobbyId}. Error: {Error}", lobbyId, e.Message);
            throw new Exception($"Failed to get players from lobby: {lobbyId}. Error: {e.Message}");
        }
    }


    public class ModuleConfig : ICloudCodeSetup
    {
        public void Setup(ICloudCodeConfig config)
        {
            config.Dependencies.AddSingleton(GameApiClient.Create());
            config.Dependencies.AddSingleton(PushClient.Create());
        }
    }
}

部署该模块。

请参阅部署 Hello World 以了解如何部署模块。

**注意:**如果您使用 UGS CLI 来部署模块,请不要忘记添加额外的服务帐户角色 Cloud Code Editor

配置触发器

要将 Cloud Code 资源关联到 Cloud Save key-saved 事件,请创建触发器。触发器会在触发事件时(例如,玩家每次保存键时)执行 Cloud Code 模块。

触发器配置中的过滤器将评估事件有效负载,仅当玩家升级且玩家实体发出 Cloud Save 事件时才会触发 Cloud Code 模块。

向 Triggers 服务发送 cURL 请求以创建触发器。

使用您先前创建的服务帐户凭据进行身份验证,将凭据编码为 key_id:secret_key 格式的 base64 字符串:

curl 'https://services.api.unity.com/triggers/v1/projects/<PROJECT_ID>/environments/<ENVIRONMENT_ID>/configs' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic <SERVICE_ACCOUNT_CREDENTIALS_ENCODED>' \
--data '{
  "name": "announce-level-up",
  "eventType": "com.unity.services.cloud-save.key-saved.v1",
  "actionType": "cloud-code",
  "actionUrn": "urn:ugs:cloud-code:AnnounceLevelUp/AnnounceLevelUp",
  "filter": "data[\"idType\"] == \"player\" && data[\"key\"] == \"LEVEL\""
}'

此示例触发器会在玩家将值保存到 LEVEL 键时执行 Cloud Code 模块函数。

设置大厅

为了按照示例操作,您需要创建一个大厅。您可以在 Unity Cloud Dashboard 中使用 Cloud Code 辅助脚本来实现此目的:

JavaScript

const { LobbyApi } = require("@unity-services/lobby-1.2");

module.exports = async ({ params, context, logger }) => {
  const lobbyApi = new LobbyApi(context);
  const serviceId = "cloud-code";
  try {
    // Create a private lobby without any initial players.
    const { data: lobby } = await lobbyApi.createLobby(serviceId, null, {
      name: "sample-lobby",
      maxPlayers: 4,
    });

    return lobby.id;
  } catch (err) {
    logger.error(`Error while calling out to Lobby: ${JSON.stringify(err.message)}`);
    throw err;
  }
};

记下大厅 ID。

向大厅添加玩家

您可以运行以下 Cloud Code 脚本,将玩家添加到从 Unity Cloud Dashboard 创建的大厅。请确保在每次测试运行时重新生成玩家 ID 令牌,以便添加新玩家。

请记下您添加到大厅的玩家的其中一个玩家 ID。您稍后可以使用玩家 ID,通过更新玩家的 Cloud Save 数据来验证结果。

此脚本接受 lobbyId 作为参数。

JavaScript

const { LobbyApi } = require("@unity-services/lobby-1.2");
module.exports = async ({ params, context, logger }) => {
  const lobbyApi = new LobbyApi(context);
  const serviceId = "cloud-code";

  try {
    await lobbyApi.joinLobbyById(params.lobbyId, serviceId, context.playerId);
  } catch (err) {
    logger.error("Failed to join lobby", { "error.message": err.message }, { lobbyId: params.lobbyId });
    throw err;
  }
};

**注意:**当您将玩家添加到大厅时,请注意大厅中允许的最大玩家数量。保留一个备用槽位,以便稍后添加玩家,在 Unity 项目中以该玩家的身份来测试示例。

验证结果

要验证结果,您可以设置一个订阅推送消息的 Unity 项目,并使用您先前创建的用于添加玩家的 Cloud Code 脚本,让经过身份验证的玩家加入大厅。

先决条件

要订阅推送消息,需要安装 Cloud Code SDK,并将 Unity Gaming Services(Unity 游戏服务)项目关联到 Unity 编辑器。

关联项目

Unity Gaming Services(Unity 游戏服务)项目与 Unity 编辑器关联。您可以在 Unity Cloud Dashboard 中找到您的 UGS Project ID。

  1. 在 Unity 编辑器中,选择 Edit(编辑)> Project Settings(项目设置)> Services(服务)

  2. 关联您的项目。
    如果项目没有 Unity Project ID:

    1. 选择 Create a Unity Project ID(创建 Unity Project ID)> Organizations(组织),然后从下拉选单中选择一个组织。
    2. 选择 Create project ID(创建 Project ID)


    如果已有 Unity Project ID:

    1. 选择 Use an existing Unity project ID(使用现有 Unity Project ID)
    2. 从下拉选单中选择组织和项目。
    3. 选择 Link project ID(关联 Project ID)

此时将显示您的 Unity Project ID,并且项目现在已关联到 Unity 服务。此外,还可以使用 UnityEditor.CloudProjectSettings.projectId 属性在 Unity 编辑器脚本中访问您的 Project ID。

SDK 安装

要安装适用于 Unity 编辑器的最新 Cloud Code 包,请执行以下操作:

  1. 在 Unity 编辑器中,打开 Window(窗口)> Package Manager(包管理器)
  2. 在 Package Manager(包管理器)中,选择 **Unity Registry(Unity 注册表)**列表视图。
  3. 搜索 com.unity.services.cloudcode 或在列表中找到 Cloud Code 包。
  4. 选择该包,然后选择 Install(安装)

请查看 Unity - 手册:Package Manager(包管理器)窗口,熟悉 Unity Package Manager(包管理器)界面。

您可以使用 Cloud Code SDK 版本 2.4.0+ 订阅消息。

创建 Monobehaviour 脚本

要订阅玩家级消息,请设置一个 Monobehaviour 脚本。如需了解更多信息,请参阅发送推送消息

您可以将以下示例代码用于您的 MonoBehaviour 脚本:

C#

using System;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Unity.Services.Authentication;
using Unity.Services.CloudCode;
using Unity.Services.CloudCode.Subscriptions;
using Unity.Services.Core;
using UnityEngine;

namespace CloudCode
{
    public class CloudCodePushExample : MonoBehaviour
    {
        async void Start()
        {
            await UnityServices.InitializeAsync();
            await AuthenticationService.Instance.SignInAnonymouslyAsync();
            Debug.Log(AuthenticationService.Instance.PlayerId);
            await SubscribeToPlayerMessages();
        }

        // This method creates a subscription to player messages and logs out the messages received,
        // the state changes of the connection, when the player is kicked and when an error occurs.
        Task SubscribeToPlayerMessages()
        {
            // Register callbacks, which are triggered when a player message is received
            var callbacks = new SubscriptionEventCallbacks();
            callbacks.MessageReceived += @event =>
            {
                Debug.Log(DateTime.Now.ToString("yyyy-MM-dd'T'HH:mm:ss.fffK"));
                Debug.Log(
                    $"Got player subscription Message: {JsonConvert.SerializeObject(@event, Formatting.Indented)}");
            };
            callbacks.ConnectionStateChanged += @event =>
            {
                Debug.Log(
                    $"Got player subscription ConnectionStateChanged: {JsonConvert.SerializeObject(@event, Formatting.Indented)}");
            };
            callbacks.Kicked += () => { Debug.Log($"Got player subscription Kicked"); };
            callbacks.Error += @event =>
            {
                Debug.Log(
                    $"Got player subscription Error: {JsonConvert.SerializeObject(@event, Formatting.Indented)}");
            };
            return CloudCodeService.Instance.SubscribeToPlayerMessagesAsync(callbacks);
        }
    }
}

首次运行 Monobehaviour 脚本时,会记录玩家 ID。请记下此值。

加入大厅

您可以使用先前创建的用于添加玩家的 Cloud Code 脚本,让经过身份验证的玩家加入大厅。将先前创建的大厅 ID 作为 lobbyId 参数传入,将运行 Unity 项目时记下的玩家 ID 作为 playerId 参数传入。

更新 Cloud Save 中的 Level

您可以运行以下脚本来更新 Cloud Save 键以触发 Cloud Code 模块。

它接受一个字符串参数 playerId 和一个数字参数 level

**注意:**如果您有一个自定义触发器使用带有事件值的过滤器,请确保将 level 参数指定为数字。如果没有定义特定类型参数,则参数将作为字符串传递。如果您的过滤器将值评估为数字,而您传入​​字符串,则触发器将无效。

请使用您先前添加到大厅的玩家的其中一个玩家 ID。这样可确保在 Unity 项目中经过身份验证的玩家会收到消息,因为他们在同一个大厅。

JavaScript

const { DataApi } = require("@unity-services/cloud-save-1.4");

module.exports = async ({ params, context, logger }) => {
  const cloudSaveApi = new DataApi(context);

  try {
    await cloudSaveApi.setItem(context.projectId, params.playerId, {
      key: "LEVEL",
      value: params.level,
    });

    result = await cloudSaveApi.getItems(context.projectId, params.playerId);

    return result.data;
  } catch (err) {
    logger.error("Error while calling out to Cloud Save", { "error.message": err.message });
    throw err;
  }
};

验证结果

确保 Unity 项目正在运行,Unity 项目中经过身份验证的玩家位于大厅内,并更新 Cloud Save 中的 LEVEL 密钥。

您应该会在 Unity 编辑器中看到玩家分数被超过时发送的推送消息:

Got player subscription Message:
  {
    "data_base64": <BASE64-ENCODED-DATA>,
    "time": "2023-11-13T16:21:35.102058117Z",
    "message": "The player Z96pNb4wfgdaMLqMQfWpwXEclaRZ has leveled up!",
    "specversion": "1.0",
    "id": <ID>,
    "source": "https://cloud-code.service.api.unity.com",
    "type": "com.unity.services.cloud-code.push.v1",
    "projectid": <PROJECT-ID>,
    "environmentid": <ENVIRONMENT-ID>,
    "correlationid": <CORRELATION-ID>,
    "messagetype": "Level up!",
  }