Integrate with other Unity services

To unlock the full potential of Cloud Code, you can seamlessly integrate it with various Unity Gaming Services using either the C# Service SDKs or REST APIs.

The C# SDKs offer a simpler and more consistent experience, providing convenient access to Unity Gaming Services. If the Unity Gaming Service you wish to utilize already has a Cloud Code C# SDK, you can take advantage of it for a smoother integration. However, in cases where a specific UGS service lacks a Cloud Code C# SDK, you have the flexibility to connect with it directly through its REST API.

Connect to UGS through the UGS SDKs

To access the Cloud Code C# services SDK from NuGet, search for Com.Unity.Services.CloudCode.Apis. Once you install the package, you can use it within your C# modules to simplify the way you connect with other Unity services.

Note: The documentation for Cloud Code C# SDKs is incomplete. For more information, you can refer to the JavaScript documentation which has a similar structure. Please also make use of your IDE's auto complete and intellisense features.

For a full list of Cloud Code C# SDKs, refer to the Available Libraries page.

API Clients

The GameApiClient and AdminApiClient classes are both wrappers around the Cloud Code C# SDKs, providing simplified interfaces for calling Unity Gaming Services (UGS) from Cloud Code modules. However, they serve different purposes and are designed for distinct use cases.

Use the GameApiClient class

The GameApiClient class is specifically tailored for interacting with standard gaming services provided by UGS. It simplifies the process of calling UGS services from Cloud Code modules related to player-specific functionality, such as saving and retrieving game data. In the provided example, the class is used to interact with the Cloud Save functionality, demonstrating how to save and retrieve player data.

To use the GameApiClient interface, register the GameApiClient as a singleton in your ICloudCodeSetup configurator:

C#

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

Usage Example

You can use the GameApiClient class in any function that you attribute with CloudCodeFunction. To use the GameApiClient class, pass in the IGameApiClient interface as a parameter to the function.

The following example demonstrates how to save and read data from Cloud Save:

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;

namespace ExampleModule;

public class CloudSaveSdkSample
{
    private static ILogger<CloudSaveSdkSample> _logger;

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

    [CloudCodeFunction("SaveData")]
    public async Task SaveData(IExecutionContext context, IGameApiClient gameApiClient, string key, object value)
    {
        try
        {
            await gameApiClient.CloudSaveData.SetItemAsync(context, context.AccessToken, context.ProjectId,
                context.PlayerId, new SetItemBody(key, value));
        }
        catch (ApiException ex)
        {
            _logger.LogError("Failed to save data. Error: {Error}", ex.Message);
            throw new Exception($"Failed to save data for playerId {context.PlayerId}. Error: {ex.Message}");
        }
    }

    [CloudCodeFunction("GetData")]
    public async Task<object> GetData(IExecutionContext context, IGameApiClient gameApiClient, string key)
    {
        try
        {
            var result = await gameApiClient.CloudSaveData.GetItemsAsync(context, context.AccessToken,
                context.ProjectId, context.PlayerId, new List<string> { key });
            return result.Data.Results.First().Value;
        }
        catch (ApiException ex)
        {
            _logger.LogError("Failed to get data. Error: {Error}", ex.Message);
            throw new Exception($"Failed to get data for playerId {context.PlayerId}. Error: {ex.Message}");
        }
    }

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

Use the AdminApiClient class

The AdminApiClient class is intended for accessing administrative functionalities of UGS services. It provides a simplified interface for calling UGS admin-related services from Cloud Code modules. The example illustrates how to create a leaderboard using the AdminApiClient, showcasing its utility for administrative tasks.

To use the AdminApiClient interface, register the AdminApiClient as a singleton in your ICloudCodeSetup configurator:

C#

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

Usage Example

Similar to the GameApiClient, the AdminApiClient class can be employed in any function attributed with CloudCodeFunction. When using the AdminApiClient class, pass in the IAdminApiClient interface as a parameter to the function.

Note: To use admin functionality with the admin SDK, you need valid service account credentials with the Leaderboards Admin Project role. To create a service account, please refer to Admin authentication.

Caution: This Cloud Code example demonstrates the use of hardcoded plaintext service account credentials. Use caution when you handle credentials and code with secrets. To ensure security, it is recommended that you don’t commit secrets to version control. If your credentials become compromised, you can regenerate your service account credentials immediately. A more secure method to store credentials in Cloud Code is in development.

The following example demonstrates how to create a Leaderboard:

C#

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

namespace ExampleModule;

public class ModuleMain
{
    private static ILogger<ModuleMain> _logger;

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

    [CloudCodeFunction("CreateLeaderboard")]
    public async Task CreateLeaderboard(IExecutionContext context, IAdminApiClient adminApiClient)
    {
        try
        {
            await adminApiClient.Leaderboards.CreateLeaderboardAsync(
                executionContext: context,
                serviceAccountKey: "YOUR_SERVICE_ACCOUNT_KEY",
                serviceAccountSecret: "YOUR_SERVICE_ACCOUNT_SECRET",
                projectId: Guid.Parse(context.ProjectId),
                environmentId: Guid.Parse(context.EnvironmentId),
                leaderboardIdConfig: new LeaderboardIdConfig(
                    id: "new-leaderboard",
                    name: "new-leaderboard",
                    sortOrder: SortOrder.Asc,
                    updateType: UpdateType.KeepBest
                )
            );
        }
        catch (ApiException ex)
        {
            _logger.LogError("Failed to create a Leaderboard. Error: {Error}", ex.Message);
            throw new Exception($"Failed to create a Leaderboard. Error: {ex.Message}");
        }
    }

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

Connect to UGS through REST APIs

If you want to use a service that doesn't have a C# SDK yet, you can also connect with the services directly through their REST API.

Authentication

Depending on your use case, you can use either the AccessToken or the ServiceToken to authenticate the API call. If you use the ServiceToken to authenticate UGS Client APIs, ensure the service you want to call supports service tokens. For a list of services and use cases that support service tokens, refer to the Service and access token support documentation.

Note: If the service you want to call provides a Cloud Code C# SDK, you can use the SDK instead of calling the service API directly. For more information, refer to the list of Available libraries.

Calling the API

Dependency Injection allows you to create API interfaces as singletons and inject them into your modules.

Note: In addition to UGS APIs, you can also implement external APIs as singletons. For an example of how to implement an external API, refer to the documentation on how to integrate with external services.

For example, you can define an interface for the User Generated Content API to create and retrieve content:

Note: You need to enable the User Generated Content service in your project before you can use it.

C#

using System.Net;
using Newtonsoft.Json;
using RestSharp;
using RestSharp.Authenticators;
using Unity.Services.CloudCode.Core;

namespace GeneratedContentSample;
public interface IGeneratedContentApi
{
    public Task<GeneratedContentGetApiResponse?> GetGeneratedContentForPlayer(IExecutionContext context);
    public Task<GeneratedContentPostApiResponse?> CreateContent(IExecutionContext context);
}

public class GeneratedContentPostApiResponse
{
    public string? uploadThumbnailUrl { get; set; }
    public string? uploadContentUrl { get; set; }
    public string? version { get; set; }
    public Content content { get; set; }
}

public class GeneratedContentGetApiResponse
{
    public int? offset { get; set; }
    public int? limit { get; set; }
    public int? total { get; set; }
    public Content[] results { get; set; }
}

public class Content
{
    public string id { get; set; }
    public string? name { get; set; }
    public string? customId { get; set; }
    public string? description { get; set; }
    public string visibility { get; set; }
    public string moderationStatus { get; set; }
    public string? version { get; set; }
    public string createdAt { get; set; }
    public string updatedAt { get; set; }
    public string deletedAt { get; set; }
    public string projectId { get; set; }
    public string environmentId { get; set; }
    public string creatorAccountId { get; set; }
    public string? thumbnailUrl { get; set; }
    public string? downloadUrl { get; set; }
    public string? portalUrl { get; set; }
    public string? metadata { get; set; }
    public float? averageRating { get; set; }
    public bool isUserSubscribed { get; set; }
    public string assetUploadStatus { get; set; }
    public string thumbnailUploadStatus { get; set; }
    public string? webhookEventName { get; set; }
}


public class GeneratedContentApi : IGeneratedContentApi
{
    private readonly RestClient _httpClient;

    public GeneratedContentApi()
    {
        _httpClient = new RestClient("https://ugc.services.api.unity.com/v1/");
    }

    public async Task<GeneratedContentGetApiResponse?> GetGeneratedContentForPlayer(IExecutionContext context)
    {
        var request = new RestRequest($"content/search")
        {
            Authenticator = new JwtAuthenticator(context.AccessToken)
        };

        // Pass through the analytics user id and Unity installation id to the service.
        if (context.AnalyticsUserId != null)
            request = request.AddHeader("analytics-user-id", context.AnalyticsUserId);
        if (context.UnityInstallationId != null)
            request = request.AddHeader("unity-installation-id", context.UnityInstallationId);


        RestResponse response = await _httpClient.ExecuteAsync(request);
        if (response.StatusCode != HttpStatusCode.OK)
            throw new Exception("Failed to get User Generated Content: " + response.Content);

        return JsonConvert.DeserializeObject<GeneratedContentGetApiResponse?>(response.Content!);
    }

    public async Task<GeneratedContentPostApiResponse?> CreateContent(IExecutionContext context)
    {
        var request = new RestRequest($"projects/{context.ProjectId}/environments/{context.EnvironmentId}/content", Method.Post)
        {
            Authenticator = new JwtAuthenticator(context.AccessToken)
        }.AddJsonBody(new
        {
            name = "a festive treat",
            description = "banasco is a traditional british toast",
        });

        // Pass through the analytics user id and Unity installation id to the service.
        if (context.AnalyticsUserId != null)
            request = request.AddHeader("analytics-user-id", context.AnalyticsUserId);
        if (context.UnityInstallationId != null)
            request = request.AddHeader("unity-installation-id", context.UnityInstallationId);


        RestResponse response = await _httpClient.ExecuteAsync(request);
        if (response.StatusCode != HttpStatusCode.OK)
            throw new Exception("Failed to create user generated content: " + response.Content);

        return JsonConvert.DeserializeObject<GeneratedContentPostApiResponse>(response.Content!);

    }

}

Then you can add the GeneratedContentApi as a singleton:

C#

public class ModuleConfig : ICloudCodeSetup
    {
        public void Setup(ICloudCodeConfig config)
        {
            config.Dependencies.AddSingleton<IGeneratedContentApi>(new GeneratedContentApi());
        }
    }

You can then pass in IGeneratedContentApi interface as a function parameter to your module functions, and access the UGC API.

C#

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Unity.Services.CloudCode.Core;

namespace GeneratedContentSample;

public class GeneratedContent
{
    private readonly ILogger<GeneratedContent> _logger;
    public GeneratedContent(ILogger<GeneratedContent> logger)
    {
        _logger = logger;
    }

    [CloudCodeFunction("AddContent")]
    public async Task<GeneratedContentPostApiResponse?> AddUserGeneratedContent(IExecutionContext ctx, IGeneratedContentApi contentClient)
    {
        try
        {
            return await contentClient.CreateContent(ctx);
        }
        catch (Exception e)
        {
            _logger.LogError("Failed to add content. Error: {Error}", e.Message);
            throw new Exception($"Failed to add content for player {ctx.PlayerId}. Error: {e.Message}");
        }
    }

    [CloudCodeFunction("GetContent")]
    public async Task<GeneratedContentGetApiResponse?> GetContent(IExecutionContext ctx, IGeneratedContentApi contentClient)
    {
        try
        {
            return await contentClient.GetGeneratedContentForPlayer(ctx);
        }
        catch (Exception e)
        {
            _logger.LogError("Failed to get content. Error: {Error}",  e.Message);
            throw new Exception($"Failed to get content. Error: {e.Message}");
        }
    }

    public class ModuleConfig : ICloudCodeSetup
    {
        public void Setup(ICloudCodeConfig config)
        {
            config.Dependencies.AddSingleton<IGeneratedContentApi>(new GeneratedContentApi());
        }
    }
}