Adding Saved Games to Your Game

This guide shows you how to save and load a player’s game progress data using the Saved Games service in a C++ application. You can use this service to automatically load and save player game progress at any point during gameplay. This service can also enable players to trigger a user interface to update or restore an existing save game, or create a new one.

Before you begin

If you haven't already done so, you might find it helpful to review the Saved Games game concepts.

Before you start to code using the Saved Games API:

Data formats and cross-platform compatibility

Saved Games data that you save to Google’s servers must be in std::vector<uint8_t> format. The Saved Games service takes care of encoding your data for cross-platform compatibility; Android applications can read in this same data as a byte array without any cross-platform compatibility issues.

Avoid using platform-specific formats when choosing a data format for your Saved Games data. We strongly encourage you to use a data format, like XML or JSON, that has strong library support on multiple platforms.

Enabling the Saved Games service

Before you can use the Saved Games service, you must first enable access to it. To do so, call EnableSnapshots() when you create the service with gpg::GameServices::Builder. This will enable the additional auth scopes required by Saved Games at the next auth event.

Displaying Saved Games

In your game, you can provide an option that players can trigger to save or restore saved games. When players select this option, your game should bring up a screen that displays existing save slots, and allow players to either save to or load from one of these slots, or create a new saved game. Use the following method to do so:

  SnapshotManager::ShowSelectUIOperation(...)

The Saved Games selection UI allows players to create a new saved game, view details about existing saved games, and load previous save games.

  SnapshotManager::SnapshotSelectUIResponse response;
  if (IsSuccess(response.status)) {
  if (response.data.Valid()) {
    LogI("Description: %s", response.data.Description().c_str());
    LogI("FileName %s", response.data.FileName().c_str());
    //Opening the snapshot data
    …
  } else {
    LogI("Creating new snapshot");
    …
  }
} else {
  LogI("ShowSelectUIOperation returns an error %d", response.status);
}

The following example illustrates how to bring up the default Saved Games UI and handle the player's UI selection:

  service_->Snapshots().ShowSelectUIOperation(
  ALLOW_CREATE_SNAPSHOT,
  ALLOW_DELETE_SNAPSHOT,
  MAX_SNAPSHOTS,
  SNAPSHOT_UI_TITLE,
  [this](gpg::SnapshotManager::SnapshotSelectUIResponse const & response) {
  …
      }

If, in the example above, ALLOW_CREATE_SNAPSHOT is true and MAX_SNAPSHOTS is greater than the actual number of snapshots that the user has currently created, the default Snapshot UI provides players with a button to create a new save game, rather than selecting an existing one. (When displayed, the button is at the bottom of the UI.) When a player clicks on this button, the SnapshotSelectUIResponse response is valid but has no data.

Opening and reading saved games

To access a saved game and read or modify its contents, first open the SnapshotMetadata object representing that saved game. Next, call the SnapshotManager::Read*() method.

The following example shows how to open a saved game:

  LogI("Opening file");
  service_->Snapshots()
  .Open(current_snapshot_.FileName(),
               gpg::SnapshotConflictPolicy::BASE_WINS,
        [this](gpg::SnapshotManager::OpenResponse const & response) {
           LogI("Reading file");
           gpg::SnapshotManager::ReadResponse responseRead =
           service_->Snapshots().ReadBlocking(response.data);
          …
        }

Detecting and resolving data conflicts

When you open a SnapshotMetadata object, the Saved Games service detects whether a conflicting saved game exists. Data conflicts might occur when the saved game stored on a player's local device is out-of-sync with the remote version stored in Google's servers.

The conflict policy you specify when opening a saved game tells the Saved Games service how to automatically resolve a data conflict. The policy can be one of the following:

Conflict Policy Description
SnapshotConflictPolicy::MANUAL Indicates that the Saved Games service should perform no resolution action. Instead, your game will perform a custom merge.
SnapshotConflictPolicy::LONGEST_PLAYTIME Indicates the Saved Games service should pick the saved game with the largest play time value.
SnapshotConflictPolicy::BASE_WINS Indicates the Saved Games service should pick the base saved game.
SnapshotConflictPolicy::REMOTE_WINS Indicates the Saved Games service should pick the remote saved game. The remote version is a version of the saved game that is detected on one of the player's devices and has a more recent timestamp than the base version.

If you specified a conflict policy other than GPGSnapshotConflictPolicyManual, the Saved Games service will merge the saved game and return the updated version through the resulting SnapshotManager::OpenResponse value. Your game can open the saved game, write to it, then call the SnapshotManager::Commit(...) method to commit the saved game to Google’s servers.

Performing a custom merge

If you specified SnapshotConflictPolicy::MANUAL as the conflict policy, your game must resolve any data conflict detected before performing further read or write operations on the saved game.

In this case, when a data conflict is detected, the service returns the following parameters through SnapshotManager::OpenResponse:

  • A conflict_id to uniquely identify this conflict (you will use this value when committing the final version of the saved game);
  • The conflicting base version of the saved game; and,
  • The conflicting remote version of the saved game.

Your game must decide what data to save, then call the SnapshotManager::ResolveConflictBlocking() method to commit/resolve the final version to Google’s servers.

    //Resolve conflict
    gpg::SnapshotManager::OpenResponse resolveResponse =
        manager.ResolveConflictBlocking(openResponse.conflict_base, metadata_change,
                                  openResponse.conflict_id);

Writing saved games

To write a saved game, first open the SnapshotMetadata object representing that saved game, resolve any data conflicts detected, then call the SnapshotManager::Commit() method to commit your saved game changes.

The following example shows how you might create a change and commit a saved game.

  1. First, open the snapshot we want to edit, and make sure all conflicts are resolved by choosing the base.

    service_->Snapshots().Open(
          file_name,
          gpg::SnapshotConflictPolicy::BASE_WINS,
          [this](gpg::SnapshotManager::OpenResponse const &response) {
            if (IsSuccess(response.status)) {
              // metadata : gpg::SnapshotMetadata
              metadata = response.data;
            } else {
              // Handle snapshot open error here
            }
          });
    
  2. Next, create a saved game change that includes the image data used for the cover image:

    gpg::SnapshotMetadataChange::Builder builder;
    gpg::SnapshotMetadataChange metadata_change =
        builder.SetDescription("CollectAllTheStar savedata")
                 .SetCoverImageFromPngData(pngData).Create();
    
  3. Finally, commit the saved game changes.

    gpg::SnapshotManager::CommitResponse commitResponse =
        service_->Snapshots().CommitBlocking(metadata, metadata_change, SetupSnapshotData());
    

    The data parameter contains all of the save game data that you are storing. The change also contains additional saved game metadata, such as time played and a description for the saved game.

If the commit operation completed successfully, players can see the saved game in the Saved Games selection UI.