Module structure

A Cloud Code module is a simple .NET project that exposes the endpoints a game client can call. Modules adopt a similar paradigm as .NET class libraries. For example, a standard C# class might look like the following:

namespace ModulesExample;

public class Example
{
    private static string[] GetCheatCodes()
    {
        return new string[]
        {
            "LEAVEMEALONE",
            "ASPIRINE",
            "BIGBANG"
        };
    }

    public void ChangeHaircut(string haircutName)
    {

    }
}

Once compiled, any reference to the class Example can only use the ChangeHaircut() method.

using ModulesExample;

class UsageExample
{
    public void GameLoop()
    {
        Example.ChangeHaircut("buzzcut");

        // Compile error "Cannot access private method 'GetCheatCodes()' here"
        var cheatCodes = Example.GetCheatCodes();
    }
}

This allows developers to choose the interface of their library through access modifiers. Cloud Code modules work in the same way, however they use the [CloudCodeFunction("ChangeHaircut")] attribute instead.

With Cloud Code modules, you can create self-contained server-authoritative libraries. This allows you to run your game logic on the server, and call it from the client, which detaches the game logic from the client and increases security. Modules provide code reuse and provide an interface you can use to manage your game logic.

C# project

A Cloud Code module project is a class library C# project. The project can also be part of a solution. The simplest project structure can look like this:

├─ Main.csproj
    └─ Dependencies
    └─ HelloWorld.cs

Or, if in a solution, it can look like this:

Sample.sln
├─ Main.csproj
    └─ Dependencies
    └─ HelloWorld.cs

The solution can also contain multiple projects that reference each other. This allows you to easily add test projects, and enables code reuse. The example below shows a valid project structure:

Sample.sln
├─ Main.csproj
    └─ Dependencies
    └─ HelloWorld.cs
├─ Tests.csproj
    └─ Dependencies
    └─ Tests.cs
├─ Other.csproj
    └─ Dependencies
    └─ Class1.cs

However, only the main project can contain Cloud Code functions.

Refer to Write unit tests to learn more about how to write unit tests for your Cloud Code module.

Refer to Cloud Code limits to check what storage restrictions apply.

Main project

Although the solution can contain multiple C# projects, you can have only one main project per module. The main project contains all your module entry points (functions with CloudCodeFunction attributes).

Important: If you attribute any functions with the CloudCodeFunction attribute outside of the main project, the Cloud Code service doesn't recognize them as entry points.

The main project is also the project that you deploy to the Cloud Code service. You don't deploy the projects and dependencies that aren't referenced in the main project to the Cloud Code service.

You need to match the name of the main project to the name of the archive that you deploy to the Cloud Code service.

Note: You can make a solution that contains more than one main Cloud Code module project. In this case, each main project corresponds to a different module.

Sample project

The simplest module project can have the following structure:

Sample.sln
├─ Main.csproj
    └─ Dependencies
    └─ HelloWorld.cs

The sample below defines a Cloud Code module with a single function Hello that returns the string Hello World!:

using Unity.Services.CloudCode.Core;

namespace Sample;

public class HelloWorld
{
    [CloudCodeFunction("Hello")]
    public string PrintHello()
    {
        return "Hello World!";
    }
}

Cloud Code NuGet packages

As .NET projects, Cloud Code module projects also use NuGet to share packaged code.

To learn more about NuGet, refer to Microsoft documentation.

Cloud Code offers two packages that you can include in your modules:

  • com.Unity.Services.CloudCode.Core: this package contains the core functionality of Cloud Code, including the CloudCodeFunction attribute. All Cloud Code modules require this package.
  • com.Unity.Services.CloudCode.Apis: this is an optional package that contains the Unity Gaming Services APIs that you can use to interact with the Cloud Code service. You can use this package to call out to other Cloud Code SDKs, such as Cloud Save.

For more information, see the documentation on how to install and use NuGet packages in your project with Jetbrains Rider or Visual Studio.

Note: You can also install any external NuGet packages that you want to use in your module project. For more information, refer to Integrate with external services.

Code structure

To avoid issues with code structure, follow these guidelines.

CloudCodeFunction attribute

To mark a method as a Cloud Code function and make it a viable Cloud Code module endpoint, use the CloudCodeFunction attribute. You can only use Cloud Code function attributes in the main class of your modules.

To invoke the function, you need to call the name of the attribute.

Method signature

Use the structure below for the method signature of your module function.

[CloudCodeFunction("FunctionName")]
public async Task<ReturnType> FunctionName(IGameApiClient gameApiClient, IExecutionContext ctx, string parameter1, string parameter2)
{
    ...
}
  • async void patterns cause errors. Always return a Task or Task<T>.
  • Cloud Code doesn't support static methods.

Use singleton pattern for modules

You can use singleton pattern to integrate with Cloud Code C# packages and external service calls. Singleton patterns improve performance and scalability in Cloud Code and ensure that you don't create multiple instances of the same object.

You can use Dependency Injection to inject the IGameApiClient and IPushClient interfaces into your modules.

Important: Don't rely on shared state across module function invocations because when there are multiple workers, the API call isn't guaranteed to land on the same worker.

Important: Cloud Code workers have a 256 MB memory limit. A worker that exceeds this limit eventually crashes.

Passing in parameters

You can pass in parameters to your module functions.

Primitive and complex types

This includes any primitive types, such as int, string, bool, and float, as well as complex types, such as List<T>, Dictionary<T>, and Dictionary<string, T>.

For example, you can pass in a string and a List<string> to your module function:

{
  "params": {
    "parameter1": "a",
    "parameter2": ["b", "c"]
  }
}

Define your module function with the same parameters:

[CloudCodeFunction("SimpleParams")]
public async Task SimpleParams(IExecutionContext ctx, string parameter1, List<string> parameter2)
{
  return parameter1 + parameter2[0] + parameter2[1];
}

Custom types

You can also pass in any custom types that you define in your module project.

For example, you can pass in a JSON object to your module object:

{
  "params": {
    "player": {
      "name": "player",
      "health": 50,
      "stamina": 20,
      "inventory": {
        "count": 5,
        "weight": 20
      }
    }
  }
}

Define a Player and Inventory classes and a function in your module project:

public class Player
{
    public string Name { get; set; }
    public int Health { get; set; }
    public int Stamina { get; set; }

    public Inventory Inventory { get; set; }
}

public class Inventory
{
    public int Count { get; set; }
    public int Weight { get; set; }
}

[CloudCodeFunction("CustomParams")]
public async Task<string> CustomParams(IExecutionContext ctx, Player player)
{
    return $"{player.Name} has {player.Health} health and {player.Stamina} stamina and {player.Inventory.Count} items in their inventory with a total weight of {player.Inventory.Weight}";
}

Available interfaces

Cloud Code modules can use IGameApiClient, IAdminApiClient, IPushClient, and IExecutionContext interfaces to expand the functionality of your modules.

The ICloudCodeSetup interface allows you to manage the dependencies of your modules. Learn more about it in the Dependency Injection documentation.

IGameApiClient interface

The IGameApiClient interface provides a way to access the Cloud Code C# Client SDKs. You can use Dependency Injection to inject the IGameApiClient into your modules.

  1. Define a class that implements the ICloudCodeSetup interface and add the GameApiClient as a singleton:
    public class ModuleConfig : ICloudCodeSetup
     {
         public void Setup(ICloudCodeConfig config)
         {
             config.Dependencies.AddSingleton(GameApiClient.Create());
         }
     }
  2. Pass in the IGameApiClient interface as a function parameter to your module functions.

IAdminApiClient interface

The IAdminApiClient interface provides a way to access the Cloud Code C# Admin SDKs. You can use Dependency Injection to inject the IAdminApiClient into your modules.

  1. Define a class that implements the ICloudCodeSetup interface and add the AdminApiClient as a singleton:
    public class ModuleConfig : ICloudCodeSetup
     {
         public void Setup(ICloudCodeConfig config)
         {
             config.Dependencies.AddSingleton(AdminApiClient.Create());
         }
     }
  2. Pass in the IAdminApiClient interface as a function parameter to your module functions.

IPushClient interface

The IPushClient interface provides a way to send push notifications to players. You can use Dependency Injection to inject the IPushClient into your modules.

  1. Define a class that implements the ICloudCodeSetup interface and add the PushClient as a singleton:
    public class ModuleConfig : ICloudCodeSetup
     {
         public void Setup(ICloudCodeConfig config)
         {
             config.Dependencies.AddSingleton(PushClient.Create());
         }
     }
  2. Pass in the PushClient object as a function parameter to your module functions.

To learn more about how to send push messages from Cloud Code, refer to Push messages.

IExecution context

To make calls to other UGS services within your module, you can use the IExecutionContext interface to get extra information about the current call. You can use the values below to access the service SDKs and see which player makes a request.

  • ProjectId: The project ID to which the caller was authenticated.
  • PlayerId: The player ID that executed the script. Note: the player ID isn't available when a service calls the script.
  • EnvironmentId: The environment ID of the module.
  • EnvironmentName: The environment name of the module.
  • AccessToken: The JSON Web Token (JWT) credential the player used to authenticate to Cloud Code.
  • UserId: The user ID of the service account. (Note: the user ID is not available when a service calls the script.)
  • Issuer: The issuer or the service account token. (Note: the issuer is not available when a player calls the script). For instance, if Multiplay makes a call, the issuer is multiplay.
  • ServiceToken: The Cloud Code service account JWT credential.
  • AnalyticsUserId: The Analytics User ID of the player. (Note: the analytic user ID is not available when a service calls the script.)
  • UnityInstallationId: The Unity device installation ID of the player. (Note: the Unity installation ID is not available when a service calls the script.)

You can use the execution context to acquire the player ID and authenticate with different Unity Gaming Services. You can obtain the execution context through both the constructor of your class, and through any method with the CloudCodeFunction attribute.

For example, you can use the execution context to call out to Cloud Save to retrieve player keys:

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

namespace Sample;

public class HelloWorld
{
    private static ILogger<HelloWorld> _logger;
    public CloudSaveSdkSample(ILogger<HelloWorld> logger)
    {
        _logger = logger;
    }

    [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 from Cloud Save. Error: {Error}", ex.Message);
            throw new Exception($"Failed to get data for playerId {ctx.PlayerId}. Error: {e.Message}");
        }
    }

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


}

Token authentication

The AccessToken and ServiceToken are JWTs and properties of the IExecutionContext interface.

You can use these tokens to authenticate calls to other Unity Gaming Services from Cloud Code.

Token typeOriginData accessUsage
accessTokenGenerated by the Authentication service.Only the authenticated player.The AccessToken is the JWT you use to authenticate a Cloud Code call. You can pass the token on to other UGS services to access data for the authenticated player.
serviceTokenGenerated by Cloud Code.Cross-player data access.The ServiceToken is a the token you use to call out to other UGS services and interact with cross-player data

Any Access Control rules that you configure affect the AccessToken.

The sample below shows how you can use the AccessToken to access an authenticated player's data with Cloud Save:

using Microsoft.Extensions.DependencyInjection;
using Unity.Services.CloudCode.Apis;
using Unity.Services.CloudCode.Core;
using Unity.Services.CloudCode.Shared;
using Unity.Services.CloudSave.Model;

namespace TokenSample;

public class TokenSample
{
    [CloudCodeFunction("GetKeysForAuthenticatedPlayer")]
    public  Task<ApiResponse<GetKeysResponse>> GetPlayerKeys(IExecutionContext ctx, IGameApiClient gameApiClient)
    {
        try
        {
            // Using ctx.AccessToken to access the data for the player calling the function
            return gameApiClient.CloudSaveData.GetKeysAsync(ctx, ctx.AccessToken, ctx.ProjectId, ctx.PlayerId);
        }
        catch (ApiException e)
        {
            throw new Exception($"Failed to get keys for playerId {ctx.PlayerId}. Error: {e.Message}");

        }
    }

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

To access data for all players, you can use a ServiceToken and pass in a parameter for the playerId:

using Microsoft.Extensions.DependencyInjection;
using Unity.Services.CloudCode.Apis;
using Unity.Services.CloudCode.Core;
using Unity.Services.CloudCode.Shared;
using Unity.Services.CloudSave.Model;

namespace TokenSample;

public class TokenSample
{
    [CloudCodeFunction("GetKeysForAnyPlayer")]
    public  Task<ApiResponse<GetKeysResponse>> GetPlayerKeys(IExecutionContext ctx, IGameApiClient gameApiClient, string playerId)
    {
        try
        {
            // Using ctx.ServiceToken to access the data for any player
            return gameApiClient.CloudSaveData.GetKeysAsync(ctx, ctx.ServiceToken, ctx.ProjectId, playerId);
        } catch (ApiException e)
        {
            throw new Exception($"Failed to get keys for playerId {ctx.PlayerId}. Error: {e.Message}");
        }
    }

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

For an in-depth explanation of the differences between the two tokens, refer to the Service and access tokens documentation.

Cloud Code module metadata

To retrieve a Cloud Code module and its metadata, you can send a GET request to the Cloud Code service. The response is a JSON object with the following structure:

{
  "name": "string",
  "language": "CS",
  "tags": {
    "property1": "string",
    "property2": "string"
  },
  "signedDownloadURL": "string",
  "dateCreated": "2022-04-05T09:12:13Z",
  "dateModified": "2022-04-05T09:12:13Z"
}

Tags

Tags are key-value pairs that you can attach to your Cloud Code modules. You can record any information you want in the tags, such as the version of the module, the author, or the commit hash if you use version control.

Modules can store up to 10 tags, and the maximum characters you can use per tag key and tag value is 128.

Note: The custom tags are only supported when you use the Cloud Code API to author. Cloud Code ignores the tags when you use the Unity Gaming Services CLI and the Unity Editor to author.

Signed Download URL

Signed Download URL is a temporary URL that you can use to download the module archive.