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 theCloudCodeFunction
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)
{
...
}
Important:async void
patterns cause errors. Always return a Task
or Task<T>
. Refer to the official Microsoft documentation for best practices in asynchronous programming.
Important: Cloud Code doesn't support static methods for Cloud Code functions.
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.
- Define a class that implements the
ICloudCodeSetup
interface and add theGameApiClient
as a singleton:public class ModuleConfig : ICloudCodeSetup { public void Setup(ICloudCodeConfig config) { config.Dependencies.AddSingleton(GameApiClient.Create()); } }
- 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.
- Define a class that implements the
ICloudCodeSetup
interface and add theAdminApiClient
as a singleton:public class ModuleConfig : ICloudCodeSetup { public void Setup(ICloudCodeConfig config) { config.Dependencies.AddSingleton(AdminApiClient.Create()); } }
- Pass in the
IAdminApiClient
interface as a function parameter to your module functions.
Note: The Admin APIs have lower rate limits than the Game APIs. Use the Admin APIs for administrative tasks only. Don't use them for player-scale operations. Use the Game APIs to achieve all functionality intended for player-scale operations. Refer to Limits.
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.
- Define a class that implements the
ICloudCodeSetup
interface and add thePushClient
as a singleton:public class ModuleConfig : ICloudCodeSetup { public void Setup(ICloudCodeConfig config) { config.Dependencies.AddSingleton(PushClient.Create()); } }
- 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 ismultiplay
.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 type | Origin | Data access | Usage |
---|---|---|---|
accessToken | Generated 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. |
serviceToken | Generated 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.