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

> Announce a player's level up to all other players in joined lobbies through a push message.

此使用示例演示了如何使用 Triggers 向玩家所在大厅中的所有其他玩家宣布该玩家升级了。此示例使用了 [Cloud Save](/cloud-save.md) 服务发出的 `key-saved` 事件。此示例通过[推送消息](/cloud-code/modules/how-to-guides/push-messages.md)将玩家升级消息告知与升级玩家位于同一大厅（请参阅 [Lobby](/lobby.md)）的玩家。

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

> **Note:**
>
> **注意**：只能将[推送消息](/cloud-code/modules/how-to-guides/push-messages.md)与 Cloud Code 模块配合使用。

> **Note:**
>
> **注意：** UGS CLI 不支持带有过滤器的触发器。此示例使用 [Triggers Admin API](../define-triggers/rest-api) 来创建触发器。

## 先决条件##prerequisites

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

### 使用服务帐户进行身份验证##authenticate-using-a-service-account

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

1. 导航到 [Unity Dashboard](https://cloud.unity.com)。
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”。请记下此值。

如需了解更多信息，请参阅[身份验证](../../authentication)。

## 检查 `key-saved` 事件##examine-the-key-saved-event

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

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

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

请参阅 [Cloud Save：保存键](/triggers/manage-events/supported-ugs-events.md#key-saved)以了解更多信息。

## 设置 Cloud Code##set-up-cloud-code

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

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

**C#** onfigure-the-ugs-cli

```csharp
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](/cloud-code/modules/getting-started.md#deploy-the-module) 以了解如何部署模块。

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

## 配置触发器##configure-a-trigger

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

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

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

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

```bash
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 模块函数。

## 设置大厅##set-up-lobby

为了按照示例操作，您需要[创建一个大厅](/lobby/create-a-lobby.md)。您可以在 [Unity Dashboard](https://cloud.unity.com) 中使用 Cloud Code 辅助脚本来实现此目的：

### JavaScript##c

```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。

## 向大厅添加玩家##add-players-to-a-lobby

您可以运行以下 Cloud Code 脚本，将玩家添加到从 [Unity Dashboard](https://cloud.unity.com) 创建的大厅。请确保在每次测试运行时重新生成玩家 ID 令牌，以便添加新玩家。


**Frame:**
![](/api/media?file=/cloud-code/media/images/cloud-code-generate-button.png)

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

此脚本接受 `lobbyId` 作为参数。

### JavaScript##javascript

```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;
  }
};
```

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

## 验证结果##validate-the-result

要验证结果，您可以设置一个订阅推送消息的 Unity 项目，并使用您[先前创建的用于添加玩家的 Cloud Code 脚本](#add-players-to-a-lobby)，让经过身份验证的玩家加入大厅。

### 先决条件##javascript

要订阅推送消息，需要安装 Cloud Code SDK，并将 [Unity Gaming Services（Unity 游戏服务）项目](/cloud/projects.md)关联到 Unity 编辑器。

#### 关联项目##link-project

将 [Unity Gaming Services（Unity 游戏服务）项目](/cloud/projects.md)与 Unity 编辑器关联。您可以在 Unity 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 安装##sdkinstallation

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

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

> **Note:**
>
> 请查看 [Unity - 手册：Package Manager（包管理器）窗口](https://docs.unity3d.com/Manual/upm-ui.html)，熟悉 Unity Package Manager（包管理器）界面。

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

### 创建 `Monobehaviour` 脚本##prerequisites

要订阅玩家级消息，请设置一个 `Monobehaviour` 脚本。如需了解更多信息，请参阅[发送推送消息](/cloud-code/modules/how-to-guides/push-messages.md)。

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

```csharp
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。请记下此值。

### 加入大厅##create-a-monobehaviour-script

您可以使用[先前创建的用于添加玩家的 Cloud Code 脚本](#add-players-to-a-lobby)，让经过身份验证的玩家加入大厅。将[先前创建的大厅 ID](#set-up-lobby) 作为 `lobbyId` 参数传入，将运行 Unity 项目时记下的玩家 ID 作为 `playerId` 参数传入。

### 更新 Cloud Save 中的 `Level` 键##join-a-lobby

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

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

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

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

#### JavaScript##javascript

```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;
  }
};
```

### 验证结果##update-the-level-key-in-cloud-save

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

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

```yaml
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!",
  }
```
