Saved Games

The Saved Games service gives you a convenient way to save your players' game progression to Google's servers. Your game can retrieve the saved game data to allow returning players to continue a game at their last save point from any device.

The Saved Games service makes it possible to synchronize a player's game data across multiple devices. For example, if you have a game that runs on Android, you can use the Saved Games service to allow a player to start a game on their Android phone, and then continue playing on a tablet without losing any of their progress. This service can also be used to ensure that a player's game play continues from where it left off even if their device is lost, destroyed, or traded in for a newer model.

 

Saved Games basics

A saved game consists of two parts:

  • An unstructured binary blob - this data can represent whatever you choose, and your game is responsible for parsing and writing to it.
  • Structured metadata - additional properties associated with the binary data that allow Play Games services to visually present Saved Games in the default Saved Games list user interface (UI), and to present useful information in the Google Play Games app (for example, last updated timestamp).

A game can write an arbitrary number of Saved Games for a single player, subject to user quota, so there is no hard requirement to restrict players to a single save file.

Cover images

The Saved Games service provides a visual user experience in addition to persistence features. You are strongly encouraged to associate representative images with corresponding save files. If you are using the default Saved Games list user interface (UI) provides by the Play Games services SDK in your game, the UI will display these cover images. The cover images may also appear in the Google Play Games app.

Descriptions

You can provide a short text description of the content of a particular saved game. This description is directly displayed to players and should summarize the state that the saved game represents; for example, “Fighting the Goblins in the Dark Woods”.

Quota

Developers are not charged for any saved game data that’s stored in the cloud. Instead, this data is counted against the player’s Google Drive quota - you never have to worry about it. The only quota that game developers need to care about is their Google Drive API quota

Read/Write isolation

All Saved Games are stored in your players' Google Drive Application Data Folder. This folder can only be read and written by your game - it cannot be viewed or modified by other developers’ games, so there is additional protection against data corruption. In addition, Saved Games are insulated from direct tampering by players so they cannot modify individual Saved Games.

Offline support

Your game can still read and write to a saved game when the player's device is offline, but will not be able to sync with Play Games services until network connectivity is established. Once reconnected, Play Games services asynchronously updates the saved game data on Google's servers.

Conflict resolution

When using the Saved Games service, your game may encounter conflicts when attempting to save data. These conflicts can occur when a user is running more than one instance of your application on different devices or computers. Your application must be able to resolve these conflicts in a way that provides the best user experience.

Typically, data conflicts occur when an instance of your application is unable to reach the Saved Games service while attempting to load data or save it. In general, the best way to avoid data conflicts is to always load the latest data from the service when your application starts up or resumes, and save data to the service with reasonable frequency. However, it is not always possible to avoid data conflicts. Your application should make every effort to handle conflicts such that your users' data is preserved and that they have a good experience.

Limits

Play Games services currently enforce size limits on binary data and cover image sizes of 3 MB and 800 KB respectively.

Saved game metadata

The structured metadata for a saved game contains these properties:

Property Description
ID A unique string generated by Play Games services for this saved game. Use this ID to refer to the saved game in your game clients.
Name A developer-supplied short name for the saved game, for example "Save slot 1" or "PlayerName_Save1". This is not shown to players.
Description A developer-supplied description of the saved game. The description is displayed to players in the Google Play Games app and the saved game list UI.
Last modified Timestamp in milliseconds generated by Play Games services for when the saved game was last updated.
Played time A developer-supplied time (in milliseconds) to display on the saved game. This value should represent how long the player has played the corresponding save game. For example, a played time value of 3600000 will be displayed by Play Games services as "1 hr".
Cover image This is an optional, developer-supplied property that contains information about the cover image.
 

 

Implementation

Specify Games, Plus and Drive API in the plugin settings.

Window -> Android Native -> Edit Settings

Maker sure that your Drive API is enabled in Developer Console

And make sure Saved Games in enabled in your play service setting in Play Service Console

 

Displaying Saved Games

You can integrate the Saved Games API wherever your game provides players with the option to save or restore their progress. Your game might display such an option at designated save/restore points or allow players to save or restore progress at any time.

Once players select the save/restore option in your game, your game should bring up a screen that prompts the players to enter information for a new saved game or select an existing saved game to restore. To simplify your development, the Saved Games API provides a default Saved Games selection user interface (UI) that you can use out-of-the-box. The Saved Games selection UI allows players to create a new saved game, view details about existing saved games, and load previous saved games.

The following snippet shows how to bring up the default Saved Games selection UI:

int maxNumberOfSavedGamesToShow = 5;
GooglePlaySavedGamesManager.Instance.ShowSavedGamesUI("See My Saves", maxNumberOfSavedGamesToShow);

 

Writing / Loading Saved Games

If the player selects to create a new saved game or load an existing saved game, the UI sends a request to Play Games services. If the request is successful, Play Games services return a GP_Snapshot object representing the saved game to your game through the ActionGameSaveLoaded callback.

The following code snippet shows a sample implementation of the ActionGameSaveLoaded callback via GooglePlaySavedGamesManager class

GooglePlaySavedGamesManager.ActionGameSaveLoaded += ActionGameSaveLoaded;

private void ActionGameSaveLoaded (GP_SpanshotLoadResult result) {

    Debug.Log("ActionGameSaveLoaded: " + result.Message);
    if(result.isSuccess) {

	    Debug.Log("Snapshot.Title: " + result.Snapshot.meta.Title);
		Debug.Log("Snapshot.Description: " + result.Snapshot.meta.Description);
		Debug.Log("Snapshot.CoverImageUrl): " + result.Snapshot.meta.CoverImageUrl);
		Debug.Log("Snapshot.stringData: " + result.Snapshot.stringData);
		Debug.Log("Snapshot.LastModifiedTimestamp: " + result.Snapshot.meta.LastModifiedTimestamp;
		Debug.Log("Snapshot.bytes.Length: " + result.Snapshot.bytes.Length);
	} 

	SA_StatusBar.text = "Games Loaded: " + result.Message;

}

Game saves are represented via GP_Snapshot class, which uses GP_SnapshotMeta class to describe GP_Snapshot objects.

 

If in Save UI user Selects to Save current game state, you will get ActionNewGameSaveRequest action fired with mean user want you to create new saved game. 

The following snippet shows how your game might commit changes to a saved game:

GooglePlaySavedGamesManager.ActionNewGameSaveRequest += ActionNewGameSaveRequest;

private void ActionNewGameSaveRequest () {
	SA_StatusBar.text = "New  Game Save Requested, Creating new save..";
	Debug.Log("New  Game Save Requested, Creating new save..");
	StartCoroutine(MakeScreenshotAndSaveGameData());
}

private IEnumerator MakeScreenshotAndSaveGameData() {
	yield return new WaitForEndOfFrame();
	// Create a texture the size of the screen, RGB24 format
	int width = Screen.width;
	int height = Screen.height;
	Texture2D Screenshot = new Texture2D( width, height, TextureFormat.RGB24, false );
	// Read screen contents into the texture
	Screenshot.ReadPixels( new Rect(0, 0, width, height), 0, 0 );
	Screenshot.Apply();
		
		
	string currentSaveName =  "snapshotTemp-" + Random.Range(1, 281).ToString();
	string description  = "Modified data at: " + System.DateTime.Now.ToString("MM/dd/yyyy H:mm:ss");


	GooglePlaySavedGamesManager.ActionGameSaveResult += ActionGameSaveResult;
	GooglePlaySavedGamesManager.Instance.CreateNewSpanshot(currentSaveName, description, Screenshot, "some save data, for example you can use JSON or byte array");
		
	Destroy(Screenshot);
}

private void ActionGameSaveResult (GP_SpanshotLoadResult result) {
	GooglePlaySavedGamesManager.ActionGameSaveResult -= ActionGameSaveResult;
	Debug.Log("ActionGameSaveResult: " + result.Message);

	if(result.isSuccess) {
		SA_StatusBar.text = "Games Saved: " + result.Snapshot.meta.Title;
	} else {
		SA_StatusBar.text = "Games Save Failed";
	}

	AndroidMessage.Create("Game Save Result", SA_StatusBar.text);
}	

Handling saved game conflicts

When using the Saved Games service in your game, it is possible for multiple devices to perform reads and writes on the same saved game. In the event that a device temporarily loses its network connection and later reconnects, this might cause data conflicts whereby the saved game stored on a player's local device is out-of-sync with the remote version stored in Google's servers. The Saved Games services provide a conflict resolution mechanism that presents both sets of conflicting saved games at real-time and lets you implement a resolution strategy that is appropriate for your game.

When Play Games services detects a data conflict, it notifies your game during a saved game open operation by firing ActionConflict event. In this event, the GP_SnapshotConflict provides two versions of the saved game:

  • The most-up-to-date version known by Play Games services to be accurate; and
  • A modified version detected on one of the player's devices that contain conflicting content or metadata. This may not be the same as the version that you tried to save.

Your game must decide how to resolve the conflict by picking one of the provided versions or merging the data of the two saved game versions.

The following snippet shows and example of how your game might handle a saved game conflict by selecting the newest saved game as the final version to save: 

GooglePlaySavedGamesManager.ActionConflict += ActionConflict;

private void ActionConflict (GP_SnapshotConflict result) {
	Debug.Log("Conflict Detected: ");
	GP_Snapshot snapshot = result.Snapshot;
	GP_Snapshot conflictSnapshot = result.ConflictingSnapshot;
	// Resolve between conflicts by selecting the newest of the conflicting snapshots.
	GP_Snapshot mResolvedSnapshot = snapshot;
	if (snapshot.meta.LastModifiedTimestamp < conflictSnapshot.meta.LastModifiedTimestamp) {
		mResolvedSnapshot = conflictSnapshot;
	}
	result.Resolve(mResolvedSnapshot);
}

 

Loading available saves

If you want to implement your own UI instead of using UI provided by google, you will find useful following API.

The code spanned bellow shows how you can retrieve all available saved games:

GooglePlaySavedGamesManager.ActionAvailableGameSavesLoaded += ActionAvailableGameSavesLoaded;
GooglePlaySavedGamesManager.Instance.LoadAvailableSavedGames();



private void ActionAvailableGameSavesLoaded (GooglePlayResult res) {

    GooglePlaySavedGamesManager.ActionAvailableGameSavesLoaded -= ActionAvailableGameSavesLoaded;
    if(res.IsSuccess) {
	    foreach(GP_SnapshotMeta meta in GooglePlaySavedGamesManager.Instance.AvailableGameSaves) {
	    	Debug.Log("Meta.Title: " 					+ meta.Title);
	    	Debug.Log("Meta.Description: " 				+ meta.Description);
		    Debug.Log("Meta.CoverImageUrl): " 			+ meta.CoverImageUrl);
		    Debug.Log("Meta.LastModifiedTimestamp: " 	+ meta.LastModifiedTimestamp);
	    }
	} else {
		AndroidMessage.Create("Fail", "Available Game Saves Load failed");
	}
}

 

Once the ActionAvailableGameSavesLoaded action is fired you can get all available saved games list using AvailableGameSaves property.

public List<GP_SnapshotMeta> AvailableGameSaves {get;}

 

The code spanned bellow shows how we can propose to user and  load first saved game from the saved games  list

if(GooglePlaySavedGamesManager.Instance.AvailableGameSaves.Count > 0) {
	GP_SnapshotMeta s =  GooglePlaySavedGamesManager.Instance.AvailableGameSaves[0];
	AndroidDialog dialog = AndroidDialog.Create("Load Snapshot?", "Would you like to load " + s.Title);
	dialog.OnComplete += OnSpanshotLoadDialogComplete;
}

void OnSpanshotLoadDialogComplete (AndroidDialogResult res) {
	if(res == AndroidDialogResult.YES) {
		GP_SnapshotMeta s =  GooglePlaySavedGamesManager.Instance.AvailableGameSaves[0];
		GooglePlaySavedGamesManager.Instance.LoadSpanshotByName(s.Title);
	}
}

 

Creating new snapshot

Instead of using Google Save Gamed UI, we can also create snapshots with scripting. Following snippet shows how to create new Game Save, and attach a screenshot as the cover picture.

public void CreateNewSnapshot() {
	StartCoroutine(MakeScreenshotAndSaveGameData());
}

private IEnumerator MakeScreenshotAndSaveGameData() {
		
	yield return new WaitForEndOfFrame();
	// Create a texture the size of the screen, RGB24 format
	int width = Screen.width;
	int height = Screen.height;
	Texture2D Screenshot = new Texture2D( width, height, TextureFormat.RGB24, false );
	// Read screen contents into the texture
	Screenshot.ReadPixels( new Rect(0, 0, width, height), 0, 0 );
	Screenshot.Apply();
	


	long TotalPlayedTime = 20000; // equivalent to 20 seconds
	string currentSaveName =  "snapshotTemp-" + Random.Range(1, 281).ToString();
	string description  = "Modified data at: " + System.DateTime.Now.ToString("MM/dd/yyyy H:mm:ss");


	GooglePlaySavedGamesManager.ActionGameSaveResult += ActionGameSaveResult;
	GooglePlaySavedGamesManager.Instance.CreateNewSnapshot(currentSaveName, description, Screenshot, "some save data, for example you can use JSON or byte array", TotalPlayedTime);
	
	Destroy(Screenshot);
}

 

Delete the snapshot

In case you want to remove saved games record you may di this as showed bellow:

public void Remove(string SnapshotName) {
	GooglePlaySavedGamesManager.ActionGameSaveRemoved += ActionGameSaveRemoved;
	GooglePlaySavedGamesManager.Instance.DeleteSpanshotByName(SnapshotName);
}


void ActionGameSaveRemoved (GP_DeleteSnapshotResult result) {
	if(result.IsSuccess) {
		AndroidMessage.Create("Success", "Snapshot with id " + result.SnapshotId + " is successfully removed");
	} else {
		AndroidMessage.Create("Failed", "Snapshot remove failed with code: " + result.response);
	}
}

 

Full implementation example can be found at the SavedGamesExample example scene.