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:
- Install the C++ Play Games SDK.
- Set up your C++ development environment.
- Download and review the C++ code sample.
- Enable the Saved Games service in the Google Play Console.
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.
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 } });
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();
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.