Documentation

Support

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:
  1. Aggregate plater contributions
  2. Set up the
    ScoreAggregator
    as a singleton
  3. Use the
    ScoreAggregator
    in your Cloud Code module
  4. Test the Cloud Code module
  5. 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:
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.
Define a
Configuration
class that implements the
ICloudCodeSetup
interface:
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
:
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:
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.
  1. Open the Unity Dashboard.
  2. Navigate to Products, and select Cloud Save.
  3. Select Game Data.
  4. Find and select the
    global
    collection.
The value of the item should increase as players contribute to the goal.