The creator-centric platform for building live games in Unity

Easily add social, commerce, and content management features to your live game with low-code, drag-and-drop prefabs fully integrated with your Unity workflow.

Code

Allow player to store progress

Overview

Here is everything needed to use the Cloud Save feature in the "Beamable SDK for Unity".

The purpose of this feature is to allow the player to store and retrieve progress in their game.

The Cloud Data is fetched online and stored locally; scoped by game and player. As changes are detected, the system automatically keeps data in sync.

API

Unlike many Beamable FeatureFeature - An individual aspect of the Beamable product used to create a great user experiences, Cloud Save does not require a specific Beamable Feature PrefabFeature Prefab - The drag-and-drop template for a specific Beamable product feature to be used. The main entry point to this feature is C# programming.

šŸ“˜

Learning Fundamentals

Game makers who are new to Unity and C# can review the fundamentals here.

ā€¢ See Beamable: Asynchronous Programming for more info

Here are API highlights for beamableAPI.CloudSavingService.

Note: This API is demonstrated in the CloudSavingServiceExample.cs below.

Method Name

Detail

Init

Resolves local game data against what the server has stored (downloads server content and uploads local content)

Note: This accepts an optional pollingIntervalSecs parameter which determines is how often the service will scan for local file changes

isInitializing

Returns a bool. The value is true during service startup, during syncing. This value is false after the client/server data matches properly

Note: It is suggested to check if (!isInitializing) before calling Init or ReinitializeUserData to prevent unneeded concurrent operations

EnsureRemoteManifest

Checks if player data exists on the Beamable back-end and returns the manifest if it exists

ReinitializeUserData

Forces a download of player data from the Beamable back-end. This replaces all local content with what is stored by the back-end

UpdateReceived

The system invokes this event when content has changed on the server. This may be triggered by the Portal or by another game client

OnError

The system invokes this event when there is an error downloading, saving, or uploading Cloud Data

Storage

Data is stored in within Unity's Application.persistentDataPath and prefixed with the Customer ID (CIDCID - The customer identification. Beamable generates this for each customer), Project ID (PIDPID - The project identification. Beamable generates this for each project), and Player ID (DBIDDBID - The database identification. Beamable generates an anonymous account for the player when the project first runs). This is to ensure data is scoped to a game and a player. The full path is available at beamableAPI.CloudSavingService.LocalCloudDataFullPath.

Once the Cloud Saving service is initialized, the local Cloud Saving storage path is monitored by the service automatically for changes. When changes are detected, references to the changed files will be queued up and uploaded in batches.

<persistentDataPath>/beamable/cloudsaving/<cid>/<pid>/<dbid>/<objectkey>

Syncing Data

The downloading operation uses Unity's DownloadHandlerFile and content writes to a /tmp/ folder first. The file checksum is checked against the manifest checksum, and upon successful validation the tmp file(s) are deleted properly.

Note If the destination files are kept open by some unrelated system during the syncing process, the CloudSavingService will reattempt several times. Upon any ultimate failure, an IOException will be thrown.*

Cloud Data Manifest

When changes are done uploading to the server, a manifest will be written locally here:

Path.Combine(Directory.GetParent(_beamableAPI.CloudSavingService.LocalCloudDataFullPath).ToString(),"cloudDataManifest.json");

Cloud Saved files are written to an object store that is managed by the Cloud Saving service. The key of the object is the MD5 checksum of the content in the file, this is done to preserve history of the files.

The manifest tracks the MD5 checksum(etag) of the file and the common name(key) of the file, so we can fetch the object by its MD5 checksum and write it to disk using the common name.

Json Format

{
   "id":"1366764038702081",
   "manifest":[
      {
         "bucketName":"beam-cloud-saving",
         "key":"ComplexFile.json",
         "size":642,
         "lastModified":20210602112148,
         "eTag":"B0FAB230EE1DB774FFE1DE866309D720"
      }
   ],
   "replacement":false
}

Examples

Here are examples which cover common programming needs.

šŸ“˜

Beamable SDK Examples

ā€¢ This and all examples are available for download at GitHub.com/Beamable_SDK_Examples

In this CloudSavingServiceExample.cs, the AudioSettings for a player is loaded if it exists. Otherwise it is created and saved. This is a common pattern for handling data which may or may not exist yet.

using System.IO;
using Beamable.Api.CloudSaving;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace Beamable.Examples.Services.CloudSavingService
{
  [System.Serializable]
  public class MyCustomSettings
  {
    public float Volume = 100;
    public bool IsMuted = false;
  }
  
  
  /// <summary>
  /// Demonstrates <see cref="CloudSavingService"/>.
  /// </summary>
  public class CloudSavingServiceExample : MonoBehaviour
  {
    //  Fields  ---------------------------------------
    [SerializeField] private MyCustomSettings myCustomSettings = null;
    private IBeamableAPI _beamableAPI;
    private Api.CloudSaving.CloudSavingService _cloudSavingService;

    
    //  Unity Methods  --------------------------------
    protected void Start()
    {
      Debug.Log($"Start()");

      SetupBeamable();
    }

    
    //  Methods  --------------------------------------
    private async void SetupBeamable()
    {
      _beamableAPI = await Beamable.API.Instance;

      Debug.Log($"beamableAPI.User.id = {_beamableAPI.User.id}");
      
      _cloudSavingService = _beamableAPI.CloudSavingService;
      
      // Subscribe to the UpdatedReceived event to handle
      // when data on disk does not yet exist and is pulled
      // from the server
      _cloudSavingService.UpdateReceived += 
        CloudSavingService_OnUpdateReceived;
      
      // Subscribe to the OnError event to handle
      // when the service fails
      _cloudSavingService.OnError += CloudSavingService_OnError;
      
      // Init the service, which will first download content
      // that the server may have, that the client does not.
      // The client will then upload any content that it has,
      // that the server is missing
      await _cloudSavingService.Init();
      
      // Gets the cloud data manifest (ManifestResponse) OR
      // creates an empty manifest if the user has no cloud data 
      await _cloudSavingService.EnsureRemoteManifest();
      
      // Resets the local cloud data to match the server cloud
      // data. Enable this if desired 
      //
      //await _cloudSavingService.ReinitializeUserData();

      // Now, attempt to load data from the CloudSavingService,
      // and if it is not found, create it locally and 
      // save it to the CloudSavingService
      myCustomSettings = ReloadOrCreateAudioSettings();
    }

    
    private MyCustomSettings ReloadOrCreateAudioSettings()
    {
      Debug.Log($"ReloadOrCreateAudioSettings()");
      
      // Creates all directories and subdirectories in the
      // specified path unless they already exist
      Directory.CreateDirectory(_cloudSavingService.LocalCloudDataFullPath);
      
      MyCustomSettings settings;
      var audioFileName = "audioFile.json";
      var audioPath = $"{_cloudSavingService.LocalCloudDataFullPath}{Path.DirectorySeparatorChar}{audioFileName}";
      
      if (File.Exists(audioPath))
      {
        // Reload AudioSettings 
        Debug.Log($"Existing AudioSettings found.");
        Debug.Log($"Reload AudioSettings.");
        
        var json = File.ReadAllText(audioPath);
        settings = JsonUtility.FromJson<MyCustomSettings>(json);
      }
      else
      {
        // Create AudioSettings
        Debug.Log($"Existing AudioSettings not found.");
        Debug.Log($"Create AudioSettings.");
        
        settings = new MyCustomSettings
        {
          IsMuted = false,
          Volume = 1
        };

        var json = JsonUtility.ToJson(settings);
        Directory.CreateDirectory(Path.GetDirectoryName(audioPath));
        
        // Once the data is written to disk, the service will
        // automatically upload the contents to the cloud
        File.WriteAllText(audioPath, json);

      }
      return settings;
    }
    
    
    //  Event Handlers  -------------------------------
    private void CloudSavingService_OnUpdateReceived(ManifestResponse manifest)
    {
      Debug.Log($"CloudSavingService_OnUpdateReceived()");
      
      // If the settings are changed by the server, reload the scene
      SceneManager.LoadScene(0);
    }
    
    
    private void CloudSavingService_OnError(CloudSavingError cloudSavingError)
    {
      Debug.Log($"CloudSavingService_OnError() Message = {cloudSavingError.Message}");
    }
  }
}

Advanced

This section contains any advanced configuration options and workflows.

Portal Management of Data

The Portal allows the game maker to manage player data as well. Search for a player, select the CloudData tab, and navigate to the player data.

Some common use-cases for game makers include;

  • Debugging - Play as a customer's player data to test and investigate reported issues
  • Support - Make minor edits to a player data and hot-upload it for the customer

Here are the major operations that can be performed against the player data.

CloudData on the Beamable "Portal"CloudData on the Beamable "Portal"

CloudData on the Beamable "Portal"

Name

Detail

  1. Copy

Allows a game maker with admin privileges to copy one player's data over an existing player's data

  1. Search

A contains-style search of the player data

  1. Download

Allows a game maker with admin privileges to download a copy of a single object of player data

  1. Upload

Replace an object of player data with a file that has the same name

šŸš§

Gotchas

Here are hints to help explain some of the trickier concepts.

ā€¢ While Beamable supports minor edits to the player data from the portal, restoring the a customer's player data completely to a historic backup state is not supported.
ā€¢ The CloudSavingService does not support multiple game sessions using the same user. If there are multiple sessions for the same user, for the same game, this will create an infinite ping/pong effect. E.g. Device A will send updates that Device B will fetch, which will send updates that Device A will fetch, etc...
ā€¢ Manually deleting content from the LocalCloudDataFullPath is not supported

Updated about a month ago


Code


Allow player to store progress

Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.