Adding Saved Games to Your iOS Game

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

Before you begin

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

Before you start to code using the Saved Games API:

Data formats and cross-platform compatibility

Saved Games data that you plan on saving to Google’s servers must be in an NSData format. The Saved Games service takes care of encoding your data in a cross-platform compatible manner. 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. It's strongly encouraged that you use a data format like XML or JSON, which has strong library support on multiple platforms.

Enabling the Saved Games service

Before calling the Saved Games API, you must first enable the service in your code.

If you are using GPGManager to perform sign-in automatically with the signInWithClientID:silently: method, enable the Saved Games service when starting your game (before player sign-in) by setting snapshotsEnabled = YES.

-(void)startGoogleGamesSignIn
{
    // Without this line, you will get an error code: GPGServiceMethodFailedError.
    [GPGManager sharedInstance].snapshotsEnabled = YES;

    // Use this way to sign in so that reauthorization is not required.
    [[GPGManager sharedInstance] signInWithClientID:CLIENT_ID silently:NO];
}

If you are signing players in manually, you also need to add the Drive API to your sign-in scope before performing player sign-in. For example:

signIn.scopes =
    [signIn.scopes arrayByAddingObjectsFromArray:@[@"https://www.googleapis.com/auth/games",
    @"https://www.googleapis.com/auth/drive.appdata"]];

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 or load from one of these slots, or create a new saved game.

To simplify your development, the GPGLauncherController 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 save games.

To bring up the default Saved Games UI and handle the player's UI selection:

  1. Implement the GPGSnapshotListLauncherDelegate interface.

    @interface ExampleInterface () <GPGSnapshotListLauncherDelegate>
    
  2. Implement the delegate methods that will be called when the player picks a saved game.

    /** Called when the user selects the Create New button from the picker. */
    - (void)snapshotListLauncherDidCreateNewSnapshot {
        NSLog(@"New snapshot selected");
    }
    
    /** Called when the user picks a saved game. */
    - (void)snapshotListLauncherDidTapSnapshotMetadata:(GPGSnapshotMetadata *)snapshot {
        NSLog(@"Selected snapshot metadata: %@", snapshot.snapshotDescription);
    
        /** Call example game code to load the given saved game. */
        [self.gameModel loadSnapshot: snapshot];
    }
    

    The default Saved Games UI also provides players with a button (located at the bottom of the UI) to create a new save game, rather than selecting an existing one. When players click on this button, the Saved Games service triggers the delegate method to indicate that a new saved game should be created.

  3. Before your game can receive messages from the default Saved Games UI, you must assign your object as the Saved Games selection UI delegate.

    [GPGLauncherController sharedInstance].snapshotListLauncherDelegate =
            (id<GPGSnapshotListLauncherDelegate>)self.exampleDelegate;
    
  4. To enable the 'New save' button, set the appropriate values in the shouldAllowCreateForSnapshotListLauncher and maxSaveSlotsForSnapshotListLauncher methods.

    - (BOOL)shouldAllowCreateForSnapshotListLauncher {
        // You can leave this as NO if you don't want to handle more than one saved game slot
        return YES;
    }
    
    - (int)maxSaveSlotsForSnapshotListLauncher {
        return 3;
    }
    
  5. Now, you can present the Saved Games UI to players from your game.

    - (IBAction)listSavesWasPressed:(id)sender {
        [[GPGLauncherController sharedInstance] presentSnapshotList];
    }
    

Opening Saved Games

To access a saved game and read or modify its contents, you first need to open the GPGSnapshotMetadata object representing that saved game.

The following code shows how you can open a saved game:

- (void)simpleLoadSnapshot:(GPGSnapshotMetadata *)snapshotMetadata {
    // If a SnapshotMetdata object is passed to your game, this usually
    // reflects a player selection from the Saved Games UI.
    if (snapshotMetadata != nil) {
        self.currentSnapshotMetadata = snapshotMetadata;
    }
    NSString *filename =
            (self.currentSnapshotMetadata) ? self.currentSnapshotMetadata.fileName
            : DEFAULT_SAVE_NAME;
    // Open snapshot for reading.
    [GPGSnapshotMetadata openWithFileName:filename
                           conflictPolicy:GPGSnapshotConflictPolicyRemoteWins
                        completionHandler:^(GPGSnapshotMetadata *snapshot,
                                NSString *conflictId,
                                GPGSnapshotMetadata *conflictingSnapshotBase,
                                GPGSnapshotMetadata *conflictingSnapshotRemote,
                                NSError *error) {
        if (error != nil) {
            // Handle the error here
        } else if (conflictId) {
            NSLog(@"Should not encounter this unless you are resolving conflicts manually");
        } else {
            // Saved game was successfully opened.
            self.currentSnapshotMetadata = snapshot;
            // This method definition is provided in the code snippet below.
            [self readCurrentSnapshot];
        }
    }];
}

Reading Saved Games

To load a saved game, first open the GPGSnapshotMetadata representing that saved game, then call the readWithCompletionHandler: method. You can only read the saved game when GPGSnapshotMetadata.isOpen is YES.

- (void)readCurrentSnapshot {
    [self.currentSnapshotMetadata readWithCompletionHandler:^(NSData *data, NSError *error) {
        if (!error) {
            NSLog(@"Successfully read %d blocks", data.length);
            [self.myGameModel updateYourselfWithSaveData:data];
        } else {
            NSLog(@"Error while loading snapshot data: %@", [error localizedDescription]);
        }
     }];
}

In the completion handler, the saved game data is available when the error is nil.

Detecting and resolving data conflicts

When you open a GPGSnapshotMetadata, the Saved Games service detects if 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 conflictPolicy you specify when opening a saved game tells the Saved Games service how to automatically resolve a data conflict, if one is detected. The policy can be one of the following:

Conflict Policy Description
GPGSnapshotConflictPolicyManual Indicates no resolution action should be performed by the Saved Games service. Instead, your game will perform a custom merge.
GPGSnapshotConflictPolicyLongestPlaytime Indicates the Saved Games service should pick the saved game with the largest play time value.
GPGSnapshotConflictPolicyBaseWins Indicates the Saved Games service should pick the base saved game to resolve the conflict. The base version is the most-up-to-date version known by the Saved Games service to be accurate.
GPGSnapshotConflictPolicyRemoteWins Indicates the Saved Games service should pick the remote saved game to resolve the conflict. The remote version is a version of the saved game detected on one of the player's devices that 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 GPGSnapshotOpenBlock callback. Your game can open the saved game, write to it, then call the commitWithMetadataChange:data:completionHandler: method to commit the saved game to Google’s servers.

Performing a custom merge

If you specified GPGSnapshotConflictPolicyManual 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 the GPGSnapshotOpenBlock callback:

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

Your game must make a decision on what data to save, then call the resolveWithMetadataChange:conflictId:data:completionHandler: method to commit the resolved/final version to Google’s servers.

Writing Saved Games

To write a saved game, first open the GPGSnapshotMetadata representing that saved game, resolve any data conflicts detected, then call the commitWithMetadataChange:data:completionHandler: method to commit your saved game changes .

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

  1. First, create a saved game change that includes the image data used for the cover image:

    - (void)commitCurrentSnapshotWithImage:(UIImage *)snapshotImage
                               description:(NSString *)description
                             savedGameData:(NSData *)gameData {
        if (!self.currentSnapshotMetadata.isOpen) {
            NSLog(@"Error trying to commit a snapshot. You must always open it first");
            return;
        }
    
        // Create a snapshot change to be committed with a description,
        // cover image, and play time.
        GPGSnapshotMetadataChange *dataChange = [[GPGSnapshotMetadataChange alloc] init];
        dataChange.snapshotDescription = description;
    
        // Note: This is done for simplicity. You should record time since
        // your game last opened a saved game.
        int millsSinceaPreviousSnapshotWasOpened = 10000;
        dataChange.playedTime =
                self.currentSnapshotMetadata.playedTime + millsSinceaPreviousSnapshotWasOpened;
        dataChange.coverImage = [[GPGSnapshotMetadataChangeCoverImage alloc]
                initWithImage:snapshotImage];
    
  2. Next, commit the saved game changes.

    [self.currentSnapshotMetadata commitWithMetadataChange:dataChange
                                                      data:gameData
                                         completionHandler:^(GPGSnapshotMetadata
                                         *snapshotMetadata, NSError *error) {
        if (!error) {
            NSLog(@"Successfully saved %@", snapshotMetadata);
        } else {
            NSLog(@"** Error while saving: %@", error);
        }
        self.currentSnapshotMetadata = nil;
    }];
    

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

If the commit operation completed successfully, players will be able to see the saved game appear in the Saved Games selection UI.

Send feedback about...

Play Games Services for iOS
Play Games Services for iOS