Use case sample: Announce a level up to all players in joined lobbies

This use case sample demonstrates how to use Triggers to announce a player’s level up to all other players in any lobbies the player is in. The sample uses the key-saved event that the Cloud Save service emits. The sample notifies players who are in the same Lobby as the player who leveled about the level up through a push message.

The trigger uses a filter to evaluate the event payload so that the Cloud Code module only triggers when the player levels up and a player entity emits the Cloud Save event.

Note: You can only use push messages with Cloud Code modules.

Note: The UGS CLI doesn't support triggers with filters. This sample uses the Triggers Admin API to create the trigger.

Prerequisites

You must first create a service account with required access roles.

Authenticate using a Service Account

Before you can call the Triggers service, you must authenticate using a Service Account.

  1. Navigate to the Unity Cloud Dashboard.
  2. Select Administration > Service Accounts.
  3. Select the New button and enter a name and description for the Service Account.
  4. Select Create.

Add Product roles and create a key:

  1. Select Manage product roles.
  2. Add the following roles to the Service Account:
    • From the LiveOps dropdown, select Triggers Configuration Editor and Triggers Configuration Viewer.
    • From the Admin dropdown, select Unity Environments Viewer.
  3. Select Save.
  4. Select Add Key.
  5. Encode the Key ID and Secret key using base64 encoding. The format is “key_id:secret_key”. Note this value down.

For more information, refer to Authentication.

Examine the key-saved event

The Cloud Save service emits a key-saved event when a Cloud Save key is saved. The event payload looks like this:

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

The event passes the event payload to Cloud Code as parameters.

Refer to Cloud Save: Key Saved for more information.

Set up Cloud Code

Define a module endpoint that sends a push message to all players in the lobby when a player levels up.

Create a AnnounceLevelUp module function with contents as below:

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());
        }
    }
}

Deploy the module.

Refer to Deploying Hello World to learn how to deploy a module.

Note: If you are deploying the module using the UGS CLI, don't forget to add additional Service Account role of Cloud Code Editor.

Configure a trigger

To connect your Cloud Code resource to the Cloud Save key-saved event, create a trigger. The trigger executes the Cloud Code module when the event is fired, for example, every time a player saves a key.

The filter in the trigger configuration evaluates the event payload to only trigger the Cloud Code module when the player levels up and the Cloud Save event is emitted by a player entity.

Send a cURL request to the Triggers service to create a trigger.

Authenticate using the Service Account credentials you created earlier by encoding them into a base64 string in the format key_id:secret_key:

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\""
}'

The sample trigger executes your Cloud Code module function when a player saves a value to the LEVEL key.

Set up Lobby

To follow the sample, you need to create a Lobby. You can use a helper Cloud Code script from the Unity Cloud Dashboard. to achieve this:

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

Note down the lobby ID.

Add players to a lobby

You can run the following Cloud Code script to add players to the lobby you created from the Unity Cloud Dashboard. Make sure to regenerate the Player ID token on every test run to add a new player.

Note down one of the player ID of the player you added to the lobby. You can use this later to validate the result by updating their Cloud Save data.

The script takes in lobbyId as parameter.

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: When you add players to the lobby, remember the maximum number of players allowed in the lobby. Leave a spare slot so you can add the player that you are authenticated as in the Unity project to test the sample later.

Validate the result

To validate the result, you can set up a Unity project that subscribes to push messages and use the Cloud Code script for adding players you created earlier to make the authenticated player join a lobby.

Prerequisites

To subscribe to push messages, you need to install the Cloud Code SDK and link your Unity Gaming Services project to the Unity Editor.

Link your Unity Gaming Services project with the Unity Editor. You can find your UGS project ID in the Unity Cloud Dashboard.

  1. In Unity Editor, select Edit > Project Settings > Services.

  2. Link your project.
    If your project doesn't have a Unity project ID:

    1. Select Create a Unity Project ID > Organizations, then select an organization from the dropdown.
    2. Select Create project ID.


    If you have an existing Unity project ID:

    1. Select Use an existing Unity project ID.
    2. Select an organization and a project from the dropdowns.
    3. Select Link project ID.

Your Unity Project ID appears, and the project is now linked to Unity services. You can also access your project ID in a Unity Editor script with the UnityEditor.CloudProjectSettings.projectId property.

SDK installation

To install the latest Cloud Code package for Unity Editor:

  1. In the Unity Editor, open Window > Package Manager.
  2. In the Package Manager, select the Unity Registry list view.
  3. Search for com.unity.services.cloudcode, or locate the Cloud Code package in the list.
  4. Select the package, then select Install.

Check Unity - Manual: Package Manager window to familiarize yourself with the Unity Package Manager interface.

You can subscribe to messages with Cloud Code SDK versions 2.4.0+.

Create a Monobehaviour script

To subscribe to player-level messages, set up a Monobehaviour script. Refer to Send push messages for more information.

You can use the sample code below for your MonoBehaviour script:

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

The first time you run the Monobehaviour script, it logs a player ID. Note this value down.

Join a lobby

You can use the Cloud Code script for adding players you created earlier to make the authenticated player join a lobby. Pass in the lobby ID you created earlier as the lobbyId parameter and player ID you noted down when running the Unity project as the playerId parameter.

Update the Level key in Cloud Save

You can update the Cloud Save key to trigger the Cloud Code module by running the script below.

It takes in a string parameter playerId and a number parameter level.

Note: If you have a custom trigger using a filter with the event's value, ensure you specify the level parameter as a number. With no specific type parameter defined, the parameter is passed as a string. If your filter evaluates the value as a number, and you pass in a string, the trigger won't work.

Use the player ID of one of the players you added to the lobby earlier. This ensures that the player authenticated in the Unity project receives the message, as they are in the same lobby.

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

};

Validate the result

Ensure that the Unity project is running, the authenticated player in your Unity project is in a lobby, and update the LEVEL key in Cloud Save.

You should encounter a push message sent when your player score is beaten in the Unity Editor:

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!"
}