Store data using Cloud Code
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.
Additionally, use access control to restrict access to the Cloud Save APIs. This is useful if you want to prevent players from accessing the APIs directly from the client.
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 the ICloudCodeSetup
interface. This class configures the Cloud Code module and sets up the dependencies.
C#
using 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());
}
}
This setup allows you to use the Cloud Save SDK in your Cloud Code module by passing the GameApiClient
to the Cloud Code module as a dependency.
C#
[CloudCodeFunction("SaveData")]
public async Task SaveData(IExecutionContext context, IGameApiClient gameApiClient, string key, string value)
{
...
// Use the gameApiClient.CloudSave to interact with Cloud Save data
}
For more information on how Cloud Code integrates with Unity services, refer to Integrate with other Unity services.
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#
using 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}");
}
}
}
You can use editor bindings to call the Cloud Code module from the Unity Editor.
After you generate the bindings, you can call the SaveData
and GetData
functions from your MonoBehaviour scripts:
C#
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);
}
}
}
When successful, the output should be similar to the following:
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. The playerId
parameter identifies the player to access.
Note: If you want to interact with the data of the player who is authenticated in the client, use the access token instead of the service token. The service token in the sample below is used to access the data of any player in the game, allowing to read and write their Cloud Save data.
C#
using 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}");
}
}
}
You can use editor bindings to call the Cloud Code module from the Unity Editor.
After you generate the bindings, you can call the SaveData
and GetData
functions from your MonoBehaviour scripts with the same approach shown in the previous section.
To identify the player whose data you want to access, you need to provide the playerId
parameter to the SaveData
and GetData
functions.
The recommended way to get another player ID is to retrieve it from another Unity service in Cloud Code. Refer to Cross Player Data for more information on how to access and modify another player's data.
Manage 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#
using 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}");
}
}
You can use editor bindings to call the Cloud Code module from the Unity Editor.
After you generate the bindings, call the ManageDataAccessClasses
function from your MonoBehaviour script:
C#
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);
}
}
}
To 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.
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
Note: Only data submitted after index creation is available for querying.
The sample code demonstrates how you can query Cloud Save data using different access modifiers for players and custom entities:
C#
using 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));
}
}
You can use editor bindings to call the Cloud Code module from the Unity Editor.
After you generate the bindings, you can call the ManagePlayerDataAsync
function from your MonoBehaviour script:
C#
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);
}
}
}
To check the output, refer to the Cloud Code logs in the Unity Cloud Dashboard. The logs should show the retrieved items for each query.
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.
The SetGameData
function sets a write lock on the data being modified. If another call tries to modify the same data while the write lock is held, they will receive a conflict error.
However, if there is a retry action provided, the function will retry the operation. The retryAction
function argument is called to get the new value to set for the game data.
This determines what value to set for the game data in case of a conflict, allowing you to implement custom logic for resolving conflicts.
C#
using 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
const cloudSaveApi = new DataApi({accessToken: context.accessToken});
The following example shows how to modify a player's data stored in Cloud Save.
Javascript
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);
Note: When you pass in the context object to the client, you authenticate the client with the
serviceToken
by default.
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 otherPlayerId
parameter identifies the other player.
Javascript
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 }
)
};
For 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.
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.
Note: The following samples use Cloud Code modules.
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. |