Interact with cross-player data

You can use Cloud Code modules to access and modify data for multiple players at once.

This unlocks a variety of use cases, such as to:

  • Update the player data in Cloud Save for multiple players at once.
  • Send a push notification to multiple players at once.
  • Update Economy balances for multiple players at once.

Note: To reduce risk of abuse, ensure that only specific users can call modules that interact with cross-player data instead of all players. To restrict access to particular modules or their endpoints, you can use Access Control.

Retrieve player IDs

To interact with multiple players, you need call to UGS services to retrieve the player IDs first.

To get the player IDs, you can use the Cloud Code C# SDKs in the Com.Unity.Services.CloudCode.Apis NuGet package.

You can also use a service's REST API directly. Services that you can use to get player IDs include Leaderboards, Lobby, and Friends.

Use Leaderboards

First, you need to create a Leaderboard.

For example, you can retrieve the top five players from a leaderboard and save their IDs. The module below takes in a leaderboard ID and returns a list of top five players' scores:

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.Leaderboards.Model;

namespace CrossPlayerData;

public class CrossPlayerData
{
    private readonly ILogger<CrossPlayerData> _logger;

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

    [CloudCodeFunction("GetTopPlayers")]
    public async Task<List<LeaderboardEntry>> GetTopPlayers(IExecutionContext ctx, IGameApiClient gameApiClient, string leaderboardId)
    {
        ApiResponse<LeaderboardScoresPage> response;
        try
        {
            response = await gameApiClient.Leaderboards.GetLeaderboardScoresAsync(ctx, ctx.ServiceToken, new Guid(ctx.ProjectId),
                    leaderboardId, null, 5);

            return response.Data.Results;

        }
        catch (ApiException e)
        {
            _logger.LogError("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());
        }
    }
}

Call the GetTopPlayers function to retrieve a list of top 5 players. The response looks similar to below:

{
    "output": [
        {
            "playerId": "wYI5NW5gEVvR3PBmYXEzFS1JvSz3",
            "playerName": "IncredibleGleamingPelican#3",
            "rank": 0,
            "score": 44.0
        },
        {
            "playerId": "ryuAA3ZX23aRHN5ZJClC1Z5BrpVb",
            "playerName": "EffectiveBindingBlackberry#9",
            "rank": 1,
            "score": 1.0
        }
    ]
}

Use Lobby

First, you need to create a Lobby.

You can retrieve a list of players that are currently in the Lobby, and use their IDs to interact with their data:

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

public class CrossPlayerData
{
    private readonly ILogger<CrossPlayerData> _logger;

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

    [CloudCodeFunction("GetLobbyPlayers")]
    public async Task<List<Player>> GetLobby(IExecutionContext ctx, IGameApiClient gameApiClient, string lobbyId)
    {
        ApiResponse<Lobby> response;
        try
        {
            response = await gameApiClient.Lobby.GetLobbyAsync(ctx, ctx.ServiceToken, lobbyId, "cloud-code");
            return response.Data.Players;
        }
        catch (ApiException 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());
        }
    }
}

Call the GetLobbyPlayers function to retrieve a list of players in the lobby.

The response looks similar to below:

{
    "output": [
        {
            "allocationId": null,
            "connectionInfo": null,
            "data": null,
            "id": "Z96pNb4wfgdaMLqMQfWpwXEclaRR",
            "joined": "2023-09-08T11:02:18.13Z",
            "lastUpdated": "2023-09-08T11:02:18.13Z"
        }
    ]
}

Use Friends

To use Friends, you need to create a relationship. For more information, see the Friends documentation.

You can use the helper method SendFriendRequest to send a friend request to a player. The method uses the playerId parameter to send the request to the correct user.

You can then retrieve a list of friends for a player, and use their IDs to interact with their data. The sample below shows how you can retrieve a list of player IDs that a player has sent friend requests to:

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

namespace CrossPlayerData;
public class CrossPlayerData
{
    private readonly ILogger<CrossPlayerData> _logger;

    public CrossPlayerData(IExecutionContext ctx, ILogger<CrossPlayerData> logger)
    {
        _logger = logger;
    }

    [CloudCodeFunction("SendFriendRequest")]
    public async Task SendFriendRequest(IExecutionContext ctx, IGameApiClient gameApiClient, string playerId)
    {
        try
        {
            await gameApiClient.FriendsRelationshipsApi.CreateRelationshipAsync(
                ctx, ctx.AccessToken, false, false, new AddRelationshipRequest(RelationshipType.FRIENDREQUEST, new List<MemberIdentity>
                {
                    new(id : playerId)
                }));
        }
        catch (Exception e)
        {
            _logger.LogError("Failed to send a friend request to playerId: {playerId}. Error: {Error}", playerId, e.Message);
            throw new Exception("Failed to send a friend request to playerId: " + playerId + ". Error: " + e.Message);
        }
    }

    [CloudCodeFunction("GetRelationships")]
    public async  Task<List<Relationship>> GetRelationships(IExecutionContext ctx, IGameApiClient gameApiClient)
    {
        try
        {
            var res = await gameApiClient.FriendsRelationshipsApi.GetRelationshipsAsync(ctx, ctx.AccessToken, 10, 0, false, false);
            return res.Data;
        }
        catch (Exception e)
        {
            _logger.LogError("Failed to get relationships for playerId: {playerId}. Error: {Error}", ctx.PlayerId, e.Message);
            throw new Exception("Failed to get relationships for playerId: " + ctx.PlayerId + ". Error: " + e.Message);
        }
    }

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

Call the GetRelationships function to retrieve a list of players that the player has sent friend requests to.

The response looks similar to below:

{
    "output": [
        {
            "created": "2023-09-18T14:46:34.74Z",
            "expires": null,
            "id": "5774e898-a078-4f92-9f0a-4c9beeb6d1bb",
            "members": [
                {
                    "id": "0gvQingjjBwpZhkUJfeoKnFUkD4T"
                }
            ],
            "type": "FRIEND_REQUEST"
        }
    ]
}

Interact with player data

Once you have a list of player IDs, you can use them to update player data through UGS services.

The samples below show how to use the Cloud Save and Economy services to interact with player data.

Authenticate as Cloud Code

To interact with cross-player data, you need to authenticate as Cloud Code. To authenticate as Cloud Code, use the ServiceToken instead of the AccessToken in the module function:

await gameApiClient.CloudSaveData.SetItemAsync(ctx, ctx.ServiceToken, ctx.ProjectId, player.PlayerId, new SetItemBody("ReachedTop5", true));

Refer to Authentication for more information and check Service and access token support for a list of services that support the ServiceToken.

Update top 5 player data using Cloud Save

One way to interact with player data is to pass in a list of player IDs to Cloud Save.

The module function in the sample takes a leaderboard ID as a parameter.

The sample below shows how to record a value for reaching top 5 in Cloud Save for a list of top 5 players retrieved from a leaderboard. To ensure the sample works, follow the steps below:

  1. Create a leaderboard using the Leaderboards service.
  2. Have some player scores on the leaderboard.

Create a new module and add the following code:

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

namespace CrossPlayerData;

public class CrossPlayerData
{
    private readonly ILogger<CrossPlayerData> _logger;

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

    [CloudCodeFunction("GetTopPlayers")]
    public async Task<List<LeaderboardEntry>> GetTopPlayers(IExecutionContext ctx, IGameApiClient gameApiClient, string leaderboardId)
    {
        ApiResponse<LeaderboardScoresPage> response;
        try
        {
            response = await gameApiClient.Leaderboards.GetLeaderboardScoresAsync(ctx, ctx.ServiceToken, new Guid(ctx.ProjectId),
                    leaderboardId, null, 5);

            return response.Data.Results;

        }
        catch (ApiException e)
        {
            _logger.LogError("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}");
        }
    }

    [CloudCodeFunction("UpdateTopPlayerData")]
    public async Task UpdateTopPlayerData(IExecutionContext ctx, IGameApiClient gameApiClient, string leaderboardId)
    {
        var players = GetTopPlayers(ctx, gameApiClient, leaderboardId);
        foreach (var player in players.Result)
        {
            try
            {
                await gameApiClient.CloudSaveData.SetItemAsync(ctx, ctx.ServiceToken, ctx.ProjectId, player.PlayerId, new SetItemBody("ReachedTop5", true));
                _logger.LogInformation("Updated data for playerId {playerId}", player.PlayerId);

            } catch (Exception e)
            {
                _logger.LogError("Failed to update data for playerId {playerId}. Error: {Error}", player.PlayerId, e.Message);
                throw new Exception ($"Failed to update data for playerId: {player.PlayerId}. Error: {e.Message}");
            }
        }
    }

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

The UpdateTopPlayerData function sets a key-value pair for all players in the leaderboard. To verify this, you can navigate to the Player Management service in the Unity Cloud Dashboard and inspect the Cloud Save data for the top players.

Reward all players in the Lobby using Economy

To interact with player balances, you can pass in a list of player IDs to the Economy API.

The sample below shows how to increment the balance of a currency for a list of players retrieved from a lobby. The module function takes in a lobby ID, currency ID, and an amount to increment the balance by as parameters.

To ensure the sample works, follow the steps below:

  1. Create a lobby using the Lobby service.
  2. Have a player in the lobby.
  3. Create and publish a currency in the Economy service.

Create a new module and add the following code:

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.Lobby.Model;

public class CrossPlayerData
{
    private readonly ILogger<CrossPlayerData> _logger;

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

    [CloudCodeFunction("GetLobbyPlayers")]
    public async Task<List<Player>> GetLobby(IExecutionContext ctx, IGameApiClient gameApiClient, string lobbyId)
    {
        ApiResponse<Lobby> response;
        try
        {
            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}");
        }
    }

    [CloudCodeFunction("RewardLobbyPlayers")]
    public async Task RewardLobbyPlayers(IExecutionContext ctx, IGameApiClient gameApiClient, string lobbyId, string currencyId, int amount)
    {
        var players = GetLobby(ctx, gameApiClient, lobbyId);
        foreach (var player in players.Result)
        {
            try
            {
                await gameApiClient.EconomyCurrencies.IncrementPlayerCurrencyBalanceAsync(ctx, ctx.ServiceToken, ctx.ProjectId, player.Id, currencyId, new CurrencyModifyBalanceRequest(currencyId, amount));
                _logger.LogInformation("Incremented balance for playerId {playerId} by {amount}", player.Id, amount);

            } catch (Exception e)
            {
                _logger.LogError("Failed to increment {currencyId} balance for playerId {playerId}. Error: {Error}", currencyId, player.Id, e.Message);
                throw new Exception($"Failed to increment {currencyId} balance for playerId {player.Id}. Error: {e.Message}");
            }
        }
    }

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

The RewardLobbyPlayers function increments the balance of the currency for all players in the lobby.

To verify, you can navigate to the Player Management service in the Unity Cloud Dashboard and inspect the balances for your players.