Advance a community goal
Use Cloud Code to enable players to contribute to a community goal. A community goal is a goal that players can contribute to collectively. The goal can be anything that contributes to a score or a count, such as to collect items, defeat enemies, or complete quests. It's up to you to define the goal and how players can contribute to it.
Cloud Save stores the state of the goal in game data. To contribute to the goal, players call a Cloud Code function.
In many instances, this might be a heavy operation, because you need to update the goal progress in real-time as players contribute to the goal. To reduce the number of calls to Cloud Save, you can aggregate the player contributions and update the goal progress in Cloud Save at regular intervals.
To enable players to contribute to a community goal, complete the following tasks:
- Aggregate plater contributions
- Set up the
ScoreAggregator
as a singleton - Use the
ScoreAggregator
in your Cloud Code module - Test the Cloud Code module
- Verify the Cloud Save data
Prerequisites
- Follow the get started guide to generate a Cloud Code module.
The get started page provides a workflow of how to set up the Cloud Code module so that you can implement the community goal use case.
Aggregate player contributions
If you call out to Cloud Save for every individual player contribution, it can increase costs. To reduce the number of calls to Cloud Save, aggregate the player contributions and update the goal progress in Cloud Save at regular intervals. The ScoreAggregator
class in the following sample demonstrates how to aggregate player contributions and update the goal progress in Cloud Save.
In the following sample, the ScoreAggregator
class has a RunningCount
field that stores the total number of items contributed by players. The Increment
method increments the RunningCount
field by the number of items that the player contributes. The method also updates the goal progress in Cloud Save at regular intervals.
The increments are locked to ensure that each request successfully updates the count and isn't overwritten. The method also locks the LastUpdate
field so that multiple simultaneous calls don't all hit Cloud Save at the same time.
Create a new file ScoreAggregator.cs
in your Cloud Code module and add the following code:
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.CloudSave.Api;
using Unity.Services.CloudSave.Model;
public interface IScoreAggregator
{
Task Increment(IExecutionContext ctx, long score);
}
public class ScoreAggregator : IScoreAggregator
{
private Lockable<long> RunningCount = new(0);
private Lockable<DateTime> LastUpdate = new(DateTime.UtcNow);
private readonly ICloudSaveDataApi _cloudSave;
private readonly ILogger<ScoreAggregator> _logger;
public ScoreAggregator(IGameApiClient gameApiClient, ILogger<ScoreAggregator> logger)
{
_logger = logger;
_cloudSave = gameApiClient.CloudSaveData;
}
public async Task Increment(IExecutionContext ctx, long score)
{
// Lock the running count to ensure that each request successfully updates the count and isn't overwritten
lock (RunningCount)
{
RunningCount.Value += score;
}
// To make sure multiple simultaneous calls don't all hit Cloud Save at the same time, lock LastUpdate and use this update flag
var update = false;
lock (LastUpdate)
{
// You can modify the granularity based on your needs
if (DateTime.UtcNow > LastUpdate.Value.AddSeconds(10))
{
// After we decide we need to update Cloud Save, reset the LastUpdate time so that the next update is 10 seconds from now
LastUpdate.Value = DateTime.UtcNow;
update = true;
}
}
if (update)
{
var currentGlobalScore = await _cloudSave.GetCustomItemsAsync(ctx, ctx.ServiceToken, ctx.ProjectId, "global",
new List<string>() { "event_score" });
var item = currentGlobalScore.Data.Results[0];
long scoreToAdd;
lock (RunningCount)
{
scoreToAdd = RunningCount.Value;
RunningCount.Value = 0;
}
_logger.LogDebug("Setting score to {newScore}", scoreToAdd + (long)item.Value);
var response = await _cloudSave.SetCustomItemAsync(
ctx, ctx.ServiceToken, ctx.ProjectId, "global", new SetItemBody("event_score", scoreToAdd + (long)item.Value, item.WriteLock));
// If the Cloud Save request was unsuccessful, add the score difference back to the running count so that it's included in the next update
if (response.StatusCode != HttpStatusCode.OK)
{
lock (RunningCount)
{
scoreToAdd = RunningCount.Value + (long)item.Value;
RunningCount.Value += scoreToAdd;
}
}
}
}
// C# can't lock value types, so instead, lock an instance of this class that holds a value
private class Lockable<T>
{
public T Value { get; set; }
public Lockable(T value)
{
Value = value;
}
}
}
Set up the ScoreAggregator
as a singleton
To ensure that ScoreAggregator
holds state between requests, use dependency injection to set up the ScoreAggregator
as a singleton.
Modules use an in-memory cache to store the state between invocations. Refer to Module persistence between invocations for more information on the limitations of in-memory caches.
Define a Configuration
class that implements the ICloudCodeSetup
interface:
C#
using Microsoft.Extensions.DependencyInjection;
using Unity.Services.CloudCode.Apis;
using Unity.Services.CloudCode.Core;
namespace CommunityGoal;
public class Configuration : ICloudCodeSetup
{
public void Setup(ICloudCodeConfig config)
{
config.Dependencies.AddSingleton(GameApiClient.Create());
config.Dependencies.AddSingleton<IScoreAggregator, ScoreAggregator>();
}
}
Use the ScoreAggregator
in your Cloud Code module
Use the ScoreAggregator
in your Cloud Code module to increment the player contributions.
You can also define a helper method to initialize the Cloud Save data. Call the helper method once to set up the Cloud Save data.
Define a Main
class that uses the ScoreAggregator
:
C#
using System.Threading.Tasks;
using Unity.Services.CloudCode.Apis;
using Unity.Services.CloudCode.Core;
using Unity.Services.CloudSave.Model;
namespace CommunityGoal;
public class MyModule
{
[CloudCodeFunction("AddScore")]
public async Task AddScore(IExecutionContext ctx, IScoreAggregator scoreAggregator, int score)
{
await scoreAggregator.Increment(ctx, score);
}
// This is a setup function for initializing the Cloud Save data. Only call it once.
// Don't include this in a live version!
[CloudCodeFunction("InitializeCloudSave")]
public async Task InitializeCloudSave(IExecutionContext ctx, IGameApiClient apiClient)
{
await apiClient.CloudSaveData.SetCustomItemAsync(
ctx, ctx.ServiceToken, ctx.ProjectId, "global", new SetItemBody("event_score", 0));
}
}
Test the Cloud Code module
Generate bindings and deploy the module.
Next, define a MonoBehaviour script that calls the AddScore
function to increment the player contributions. The following test script is a simple example that increments the score by 10:
Note: In a live version, replace the score with your own logic to generate the score.
C#
using Unity.Services.Authentication;
using Unity.Services.CloudCode;
using Unity.Services.CloudCode.GeneratedBindings;
using Unity.Services.Core;
using UnityEngine;
public class TestModule : MonoBehaviour
{
private async void Start()
{
// Initialize the Unity Services Core SDK
await UnityServices.InitializeAsync();
// Authenticate by logging into an anonymous account
await AuthenticationService.Instance.SignInAnonymouslyAsync();
try
{
var score = Random.Range(0, 100); // Replace this with your own logic to generate the score
var module = new CommunityGoalBindings(CloudCodeService.Instance);
await module.InitializeCloudSave(); // Remove this line after the Cloud Save data is initialized
await module.AddScore(score);
}
catch (CloudCodeException exception)
{
Debug.LogException(exception);
}
}
}
Attach the MonoBehaviour
script to a GameObject in your scene and run the scene.
After you verify that the Cloud Code module works as you expect, you can remove the InitializeCloudSave
function from the Cloud Code module.
You can further customize the Cloud Code module to suit your game's requirements for score generation.
Verify the Cloud Save data
Track the goal progress update in Cloud Save as players contribute to the goal.
- Open the Unity Cloud Dashboard.
- Navigate to Products, and select Cloud Save.
- Select Game Data.
- Find and select the
global
collection.
The value of the item should increase as players contribute to the goal.