Advance a community goal
Enable players to contribute to a collective goal that other players help advance.
Read time 4 minutesLast updated 18 hours ago
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 as a singleton
ScoreAggregator - Use the in your Cloud Code module
ScoreAggregator - Test the Cloud Code module
- Verify the Cloud Save data
Prerequisites
- Follow the get started guide to generate a Cloud Code module.
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. TheScoreAggregatorScoreAggregatorRunningCountIncrementRunningCountLastUpdateScoreAggregator.csusing 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 ScoreAggregatorScoreAggregatorScoreAggregator
Define a
ConfigurationICloudCodeSetupusing 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 ScoreAggregatorScoreAggregatorMainScoreAggregatorusing 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 theAddScoreAttach theusing 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); } }}
MonoBehaviourInitializeCloudSaveVerify the Cloud Save data
Track the goal progress update in Cloud Save as players contribute to the goal.- Open the Unity Dashboard.
- Navigate to Products, and select Cloud Save.
- Select Game Data.
- Find and select the collection.
global