Use case sample: Reward top players with in-game currency at the end of season

The use case sample demonstrates how to reward your top players with in-game currency at the end of a season. It uses the reset event emitted by the Leaderboards service, and rewards the top players with in-game currency in Economy.

Prerequisites

You must first create a service account with required access roles and configure the UGS CLI.

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.

Configure the UGS CLI

Follow the steps below to get stated with the UGS CLI:

  1. Install the UGS CLI.

  2. Configure your Project ID and Environment as such:
    ugs config set project-id <your-project-id>
    ugs config set environment-name <your-environment-name>

  3. Authenticate using the Service account you created earlier. Refer to Get Authenticated.

Set up Leaderboards and Economy

Define a script or a module endpoint that rewards the top players with in-game currency.

Set up Leaderboards

To follow this sample, you should create a leaderboard.

You can use the UGS CLI to deploy the following leaderboard.lb file. It defines a leaderboard with ascending sort order and keeps the best score.

The file name corresponds to the leaderboard ID.

{
  "$schema": "https://ugs-config-schemas.unity3d.com/v1/leaderboards.schema.json",
  "SortOrder": "asc",
  "UpdateType": "keepBest",
  "Name": "leaderboard"
}

Deploy the file using the UGS CLI tool:

ugs deploy leaderboard.lb

Now that you have created a leaderboard, you can add scores to it.

Optional: Add scores to the leaderboard

You can run the following Cloud Code script to add scores to the leaderboard from the Unity Cloud Dashboard. Make sure to regenerate the Player ID token on every test run to generate a score for a new player.

JavaScript

const { LeaderboardsApi } = require("@unity-services/leaderboards-1.1");
const _ = require("lodash-4.17");

module.exports = async ({ params, context, logger }) => {
  const leaderboardsApi = new LeaderboardsApi({ accessToken: context.accessToken });
  const leaderboardID = "leaderboard";

  var randomScore = _.random(1, 100);
  try {
    await leaderboardsApi.addLeaderboardPlayerScore(context.projectId, leaderboardID, context.playerId, { score: randomScore });
  } catch (err) {
    logger.error("Failed to add score to the leaderboard", { "error.message": err.message });
  }
};

Set up Economy

To follow this sample, you should create a virtual currency that you can reward your top players with.

  1. Navigate to the Unity Cloud Dashboard.
  2. On the left navigation menu, select Products > Economy.
  3. Select the Configuration section.
  4. Select Add Resource.
  5. Enter coin for the key name.
  6. Select Currency for the resource type.
  7. Set the initial and maximum balance.
  8. Select Create.
  9. Select Publish.

Now that you have created a virtual currency, you can reward your top players with it.

Examine the reset event

The Leaderboards service emits a reset when a leaderboard is reset. The event payload looks like this:

{
  "leaderboardId": "string",
  "leaderboardVersionId": "string"
}

The event payload is passed on to Cloud Code as parameters.

Refer to Leaderboards: Reset for more information.

Set up Cloud Code

You need to create either a Cloud Code module or script to define logic for rewarding your top players with in-game currency when the leaderboard is reset.

Cloud Code C# module

You can use the Economy and Leaderboards Client SDKs to interact with the Economy and Leaderboards services.

Define a LeaderboardsEconomySample module with a function that has a string argument leaderboardId.

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.Economy.Model;
using Unity.Services.Leaderboards.Model;

namespace LeaderboardsEconomySample;

public class LeaderboardsEconomySample
{
    private const string CoinsKey = "COIN";
    private const int Amount = 10;

    private static ILogger<LeaderboardsEconomySample> _logger;

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

    [CloudCodeFunction("RewardTopPlayers")]
    public async Task RewardTopPlayers(IExecutionContext ctx, IGameApiClient gameApiClient, string leaderboardId, string leaderboardVersionId)
    {
        var top5Players = await GetTop5(ctx, gameApiClient, leaderboardId, leaderboardVersionId);
        foreach (var score in top5Players)
        {
            try
            {
                // Retrieve the player's configuration to ensure currency configuration is synced
                var configResponse = await gameApiClient.EconomyConfiguration.GetPlayerConfigurationAsync(ctx, ctx.ServiceToken, ctx.ProjectId, score.PlayerId);
                var res = await gameApiClient.EconomyCurrencies.IncrementPlayerCurrencyBalanceAsync(ctx, ctx.ServiceToken, ctx.ProjectId,
                    score.PlayerId,
                    CoinsKey, new CurrencyModifyBalanceRequest(amount: Amount), configResponse.Data.Metadata.ConfigAssignmentHash);
                _logger.LogInformation("Incremented balance for playerId {playerId} by {amount}", score.PlayerId, Amount);
            }
            catch (ApiException e)
            {
                _logger.LogError(e, "Failed to increment balance for playerId {playerId}. Error: {Error}", score.PlayerId, e.Message);
                throw;
            }
        }
    }


    [CloudCodeFunction("GetTopPlayers")]
    public async Task<List<LeaderboardEntry>> GetTop5(IExecutionContext ctx, IGameApiClient gameApiClient, string leaderboardId, string leaderboardVersionId)
    {
        try
        {
            var results = await gameApiClient.Leaderboards.GetLeaderboardVersionScoresAsync(ctx, ctx.ServiceToken,
                new Guid(ctx.ProjectId), leaderboardId, leaderboardVersionId, 0, 5);
            return results.Data.Results;

        }
        catch (ApiException e)
        {
            _logger.LogError(e, "Failed to get top players for leaderboard: {LeaderboardId}. Error: {Error}", leaderboardId, e.Message);
            throw new Exception($"Failed to get top players for leaderboard: {leaderboardId}. Error: {e.Message}");
        }
    }

    public class ModuleConfig : ICloudCodeSetup
    {
        public void Setup(ICloudCodeConfig config)
        {
            config.Dependencies.AddSingleton(GameApiClient.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.

Cloud Code JavaScript script

You can achieve the same outcome by creating a Cloud Code script.

Create a LeaderboardsEconomySample script with required string argument amount with contents as below:

JavaScript

const axios = require("axios");
const { CurrenciesApi, ConfigurationApi } = require("@unity-services/economy-2.4");
const { LeaderboardsApi } = require("@unity-services/leaderboards-1.1");

module.exports = async ({ params, context, logger }) => {
  // Get top 5 players from the Leaderboard
  const leaderboardsApi = new LeaderboardsApi(context);
  const cointAmount = 10;

  let result;

  try {
    result = await leaderboardsApi.getLeaderboardVersionScores(context.projectId, params.leaderboardId, params.leaderboardVersionId, 0, 5);
  } catch (err) {
    logger.error("Failed to retrieve players from the leaderboard", { "error.message": err.message }, { "leaderboardId": params.leaderboardId });
    throw err;
  }

  const currencyId = "COIN";
  const currenciesApi = new CurrenciesApi(context);
  const configApi = new ConfigurationApi(context);

  // Reward currency to every player
  const promises = result.data.results.map(async (score) => {
    try {
      // Retrieve the player config to get the configAssignmentHash.
      // This is needed to ensure the currency is synced
      var config = await configApi.getPlayerConfiguration({
        playerId: score.playerId,
        projectId: context.projectId,
      });

      await currenciesApi.incrementPlayerCurrencyBalance({
        currencyId,
        playerId: score.playerId,
        projectId: context.projectId,
        configAssignmentHash: config.data.metadata.configAssignmentHash,
        currencyModifyBalanceRequest: { amount: cointAmount },
      });
    } catch (err) {
      logger.error("Failed to increment currency for player", { "error.message": err.message }, { "affectedPlayerId": score.playerId });
      return;
    }
  });

  await Promise.all(promises);
};

// Uncomment the code below to enable the inline parameter definition
// - Requires Cloud Code JS dev environment setup with NodeJS (https://docs.unity3d.com/Packages/com.unity.services.cloudcode@2.5/manual/Authoring/javascript_project.html)
//
// module.exports.params = {
//   leaderboardId: { type: "String", required: true },
//   leaderboardVersionId: { type: "String", required: true },
// };

Note: If you are using Unity Editor to manage your scripts, you can uncomment the code at the bottom of the script to declare the required leaderboardId and leaderboardVersionId parameters in-script. To learn more about how to manage in-script parameters, refer to Modify script parameters within the Unity Editor.

Publish the script.

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

Note: If you are deploying the script using the UGS CLI, don't forget to add additional Service Account roles: Cloud Code Script Publisher and Cloud Code Editor.

Configure a trigger

To connect your Cloud Code resource to the schedule, create a trigger. The trigger executes the Cloud Code script or module when the event is fired, for example, when the leaderboard is reset.

Note: You can use a filter to only trigger the Cloud Code logic when a leaderboard with a specific ID resets. The UGS CLI doesn't support triggers with filters. To create a trigger with a filter, you can use the Triggers API. Add data['leaderboardId'] == 'leaderboard' as the filter configuration.

If you created a Cloud Code script, create a triggers-config.tr file with the following configuration:

{
  "Configs": [
    {
      "Name": "reward-leaderboard",
      "EventType": "com.unity.services.leaderboards.reset.v1",
      "ActionUrn": "urn:ugs:cloud-code:LeaderboardsEconomySample",
      "ActionType": "cloud-code"
    }
  ]
}

If you created a Cloud Code module, create a triggers-config.tr file with the following configuration:

{
  "Configs": [
    {
      "Name": "reward-leaderboard",
      "EventType": "com.unity.services.leaderboards.reset.v1",
      "ActionUrn": "urn:ugs:cloud-code:LeaderboardsEconomySample/RewardTopPlayers",
      "ActionType": "cloud-code"
    }
  ]
}

Deploy the configuration using the UGS CLI tool:

ugs deploy triggers-config.tr

You should get a response similar to the following:

Deployed:
    triggers-config.tr

Now you have a trigger that executes your Cloud Code script or module when a leaderboard is reset.

Validate the result

To validate the result, note down a player ID from the leaderboard before it's reset.

  1. Navigate to the Unity Cloud Dashboard.
  2. Select Products > Leaderboards.
  3. Select the Overview section.
  4. Select the leaderboard leaderboard.
  5. Select the Entries tab.
  6. Note down one of the top player IDs.

Reset the leaderboard.

  1. Navigate to the Unity Cloud Dashboard.
  2. Select LiveOps > Leaderboards.
  3. Select the Overview section.
  4. Select the leaderboard leaderboard.
  5. Select Reset Leaderboard .
  6. Enable Archive current scores.

To validate that the player has been rewarded with 10 coins, follow the steps below:

  1. Select LiveOps > Player Management.
  2. In the search field, enter the player ID you noted down earlier and select Find Player.
  3. Navigate to the Economy > Currencies section. If successful, you can find that the player has been rewarded with 10 coins.

To learn more about how to configure your leaderboard resets to compliment your game logic, refer to Resets.