Store data using Cloud Code
Use Cloud Code to access, modify, and manage Cloud Save data to achieve server-authoritative gameplay.
Read time 13 minutesLast updated 2 days ago
You can use Cloud Code to interact with Cloud Save. For example, you can use Cloud Code to run a script when a player saves data to Cloud Save, or to modify data in response to a game event. With Cloud Code, you can achieve server authority and reduce the risk of players cheating in your game. The Cloud Save SDK for Cloud Code allows you to read and write data for Player, to read and write Game Data and to query data (if you have configured indexes to make data queryable) from Cloud Code. The Cloud Save SDK is available for both Cloud Code C# modules and Cloud Code JavaScript scripts. Refer to the Cloud Save SDK for Cloud Code documentation for a list of all available methods.
Authentication
Cloud Code offers two authentication methods you can use to interact with Cloud Save data. The recommended authentication method depends on whether you want to access data for a specific player, or for any player in the game:- To interact with one player's data only, use the access token to authenticate the request. This allows you to read and write data for the player.
- To interact with cross-player data or Game Data, use the service token to authenticate the request. This allows you to read and write both Game Data and data for any player.
- To interact with Cloud Save events in Cloud Code Triggers, use the service token to authenticate the request. This allows you to read and write both Game Data and data for any player.
Using Cloud Save with Cloud Code modules
You can use Cloud Code modules to interact with Cloud Save data.Set up the module
To use Cloud Save with Cloud Code modules, you first need to set up a Cloud Code module. For more information, refer to the Getting started with Cloud Code modules guide. As with any other Unity services integrations in Cloud Code, you need to initialize the client SDK in the module. Create a class that implements theICloudCodeSetupThis setup allows you to use the Cloud Save SDK in your Cloud Code module by passing theusing Microsoft.Extensions.DependencyInjection;using Unity.Services.CloudCode.Apis;using Unity.Services.CloudCode.Core; public class ModuleConfig : ICloudCodeSetup { public void Setup(ICloudCodeConfig config) { config.Dependencies.AddSingleton(GameApiClient.Create()); } }
GameApiClientFor more information on how Cloud Code integrates with Unity services, refer to Integrate with other Unity services.[CloudCodeFunction("SaveData")]public async Task SaveData(IExecutionContext context, IGameApiClient gameApiClient, string key, string value){ ... // Use the gameApiClient.CloudSave to interact with Cloud Save data}
Modify player data
To modify a player's data, use the Cloud Save SDK for Cloud Code modules. The following example shows how to update a player's data stored in Cloud Save. The example uses the access token to authenticate the request, which restricts the access to the data of the player who is authenticated in the client: C#:You can use editor bindings to call the Cloud Code module from the Unity Editor. After you generate the bindings, you can call theusing System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;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 CloudSaveModule;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, string 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<List<string?>> GetData(IExecutionContext context, IGameApiClient gameApiClient, string playerId, string key) { try { var result = await gameApiClient.CloudSaveData.GetItemsAsync(context, context.AccessToken, context.ProjectId, context.PlayerId, new List<string> { key }); return result.Data.Results .Select(item => item.Value?.ToString()) .Where(value => value != null) .ToList(); } 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}"); } }}
SaveDataGetDataWhen successful, the output should be similar to the following:using System.Collections.Generic;using System.Threading.Tasks;using UnityEngine;using Unity.Services.Authentication;using Unity.Services.CloudCode;using Unity.Services.CloudCode.GeneratedBindings;using Unity.Services.Core;public class Test : MonoBehaviour{ public async void Awake() { await UnityServices.InitializeAsync(); // Sign in anonymously into the Authentication service if (!AuthenticationService.Instance.IsSignedIn) await AuthenticationService.Instance.SignInAnonymouslyAsync(); try { var module = new CloudSaveModuleBindings(CloudCodeService.Instance); const string key = "testKey"; await module.SaveData(AuthenticationService.Instance.PlayerId,key, "testValue"); var data = await module.GetData(AuthenticationService.Instance.PlayerId, key); Debug.Log($"Data retrieved: {string.Join(", ", data)}"); } catch (CloudCodeException exception) { Debug.LogException(exception); } }}
Data retrieved: testValue
Modify cross-player data
You can use the Cloud Save SDK for Cloud Code modules to modify cross-player data or Game Data. The sample code demonstrates how to access and modify any player's data that is stored in Cloud Save.Read and write data
The sample code below shows how to read and write any player's data that is stored in Cloud Save. TheplayerId
C#:
You can use editor bindings to call the Cloud Code module from the Unity Editor. After you generate the bindings, you can call theusing System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;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 CloudSaveModule;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 playerId, string key, string value) { try { await gameApiClient.CloudSaveData.SetItemAsync(context, context.ServiceToken, context.ProjectId, 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 {playerId}. Error: {ex.Message}"); } } [CloudCodeFunction("GetData")] public async Task<List<string?>> GetData(IExecutionContext context, IGameApiClient gameApiClient, string playerId, string key) { try { var result = await gameApiClient.CloudSaveData.GetItemsAsync(context, context.ServiceToken, context.ProjectId, playerId, new List<string> { key }); return result.Data.Results .Select(item => item.Value?.ToString()) .Where(value => value != null) .ToList(); } catch (ApiException ex) { _logger.LogError("Failed to get data. Error: {Error}", ex.Message); throw new Exception($"Failed to get data for playerId {playerId}. Error: {ex.Message}"); } }}
SaveDataGetDataplayerIdSaveDataGetDataManage data with different access levels
The following sample code demonstrates how you can use different access modifiers for players and custom entities to access and modify Cloud Save data. Note that the use of access modifiers depends on the type of data you work with and the operations you want to perform. C#:You can use editor bindings to call the Cloud Code module from the Unity Editor. After you generate the bindings, call theusing System;using System.Collections.Generic;using System.Threading.Tasks;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 CloudSaveModule;public class CloudSaveAccessClasses{ private static ILogger<CloudSaveAccessClasses> _logger; public CloudSaveAccessClasses(ILogger<CloudSaveAccessClasses> logger) { _logger = logger; } [CloudCodeFunction("ManageDataAccessClasses")] public async Task ManageDataAccessClasses(IExecutionContext context, IGameApiClient gameApiClient) { var projectId = context.ProjectId; var playerId = context.PlayerId; // Access token is used for player-specific operations // Service token is used for server-side/service account operations try { //----------------Default item data, readable and writable by the player ----------------- // This is the most common use case for player-specific data await gameApiClient.CloudSaveData.SetItemAsync(context, context.AccessToken, projectId, playerId, new SetItemBody("defaultData", "value")); // Retrieve default items var result = await gameApiClient.CloudSaveData.GetItemsAsync(context, context.AccessToken, projectId, playerId, null); _logger.LogInformation($"Retrieved default items from Cloud Save: {System.Text.Json.JsonSerializer.Serialize(result.Data.Results)}"); // -------------- Custom item data - non-player entities ------------------- // Can only be read/written by the server or service accounts var customId = "gameLevelAttributes"; await gameApiClient.CloudSaveData.SetCustomItemAsync(context, context.ServiceToken, projectId, customId, new SetItemBody("levelDifficulty", "easy")); var customItemResult = await gameApiClient.CloudSaveData.GetCustomItemsAsync(context, context.ServiceToken, projectId, customId, new List<string> { "levelDifficulty" }); _logger.LogInformation($"Retrieved custom items from Cloud Save: {System.Text.Json.JsonSerializer.Serialize(customItemResult.Data.Results)}"); // ----------------- Protected data - only readable by player, not writable ----------------- // Can only be written by the server or service accounts await gameApiClient.CloudSaveData.SetProtectedItemAsync(context, context.ServiceToken, projectId, playerId, new SetItemBody("protectedItem", "verySecretData")); // Retrieve protected items var protectedDataResult = await gameApiClient.CloudSaveData.GetProtectedItemsAsync(context, context.AccessToken, projectId, playerId, new List<string> { "protectedItem" }); _logger.LogInformation($"Retrieved protected item from Cloud Save: {System.Text.Json.JsonSerializer.Serialize(protectedDataResult.Data.Results)}"); // ----------------- Private data - only readable and writable by server or service accounts ----------------- // Can only be written to by server/service accounts await gameApiClient.CloudSaveData.SetPrivateCustomItemAsync(context, context.ServiceToken, projectId, customId, new SetItemBody("levelTimerInMinutes", 25)); // Can only be accessed by server/service accounts var customDataResult = await gameApiClient.CloudSaveData.GetPrivateCustomItemsAsync(context, context.ServiceToken, projectId, customId); _logger.LogInformation($"Retrieved private custom items from Cloud Save: {System.Text.Json.JsonSerializer.Serialize(customDataResult.Data.Results)}"); } catch (ApiException ex) { _logger.LogError("Error while calling out to Cloud Save. Error: {Error}", ex.Message); throw new Exception($"Failed to manage data access classes for playerId {playerId}. Error: {ex.Message}"); } }
ManageDataAccessClassesTo check the output, refer to the Cloud Code logs in the Unity Cloud Dashboard. The logs should show the retrieved items for each access class.using System.Collections.Generic;using System.Threading.Tasks;using UnityEngine;using Unity.Services.Authentication;using Unity.Services.CloudCode;using Unity.Services.CloudCode.GeneratedBindings;using Unity.Services.Core;public class Test : MonoBehaviour{ public async void Awake() { await UnityServices.InitializeAsync(); // Sign in anonymously into the Authentication service if (!AuthenticationService.Instance.IsSignedIn) await AuthenticationService.Instance.SignInAnonymouslyAsync(); try { var module = new CloudSaveModuleBindings(CloudCodeService.Instance); await module.ManageDataAccessClasses(); } catch (CloudCodeException exception) { Debug.LogException(exception); } }}
Query data
You can use Cloud Code to query Cloud Save data. Before you use the sample below, you need to do the following steps:- Create an index in the Cloud Save service.
- Create a query in the Cloud Save service.
- Navigate to the Cloud Save service in the Unity Cloud Dashboard, and create indexes with the following parameters:
- An ascending index for player entities with default access class for the key .
health - A descending index for custom entities with private access class for the key .
difficulty - An ascending index for player entities with protected access class for the key .
level
- An ascending index for player entities with default access class for the key
The sample code demonstrates how you can query Cloud Save data using different access modifiers for players and custom entities:
C#:
You can use editor bindings to call the Cloud Code module from the Unity Editor. After you generate the bindings, you can call theusing System;using System.Collections.Generic;using System.Text.Json;using System.Threading.Tasks;using Microsoft.Extensions.Logging;using Unity.Services.CloudCode.Apis;using Unity.Services.CloudCode.Core;using Unity.Services.CloudSave.Model;namespace CloudSaveModule;public class CloudSaveQueries{ private readonly ILogger<CloudSaveQueries> _logger; private const string HealthKey = "health"; private const string StaminaKey = "stamina"; private const string LevelKey = "level"; private const string ExperiencePointsKey = "experiencePoints"; private const string DifficultyKey = "difficulty"; private const string CastleLevelKey = "castleLevel"; private const string ForestLevelKey = "forestLevel"; private const string EasyValue = "easy"; private const string HardValue = "hard"; public CloudSaveQueries(ILogger<CloudSaveQueries> logger) { _logger = logger; } [CloudCodeFunction("ManagePlayerData")] public async Task ManagePlayerDataAsync(IExecutionContext context, IGameApiClient gameApiClient, string playerId) { try { // Set and query player data // This sets the player's health and stamina, and queries for players with health less than 100 await SetPlayerDataAsync(context, gameApiClient, playerId); await QueryPlayerDataAsync(context, gameApiClient); // Set and query custom data // This sets custom data for different levels and queries for levels with difficulty equal to "easy" await SetCustomDataAsync(context, gameApiClient); await QueryCustomDataAsync(context, gameApiClient); // Set and query protected player data // This sets protected data for the player and queries for players with level greater than 5 await SetProtectedPlayerDataAsync(context, gameApiClient, playerId); await QueryProtectedPlayerDataAsync(context, gameApiClient); } catch (Exception ex) { _logger.LogError("Error while managing Cloud Save data: {ErrorMessage}", ex.Message); throw; } } private async Task SetPlayerDataAsync(IExecutionContext context, IGameApiClient gameApiClient, string playerId) { var requestData = new SetItemBatchBody(new List<SetItemBody> { new (HealthKey, 95), new (StaminaKey, 20) }); await gameApiClient.CloudSaveData.SetItemBatchAsync(context, context.ServiceToken, context.ProjectId, playerId, requestData); } private async Task QueryPlayerDataAsync(IExecutionContext context, IGameApiClient gameApiClient) { var query = new QueryIndexBody( new List<FieldFilter> { new(HealthKey, 100, FieldFilter.OpEnum.LT) }, new List<string> { StaminaKey } ); var result = await gameApiClient.CloudSaveData.QueryDefaultPlayerDataAsync(context, context.ServiceToken, context.ProjectId, query); _logger.LogInformation("Query results: {Results}", JsonSerializer.Serialize(result.Data.Results)); } private async Task SetCustomDataAsync(IExecutionContext context, IGameApiClient gameApiClient) { await gameApiClient.CloudSaveData.SetPrivateCustomItemAsync(context, context.ServiceToken,context.ProjectId, CastleLevelKey, new SetItemBody(DifficultyKey, EasyValue)); await gameApiClient.CloudSaveData.SetPrivateCustomItemAsync(context, context.ServiceToken, context.ProjectId, ForestLevelKey, new SetItemBody(DifficultyKey, HardValue)); } private async Task QueryCustomDataAsync(IExecutionContext context, IGameApiClient gameApiClient) { var query = new QueryIndexBody( new List<FieldFilter> { new(DifficultyKey, EasyValue, FieldFilter.OpEnum.EQ) } ); var result = await gameApiClient.CloudSaveData.QueryPrivateCustomDataAsync(context, context.ServiceToken, context.ProjectId, query); _logger.LogInformation("Private query results: {Results}", JsonSerializer.Serialize(result.Data.Results)); } private async Task SetProtectedPlayerDataAsync(IExecutionContext context, IGameApiClient gameApiClient, string playerId) { var requestData = new SetItemBatchBody(new List<SetItemBody> { new (LevelKey, 15), new (ExperiencePointsKey, 20) }); await gameApiClient.CloudSaveData.SetProtectedItemBatchAsync(context, context.ServiceToken, context.ProjectId, playerId, requestData); } private async Task QueryProtectedPlayerDataAsync(IExecutionContext context, IGameApiClient gameApiClient) { var query = new QueryIndexBody( new List<FieldFilter> { new(LevelKey, 5, FieldFilter.OpEnum.GT) }, new List<string> { ExperiencePointsKey, LevelKey } ); var result = await gameApiClient.CloudSaveData.QueryProtectedPlayerDataAsync(context, context.ServiceToken, context.ProjectId, query); _logger.LogInformation("Protected player query results: {Results}", JsonSerializer.Serialize(result.Data.Results)); }}
ManagePlayerDataAsyncTo check the output, refer to the Cloud Code logs in the Unity Cloud Dashboard. The logs should show the retrieved items for each query.using System.Collections.Generic;using System.Threading.Tasks;using UnityEngine;using Unity.Services.Authentication;using Unity.Services.CloudCode;using Unity.Services.CloudCode.GeneratedBindings;using Unity.Services.Core;public class Test : MonoBehaviour{ // Call this method to roll the dice (use a button) public async void Awake() { await UnityServices.InitializeAsync(); // Sign in anonymously into the Authentication service if (!AuthenticationService.Instance.IsSignedIn) await AuthenticationService.Instance.SignInAnonymouslyAsync(); try { var module = new CloudSaveModuleBindings(CloudCodeService.Instance); await module.ManagePlayerData(AuthenticationService.Instance.PlayerId); } catch (CloudCodeException exception) { Debug.LogException(exception); } }}
Aggregate calls to Cloud Save
You can use Cloud Code modules to aggregate calls to Cloud Save. This is useful in case of high-frequency calls to Cloud Save. Refer to Advance a community goal for an example.Use write locks
You can use write locks to prevent concurrent writes to the same data in Cloud Save. This is useful if you want to ensure that only one player can modify a specific piece of data at a time. The sample below demonstrates how to use write locks in Cloud Code modules to prevent concurrent writes to the same data in Cloud Save. TheSetGameDataretryActionusing System;using System.Collections.Generic;using System.Net;using System.Threading.Tasks;using Microsoft.Extensions.Logging;using Unity.Services.CloudCode.Apis;using Unity.Services.CloudCode.Core;using Unity.Services.CloudCode.Shared;using Unity.Services.CloudSave.Model;public class GameDataManagement{ private const string GameVariablesId = "GAME_VARIABLES"; private readonly int _defaultRetryAttempts = 3; private readonly ILogger<GameDataManagement> _logger; public GameDataManagement(ILogger<GameDataManagement> logger) { _logger = logger; } [CloudCodeFunction("SetGameData")] public async Task SetGameData(IExecutionContext context, IGameApiClient gameApiClient, string key, string value, string? writeLock = null, Func<Item, string, Task<string?>>? retryAction = null) { var cloudSave = gameApiClient.CloudSaveData; try { await cloudSave.SetCustomItemAsync(context, context.ServiceToken, context.ProjectId, GameVariablesId, new SetItemBody(key, value, writeLock)); _logger.LogInformation("Game data {key} created with value {value}!", GameVariablesId, value); } catch (ApiException e) { if (e.Response.StatusCode == HttpStatusCode.Conflict) { _logger.LogError("There was a conflict when trying to set game data. Error: {Message}", e.Message); if (retryAction != null) { _logger.LogInformation("Retrying action..."); await Retry(context, gameApiClient, key, value, retryAction); return; } } _logger.LogError("Failed to create game data. Error: {Message}", e.Message); throw new Exception($"Failed to create game data. Error: {e.Message}"); } } [CloudCodeFunction("GetGameData")] public async Task<List<Item>> GetGameData(IExecutionContext context, IGameApiClient gameApiClient) { var cloudSave = gameApiClient.CloudSaveData; try { var defaultResult = await cloudSave.GetCustomItemsAsync(context, context.AccessToken, context.ProjectId, GameVariablesId); return defaultResult.Data.Results; } catch (ApiException e) { _logger.LogError("Failed to get game data. Error: {Message}", e.Message); throw new Exception($"Failed to get game data. Error: {e.Message}"); } } private async Task Retry(IExecutionContext context, IGameApiClient gameApiClient, string key, string value, Func<Item, string, Task<string?>> action) { var retryAttempts = 0; while (retryAttempts < _defaultRetryAttempts) { try { var currentData = await GetGameData(context, gameApiClient); var existingGameData = currentData.Find(item => item.Key == key); if (existingGameData == null) { _logger.LogError("The game data {key} does not exist.", key); return; } var currentWriteLock = existingGameData.WriteLock; var newItemToSet = await action(existingGameData, value); if (newItemToSet == null) { _logger.LogInformation("No new data to set for game data {key}.", key); return; } _logger.LogInformation("Retrying to set game data {key} with value {value}...", key, value); await SetGameData(context, gameApiClient, key, newItemToSet, currentWriteLock); return; } catch (Exception e) { _logger.LogError("Failed to retry action, attempt {attempt}. Error: {Message}", retryAttempts + 1, e.Message); retryAttempts++; if (retryAttempts != _defaultRetryAttempts) continue; _logger.LogError("Failed to retry action after maximum of {retryAttempts} attempts. Error: {Message}", _defaultRetryAttempts, e.Message); throw new Exception($"Failed to retry action after maximum of {_defaultRetryAttempts} attempts. Error: {e.Message}"); } } }}
Use Cloud Save with Cloud Code scripts
You can use Cloud Code scripts to interact with Cloud Save data. The following examples show how to modify player data and cross-player data using the Cloud Save SDK for Cloud Code scripts.Set up the script
To use Cloud Save with Cloud Code scripts, you first need to set up a Cloud Code script. Refer to the Getting started with Cloud Code scripts guide. Set up the Cloud Save SDK in your script by importing the Cloud Save package: Javascript:const { DataApi } = require('@unity-services/cloud-save-1.4');
Modify player data
To interact with player's data, you need to authenticate using the access token. This restricts Cloud Code's access to the data of the player who is authenticated in the client. Javascript:The following example shows how to modify a player's data stored in Cloud Save. Javascript:const cloudSaveApi = new DataApi({accessToken: context.accessToken});
const { DataApi } = require('@unity-services/cloud-save-1.4');module.exports = async ({ params, context, logger }) => { const { projectId, playerId } = context; const cloudSaveApi = new DataApi({accessToken: context.accessToken}); try { const response = await cloudSaveApi.setItem( projectId, playerId, { key: 'data', value: 'saved' } ); } catch (err) { logger.error("Error while calling out to Cloud Save", { "error.message": err.message }); throw err; }};
Modify cross-player data
To interact with cross-player or Game Data, you need to authenticate using the service token. To authenticate with the service token, pass the context object to the Cloud Save SDK client. Javascript:const cloudSaveApi = new DataApi(context);
By passing another player’s ID to the Cloud Save SDK, you can read and write their Cloud Save data, allowing asynchronous multiplayer interactions. This would not be possible from within the client, where users are only permitted to access their own data.
The following example shows how to provide a commendation to another player within the same lobby. The sample code demonstrates accessing and modifying another player's data stored in Cloud Save. The
otherPlayerIdFor samples on how to access different access-level data and query data using Cloud Code scripts, refer to the Read and write data. For more samples on how to use cross-player data in Cloud Code scripts, refer to Cross Player Data.const { DataApi } = require('@unity-services/cloud-save-1.4');module.exports = async ({ params, context, logger }) => { const { projectId } = context; const { otherPlayerId } = params; // Initialize the cloud save API client const cloudSaveAPI = new DataApi(context); // Access the save data for the specified player const otherPlayerSave = await cloudSaveAPI.getItems( projectId, otherPlayerId, // or any other player ID "likes" // Cloud Code key ); // Assume that player's likes is 0 if the key doesn't exist const otherPlayerLikes = otherPlayerSave.data.results[0]?.value || 0; // Update the data for the specified player cloudSaveAPI.setItem( projectId, otherPlayerId, { key: "likes", value: otherPlayerLikes + 1 } )};
Modify data as a response to a game event
You can use Cloud Code to modify data in response to a game event. For example, you can use Cloud Code to update the player's data when they complete a quest or level up. This can be achieved by using Triggers, which allow Cloud Code to run in response to events in your game. Cloud Save emits events when data is updated. You can use these events to trigger Cloud Code logic that modifies the data. Triggers can be activated either a Cloud Code script or a Cloud Code module, depending on your use case. Refer to the table below for use case samples integrating Cloud Save with Cloud Code Triggers.Use case | Description |
|---|---|
| Announce level up | Announce a player’s level up to all other players in any lobbies the player is in. |
| Initialize player data | Initialize player's data in Cloud Save when a new player signs in. |
| Tune game difficulty | Adjust the game difficulty stored in Cloud Save based on player activity. |