Module structure
Understand the structure of a Cloud Code module project and how it exposes endpoints.
Read time 8 minutesLast updated a day ago
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:
Once compiled, any reference to the classnamespace ModulesExample;public class Example{ private static string[] GetCheatCodes() { return new string[] { "LEAVEMEALONE", "ASPIRINE", "BIGBANG" }; } public void ChangeHaircut(string haircutName) { }}
ExampleChangeHaircut()This allows developers to choose the interface of their library through access modifiers. Cloud Code modules work in the same way, however they use theusing ModulesExample;class UsageExample{ public void GameLoop() { Example.ChangeHaircut("buzzcut"); // Compile error "Cannot access private method 'GetCheatCodes()' here" var cheatCodes = Example.GetCheatCodes(); }}
[CloudCodeFunction("ChangeHaircut")]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:Or, if in a solution, it can look like this:├─ 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
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.Sample.sln├─ Main.csproj └─ Dependencies └─ HelloWorld.cs├─ Tests.csproj └─ Dependencies └─ Tests.cs├─ Other.csproj └─ Dependencies └─ Class1.cs
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 withCloudCodeFunction
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.
Sample project
The simplest module project can have the following structure:The sample below defines a Cloud Code module with a single functionSample.sln├─ Main.csproj └─ Dependencies └─ HelloWorld.cs
HelloHello 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:- : this package contains the core functionality of Cloud Code, including the
com.Unity.Services.CloudCode.Coreattribute. All Cloud Code modules require this package.CloudCodeFunction - : 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.
com.Unity.Services.CloudCode.Apis
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 theCloudCodeFunctionMethod 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){ ...}
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 theIGameApiClientIPushClientPassing in parameters
You can pass in parameters to your module functions.Primitive and complex types
This includes any primitive types, such asintstringboolfloatList<T>Dictionary<T>Dictionary<string, T>stringList<string>Define your module function with the same parameters:{ "params": { "parameter1": "a", "parameter2": ["b", "c"] }}
[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:Define a{ "params": { "player": { "name": "player", "health": 50, "stamina": 20, "inventory": { "count": 5, "weight": 20 } } }}
PlayerInventorypublic 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 useIGameApiClientIAdminApiClientIPushClientIExecutionContextICloudCodeSetupIGameApiClient interface
TheIGameApiClientIGameApiClient- Define a class that implements the interface and add the
ICloudCodeSetupas a singleton:GameApiClientpublic class ModuleConfig : ICloudCodeSetup{ public void Setup(ICloudCodeConfig config) { config.Dependencies.AddSingleton(GameApiClient.Create()); }} - Pass in the interface as a function parameter to your module functions.
IGameApiClient
IAdminApiClient interface
TheIAdminApiClientIAdminApiClient- Define a class that implements the interface and add the
ICloudCodeSetupas a singleton:AdminApiClientpublic class ModuleConfig : ICloudCodeSetup{ public void Setup(ICloudCodeConfig config) { config.Dependencies.AddSingleton(AdminApiClient.Create()); }} - Pass in the interface as a function parameter to your module functions.
IAdminApiClient
IPushClient interface
TheIPushClientIPushClient- Define a class that implements the interface and add the
ICloudCodeSetupas a singleton:PushClientpublic class ModuleConfig : ICloudCodeSetup{ public void Setup(ICloudCodeConfig config) { config.Dependencies.AddSingleton(PushClient.Create()); }} - Pass in the object as a function parameter to your module functions.
PushClient
IExecution context
To make calls to other UGS services within your module, you can use theIExecutionContext- : The project ID to which the caller was authenticated.
ProjectId - : The player ID that executed the script. Note: the player ID isn't available when a service calls the script.
PlayerId - : The environment ID of the module.
EnvironmentId - : The environment name of the module.
EnvironmentName - : The JSON Web Token (JWT) credential the player used to authenticate to Cloud Code.
AccessToken - : The user ID of the service account. (Note: the user ID is not available when a service calls the script.)
UserId - : 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
Issuer.multiplay - : The Cloud Code service account JWT credential.
ServiceToken - : The Analytics User ID of the player. (Note: the analytic user ID is not available when a service calls the script.)
AnalyticsUserId - : The Unity device installation ID of the player. (Note: the Unity installation ID is not available when a service calls the script.)
UnityInstallationId
CloudCodeFunctionusing 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
TheAccessTokenServiceTokenIExecutionContextToken type | Origin | Data access | Usage |
|---|---|---|---|
| Generated by the Authentication service. | Only the authenticated player. | The |
| Generated by Cloud Code. | Cross-player data access. | The |
AccessTokenAccessTokenTo access data for all players, you can use ausing 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()); } }}
ServiceTokenplayerIdFor an in-depth explanation of the differences between the two tokens, refer to the Service and access tokens documentation.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()); } }}
Cloud Code module metadata
To retrieve a Cloud Code module and its metadata, you can send aGET{ "name": "string", "language": "CS", "tags": { "property1": "string", "property2": "string" }, "signedDownloadURL": "string", "dateCreated": "2022-04-05T09:12:13Z", "dateModified": "2022-04-05T09:12:13Z"}