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.

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 users 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.

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 newly saved game file, use the SavedGame method of ISN_GKLocalPlayer class to save the game data to iCloud. 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 ISN_GKSavedGame object is created and saved.

using SA.IOSNative.GameKit;
...
byte[] data = Encoding.UTF8.GetBytes("data A");
ISN_GKLocalPlayer.SavedGame("file_name", data, (ISN_GKSavedGameSaveResult result) => {
    if (result.IsSucceeded) {
        Debug.Log(result.SavedGame.Name);
        Debug.Log(result.SavedGame.DeviceName);
        Debug.Log(result.SavedGame.ModificationDate);
    } else {
        Debug.Log("SavedGame is failed! With: " + result.Error.Code + " and description: " + result.Error.Message);
    }
});

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 to retrieve the list of games for the player. This method returns a list of ISN_GKSavedGame 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.

using SA.IOSNative.GameKit;
...
ISN_GKLocalPlayer.FetchSavedGames((ISN_GKSavedGameFetchResult result) => {
    if (result.IsSucceeded) {
        Debug.Log("Loaded " + result.SavedGames.Count + " saved games");
        foreach (ISN_GKSavedGame game in result.SavedGames) {
            Debug.Log(game.Name);
            Debug.Log(game.DeviceName);
            Debug.Log(game.ModificationDate);
        }
    }
    else {
        Debug.Log("Fetching saved games is failed! " +
        "With: " + result.Error.Code + " and description: " + result.Error.Message);
    }
});

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 LoadGameData method to retrieve the data associated with the ISN_GKSavedGame object chosen by the player.

using SA.IOSNative.GameKit;
...
ISN_GKSavedGame game = GetUserPickedSave();
ISN_GKLocalPlayer.LoadGameData(game, (ISN_GKSavedGameLoadResult result) => {
    if (result.IsSucceeded) {
        Debug.Log("Loading game data is succeeded! " +
                  "StringData = " + result.StringData + " " +
                  "byte array length " + result.BytesArrayData.Length);
    } else {
        Debug.Log("Loading game data is failed! Error with code: " + result.Error.Code + " and description: " + result.Error.Message);
    }
});

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 ISN_GKSavedGame  objects for the conflicting games. You then send the array to the ResolveConflictingSavedGames method. This method resolves any saved game conflicts and modifies the array so that it does not contain any saved game conflicts.

ISN_GKLocalPlayer.ResolveConflictingSavedGames(m_conflictedSavedGames, data, (ISN_GKSavedGameFetchResult result) => {
    if (result.IsSucceeded) {
        Debug.Log("Resolve Conflicted Saved Games is succeeded!");
    } else {
        Debug.Log("Resolve Conflicted Saved Games is failed!");
    }
});

Saved Games Events

It's recommended that you handle and subscribe to the saved game's events that can occur during your app lifetime. See the code snippet below:

using SA.IOSNative.GameKit;
...
ISN_GKLocalPlayerListener.DidModifySavedGame.AddListener(DidModifySavedGame);
ISN_GKLocalPlayerListener.HasConflictingSavedGames.AddListener(HasConflictingSavedGames);


/// <summary>
/// Indicates that saved game data was modified.
/// This method is usually called when a game is saved on device other than the device currently in use.
/// </summary>
private void DidModifySavedGame(ISN_GKSavedGameSaveResult result) {
    Debug.Log("DidModifySavedGame! Device name = " + result.SavedGame.DeviceName + " | game name = " + result.SavedGame.Name + " | modification Date = " + result.SavedGame.ModificationDate.ToString());
}

/// <summary>
/// Invoked when a conflict arises between different versions of the same saved game.
/// Saved game files conflict when multiple devices write to the same saved game file while one or more of the devices are offline.
/// The app must determine which saved game data is the correct data to use and then call the ResolveConflicts <see cref="ISN_GKLocalPlayer"/>
/// </summary>
private void HasConflictingSavedGames(ISN_GKSavedGameFetchResult result) {
    foreach(ISN_GKSavedGame game in result.SavedGames) {
        m_conflictedSavedGames.Add(game.Id);
    }

    foreach (ISN_GKSavedGame game in result.SavedGames) {
        Debug.Log("HasConflictingSavedGames! Device name = " 
                  + game.DeviceName + " | game name = " 
                  + game.Name + " | modification Date = " 
                  + game.ModificationDate.ToString());
    }
}

Delete Saved Game

When you need to delete a saved game, simply call the DeleteSavedGame method as shown on example below:

using SA.IOSNative.GameKit;
...
var game = GetGameSaveToDelete();
ISN_GKLocalPlayer.DeleteSavedGame(game, (SA_Result result) => {
    if (result.IsSucceeded) {
        Debug.Log("DeleteSavedGame is succeeded!");
    }
    else {
        Debug.Log("DeleteSavedGame is failed! Error with code: " + result.Error.Code + " and description: " + result.Error.Message);
    }
});