Saving A Game

Saving and sharing game data between iOS devices can be difficult. Current iCloud strategies do not provide developers with a good solution to saving game data between devices. Whether requiring developers to implement several different APIs or limiting the amount of information they can save, current iCloud strategies do not provide developers with a viable solution. 

To provide saved game data between devices, developers need to create their own solutions, either using their own servers or using a secondary service, such as Facebook. Using the new saved game methods developers can easily save game data to iCloud.

General information about saved games

Currently, the saved game API is best used for single-player games or a pass-and-play game on a single device. Only the local player is required to save the game and any device can be used to retrieve the saved game information. Turn-based games already have a saved state as the turn is passed from one player to another and real-time games require all players to be present any time the game is played and only a single player can save the game.

When implementing saved games in your app, you must keep the following information in mind:

  • The player must have an iCloud account to save games
  • No specific limit on the amount of data that can be saved
  • Game won’t be saved if there is no room in the player’s iCloud account
  • The app controls how and what data is saved
  • Saved games are tied to the iCloud account, not the Game Center account

iCloud Requirements

Saving games using the saved game APIs requires the user to have an iCloud account. Using iCloud provides users with a way to save a game from any device that has an Internet connection. Along with providing the ability to save games from anywhere, iCloud provides users with the ability to save large data files. The size of a saved game file is limited to the amount of space in the user's iCloud account. However, you should always strive to minimize the amount of data being saved. This prevents the user from running out of space and decreases the amount of time required to fetch or save a game file.

Saving a Game

Today’s games are played on multiple devices depending on where the user happens to be. Whether they are at home playing on their iMac, sitting in front of a television with their iPad, or on a bus with their iPhone, a player wants to continue playing a game from the last place they left off and not have to worry about which device they were playing on. Including the ability to save and retrieve saved games greatly increases the playability of your game.

Please note, that you need to Initialise Game Center, and a player should be successfully authorized with Game Center, before you can use SavedGames API.

Saving the game data

It is up to you to decide how and when users can save a game. If you want your player’s to consider each action before performing that action, then you might want to only allow a single saved game file. However, if you don’t mind your players going back and trying different actions, then allow the player to name and create several different saved game files. In either case, it is up to you to create a save game mechanism for your app.

After the player creates a new saved game file, use the SaveGame(...) method of the ISN_GameSaves class, to save the game data to iCloud.

void Save () {
	ISN_GameSaves.ActionGameSaved += HandleActionGameSaved;
	byte[] data = System.Text.Encoding.UTF8.GetBytes("Some data");
	ISN_GameSaves.Instance.SaveGame(data, "savedgame1");
}

void HandleActionGameSaved (GK_SaveResult res) {
	if(res.IsSucceeded) {
		Debug.Log("Saved");
	} else {
		Debug.Log("Failed: " + res.Error.Description);
	}
}

If there is already a saved game object with the same name, the new saved game data overwrites the old saved game data. Otherwise a new GK_SavedGame object is created and saved.

Retrieving a saved game

Players want to continue to play games they have saved, whether they saved the game on their current device or another device. To retrieve the list of games the player has saved from a device, your app calls the FetchSavedGames (...) method of the ISN_GameSaves class, to retrieve the list of games for the player. This method returns a list of GK_SavedGame objects that contains the identifying information for each saved game. Before presenting the list of saved games to the player, you must ensure that none of the saved games have the same name. If more than one saved game has the same name, you must resolve the name conflict before you present the list of saved games to the player.

void Fetch() {
	ISN_GameSaves.ActionSavesFetched += HandleActionSavesFetched;
	ISN_GameSaves.Instance.FetchSavedGames();
}

void HandleActionSavesFetched (GK_FetchResult res) {
	if(res.IsSucceeded) {
		Debug.Log(res.SavedGames.Count + " saves loaded");
	} else {
		Debug.Log("Failed: " + res.Error.Description);
	}
}

After you have resolved any conflicts, present the list of saved games to the player, if applicable. The player then selects the saved game file they wish to continue playing. Call the LoadData() method to retrieve the data associated with the GK_SavedGame object chosen by the player.

void Load() {
	GK_SavedGame  save = GetLoadedSave();

	save.ActionDataLoaded += HandleActionDataLoaded;
	save.LoadData();
}

void HandleActionDataLoaded (GK_SaveDataLoaded res) {
	if(res.IsSucceeded) {
		Debug.Log("Data loaded. data Length: " + res.SavedGame.Data.Length);
	} else {
		Debug.Log("Failed: " + res.Error.Description);
	}
}

Conflicting saved games

With users having multiple devices, it is not unusual for a player to be playing the same game on different devices at the same time. Because of this, it is possible to have multiple saved games with the same name and from different devices. It is up to your app to find any conflicting games and fix the conflict.

After determining that there is a conflict between saved games, you need to create an array containing only the GK_SavedGame objects for the conflicting games. You then send the array to the ResolveConflictingSavedGames(...) method of the ISN_GameSaves class. This method resolves any saved game conflicts and modifies the array so that it does not contain any saved game conflicts.

void Conflitcs() {
	List<GK_SavedGame> conflicts;// = GetConflicts();

	ISN_GameSaves.ActionSavesResolved += HandleActionSavesResolved;
	byte[] data = System.Text.Encoding.UTF8.GetBytes("Some data");
	ISN_GameSaves.Instance.ResolveConflictingSavedGames(conflicts)
}

void HandleActionSavesResolved (GK_SavesResolveResult res) {
	if(res.IsSucceeded) {
		Debug.Log("Resolved loaded");
	} else {
		Debug.Log("Failed: " + res.Error.Description);
	}
}

See the detailed information for the GK_SavesResolveResult.

API Reference

ISN_GameSaves

public class ISN_GameSaves : ISN_Singleton<ISN_GameSaves> {

	//--------------------------------------
	//  Actions
	//--------------------------------------

	static event Action<GK_SaveRemoveResult> ActionSaveRemoved = delegate {};
	static event Action<GK_SaveResult> ActionGameSaved = delegate {};
	static event Action<GK_FetchResult> ActionSavesFetched = delegate {};
	static event Action<GK_SavesResolveResult> ActionSavesResolved = delegate {};

	//--------------------------------------
	//  Methods
	//--------------------------------------

	/// <summary>
	/// Saves game data under the specified name.
	/// 
	/// This method saves game data asynchronously. 
	/// When a game is saved, if there is already a saved game with 
	/// the same name, the new saved game data overwrites 
	/// the old saved game data. If there is no saved game with 
	/// the same name, a new saved game is created. 
	/// 
	///<param name="data">An object that contains the saved game data.</param>
	///<param name="name">A string that identifies the saved game data.</param>
	/// </summary>
	public void SaveGame(byte[] data, string name);
	

	/// <summary>
	/// Retrieves all available saved games.
	/// 
	/// This method deletes saved game files asynchronously. 
	/// same name, a conflict occurs. The app must determine 
	/// which saved game file is correct and call the
	/// ResolveConflictingSavedGames method. 
	/// 
	/// </summary>
	public void FetchSavedGames();

	/// <summary>
	/// Deletes a specific saved game file.
	/// This method deletes saved game files asynchronously.
	/// 
	/// <param name="name">A string that identifies the saved game data to be deleted.</param>
	/// </summary>
	public void DeleteSavedGame(string name);

	/// <summary>
	/// Resolves any conflicting saved games..
	/// 
	/// This method takes an array of GK_SavedGame objects that 
	/// contain conflicting saved game files and creates a 
	/// new array that contains the resolved conflicts. 
	/// All saved game conflicts are resolved and added to the
	/// conflicts array in the completion handler. 
	/// Call this method separately for each set of saved game conflicts. 
	/// For example, if you have multiple saved game files 
	/// with the name of “savedgame1” and “savedgame2”, you 
	/// need to call this method twice—once with an array containing 
	/// the GK_SavedGame objects with the “savedgame1
	/// ame and once for the “savedgame2” objects. 
	/// All saved game conflicts are resolved asynchronously.
	/// 
	/// <param name="conflicts">An list of GK_SavedGame objects containing the conflicting saved games to be deleted.</param>
	/// <param name="data">An object that contains the saved game data.</param>
	/// </summary>
	public void ResolveConflictingSavedGames(List<GK_SavedGame> conflicts, byte[] data);
}

GK_SavedGame

public class GK_SavedGame  {

	//--------------------------------------
	// Actions
	//--------------------------------------

	public event Action<GK_SaveDataLoaded> ActionDataLoaded = delegate {};


	//--------------------------------------
	// Public Methods
	//--------------------------------------

	/// <summary>
	/// Loads previously retrieved saved game data.
	/// 
	/// his method loads the saved game data asynchronously. 
	/// The ActionDataLoaded action contains 
	/// either the saved game data, or an error if no game was loaded.
	/// </summary>
	public void LoadData() {
		ISN_GameSaves.Instance.LoadSaveData(this);
	}


	//--------------------------------------
	// Get / Set
	//--------------------------------------


	/// <summary>
	/// Object unique identifier. (read-only)
	/// </summary>
	public string Id {get;}

	/// <summary>
	/// The name of the saved game. (read-only)
	/// 
	/// You can allow users to name their own saved games, 
	/// or you can create a saved game name automatically.
	/// </summary>
	public string Name {get;}

	/// <summary>
	/// The name of the device that created the saved game data. (read-only)
	/// 
	/// The device name is equal to whatever the user has named 
	/// his or her device.  For example, “Bob’s iPhone”, “John’s Macbook Pro”.
	/// </summary>
	public string DeviceName {get;}

	/// <summary>
	/// The date when the saved game file was modified. (read-only)
	/// </summary>
	public DateTime ModificationDate {get;}

	/// <summary>
	/// Data of the saved game. Can be used only after was 
	/// loaded with the LoadData method 
	/// and ActionDataLoaded action was received.
	/// </summary>
	public byte[] Data {get;}
}

GK_FetchResult

public class GK_FetchResult : ISN_Result {
	public List<GK_SavedGame> SavedGames {get;}
}

GK_SaveDataLoaded

public class GK_SaveDataLoaded : ISN_Result {
	public GK_SavedGame SavedGame {get;}
}

GK_SaveRemoveResult

public class GK_SaveRemoveResult : ISN_Result {
	public string SaveName  {get;}
}

GK_SaveResult

public class GK_SaveResult : ISN_Result {
	public GK_SavedGame SavedGame {get;}
}

GK_SavesResolveResult

public class GK_SavesResolveResult : ISN_Result {
	/// <summary>
	/// An array of GK_SavedGame objects containing a saved 
	/// game list with all conflicts contained in the conflicting 
	/// parameter resolved. If there are any conflicting saved 
	/// game files that were not in the conflicts parameter, 
	/// these objects will automatically be appended to the end 
	/// of the SavedGames list. For example, if there are five 
	/// saved game files with the same name, but only three are 
	/// in theconflicts array, this array will contain the resolved
	/// saved game file and the two unresolved files.
	/// </summary>
	public List<GK_SavedGame> SavedGames {get;}
}