Compatibilité avec les jeux Android enregistrés dans les jeux

Ce guide vous explique comment implémenter un jeu enregistré à l'aide de l'API Snapshots fournie par les services de jeux Google Play. Les API sont disponibles dans les packages com.google.android.gms.games.snapshot et com.google.android.gms.games.

Avant de commencer

Si ce n'est pas déjà fait, vous pouvez consulter les concepts de jeux enregistrés.

Obtenir le client d'instantanés

Pour commencer à utiliser l'API Snapshots, votre jeu doit d'abord se procurer un objet SnapshotsClient. Pour ce faire, il suffit d'appeler la méthode Games.getSnapshotsClient() et de transmettre l'activité.

Spécifier le champ d'application Drive

L'API des instantanés fait appel à l'API Google Drive pour le stockage des jeux enregistrés. Pour accéder à l'API Drive, votre application doit spécifier le champ d'application Drive.SCOPE_APPFOLDER lors de la création du client Google Sign-In.

L'exemple suivant contient la procédure à suivre dans la méthode onResume() pour votre activité de connexion :


@Override
protected void onResume() {
  super.onResume();
  signInSilently();
}

private void signInSilently() {
  GoogleSignInOptions signInOption =
      new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN)
          // Add the APPFOLDER scope for Snapshot support.
          .requestScopes(Drive.SCOPE_APPFOLDER)
          .build();

  GoogleSignInClient signInClient = GoogleSignIn.getClient(this, signInOption);
  signInClient.silentSignIn().addOnCompleteListener(this,
      new OnCompleteListener<GoogleSignInAccount>() {
        @Override
        public void onComplete(@NonNull Task<GoogleSignInAccount> task) {
          if (task.isSuccessful()) {
            onConnected(task.getResult());
          } else {
            // Player will need to sign-in explicitly using via UI
          }
        }
      });
}

Affichage des jeux enregistrés

Vous pouvez intégrer l'API Snapshots partout où votre jeu offre aux joueurs la possibilité d'enregistrer ou de restaurer leur progression. Votre jeu peut afficher une option de ce type à certains points d'enregistrement/de restauration, ou permettre aux joueurs d'enregistrer ou de restaurer leur progression à tout moment.

Une fois l'option d'enregistrement ou de restauration sélectionnée, le jeu peut également afficher un écran qui invite le joueur à saisir les informations d'un nouveau jeu enregistré ou à sélectionner un jeu existant à restaurer.

Pour simplifier le développement, l'API Snapshots fournit une interface utilisateur (UI) de sélection de jeux enregistrés par défaut prête à l'emploi L'UI de sélection de jeux enregistrés permet aux joueurs de créer un jeu enregistré, de consulter les détails des jeux enregistrés existants et de charger de précédents jeux enregistrés.

Pour lancer l'UI Jeux enregistrés par défaut :

  1. Appelez SnapshotsClient.getSelectSnapshotIntent() pour obtenir un Intent permettant de lancer l'interface utilisateur par défaut de sélection des jeux enregistrés.
  2. Appelez startActivityForResult() et transmettez-le Intent. Si l'appel aboutit, le jeu affiche l'UI de sélection de jeux enregistrés, ainsi que les options que vous avez spécifiées.

Voici un exemple de lancement de l'UI de sélection de jeux enregistrés par défaut :

private static final int RC_SAVED_GAMES = 9009;

private void showSavedGamesUI() {
  SnapshotsClient snapshotsClient =
      PlayGames.getSnapshotsClient(this);
  int maxNumberOfSavedGamesToShow = 5;

  Task<Intent> intentTask = snapshotsClient.getSelectSnapshotIntent(
      "See My Saves", true, true, maxNumberOfSavedGamesToShow);

  intentTask.addOnSuccessListener(new OnSuccessListener<Intent>() {
    @Override
    public void onSuccess(Intent intent) {
      startActivityForResult(intent, RC_SAVED_GAMES);
    }
  });
}

Si le joueur choisit de créer un jeu enregistré ou de charger un jeu enregistré existant, l'interface utilisateur envoie une requête aux services de jeux Google Play. Si la requête aboutit, les services de jeux Google Play renvoient des informations pour créer ou restaurer le jeu enregistré via le rappel onActivityResult(). Votre jeu peut ignorer ce rappel pour vérifier si des erreurs se sont produites lors de la requête.

L'extrait de code suivant fournit un exemple d'implémentation de onActivityResult() :

private String mCurrentSaveName = "snapshotTemp";

/**
 * This callback will be triggered after you call startActivityForResult from the
 * showSavedGamesUI method.
 */
@Override
protected void onActivityResult(int requestCode, int resultCode,
                                Intent intent) {
  if (intent != null) {
    if (intent.hasExtra(SnapshotsClient.EXTRA_SNAPSHOT_METADATA)) {
      // Load a snapshot.
      SnapshotMetadata snapshotMetadata =
          intent.getParcelableExtra(SnapshotsClient.EXTRA_SNAPSHOT_METADATA);
      mCurrentSaveName = snapshotMetadata.getUniqueName();

      // Load the game data from the Snapshot
      // ...
    } else if (intent.hasExtra(SnapshotsClient.EXTRA_SNAPSHOT_NEW)) {
      // Create a new snapshot named with a unique string
      String unique = new BigInteger(281, new Random()).toString(13);
      mCurrentSaveName = "snapshotTemp-" + unique;

      // Create the new snapshot
      // ...
    }
  }
}

Écriture de jeux enregistrés

Pour stocker du contenu dans un jeu enregistré :

  1. Ouvrez un instantané de manière asynchrone via SnapshotsClient.open(). Ensuite, récupérez l'objet Snapshot à partir du résultat de la tâche en appelant SnapshotsClient.DataOrConflict.getData().
  2. Récupérez une instance SnapshotContents via SnapshotsClient.SnapshotConflict.
  3. Appelez SnapshotContents.writeBytes() pour stocker les données du lecteur au format octet.
  4. Une fois toutes vos modifications effectuées, appelez SnapshotsClient.commitAndClose() pour les envoyer aux serveurs de Google. Dans l'appel de méthode, votre jeu peut éventuellement fournir des informations supplémentaires pour indiquer aux services de jeux Google Play comment présenter ce jeu enregistré aux joueurs. Ces informations sont représentées dans un objet SnapshotMetaDataChange, que votre jeu crée à l'aide de SnapshotMetadataChange.Builder.

L'extrait de code suivant montre comment votre jeu peut apporter des modifications à un jeu enregistré :

private Task<SnapshotMetadata> writeSnapshot(Snapshot snapshot,
                                             byte[] data, Bitmap coverImage, String desc) {

  // Set the data payload for the snapshot
  snapshot.getSnapshotContents().writeBytes(data);

  // Create the change operation
  SnapshotMetadataChange metadataChange = new SnapshotMetadataChange.Builder()
      .setCoverImage(coverImage)
      .setDescription(desc)
      .build();

  SnapshotsClient snapshotsClient =
      PlayGames.getSnapshotsClient(this);

  // Commit the operation
  return snapshotsClient.commitAndClose(snapshot, metadataChange);
}

Si l'appareil du joueur n'est pas connecté à un réseau lorsque votre application appelle SnapshotsClient.commitAndClose(), les services de jeux Google Play stockent les données de jeu enregistrées localement sur l'appareil. Lors de la reconnexion de l'appareil, les services de jeux Google Play synchronisent les modifications enregistrées en local sur le serveur avec les serveurs Google.

Chargement des jeux enregistrés...

Pour récupérer les jeux enregistrés du joueur actuellement connecté :

  1. Ouvrez un instantané de manière asynchrone via SnapshotsClient.open(). Ensuite, récupérez l'objet Snapshot à partir du résultat de la tâche en appelant SnapshotsClient.DataOrConflict.getData(). Vous pouvez également récupérer un instantané spécifique via l'interface utilisateur de sélection de jeux enregistrés, comme décrit dans la section Afficher les jeux enregistrés.
  2. Récupérez l'instance SnapshotContents via SnapshotsClient.SnapshotConflict.
  3. Appelez SnapshotContents.readFully() pour lire le contenu de l'instantané.

L'extrait de code suivant montre comment charger un jeu enregistré spécifique :

Task<byte[]> loadSnapshot() {
  // Display a progress dialog
  // ...

  // Get the SnapshotsClient from the signed in account.
  SnapshotsClient snapshotsClient =
      PlayGames.getSnapshotsClient(this);

  // In the case of a conflict, the most recently modified version of this snapshot will be used.
  int conflictResolutionPolicy = SnapshotsClient.RESOLUTION_POLICY_MOST_RECENTLY_MODIFIED;

  // Open the saved game using its name.
  return snapshotsClient.open(mCurrentSaveName, true, conflictResolutionPolicy)
      .addOnFailureListener(new OnFailureListener() {
        @Override
        public void onFailure(@NonNull Exception e) {
          Log.e(TAG, "Error while opening Snapshot.", e);
        }
      }).continueWith(new Continuation<SnapshotsClient.DataOrConflict<Snapshot>, byte[]>() {
        @Override
        public byte[] then(@NonNull Task<SnapshotsClient.DataOrConflict<Snapshot>> task) throws Exception {
          Snapshot snapshot = task.getResult().getData();

          // Opening the snapshot was a success and any conflicts have been resolved.
          try {
            // Extract the raw data from the snapshot.
            return snapshot.getSnapshotContents().readFully();
          } catch (IOException e) {
            Log.e(TAG, "Error while reading Snapshot.", e);
          }

          return null;
        }
      }).addOnCompleteListener(new OnCompleteListener<byte[]>() {
        @Override
        public void onComplete(@NonNull Task<byte[]> task) {
          // Dismiss progress dialog and reflect the changes in the UI when complete.
          // ...
        }
      });
}

Gérer les conflits de jeux enregistrés

Lorsque vous utilisez l'API Snapshots dans votre jeu, il peut arriver que plusieurs appareils exécutent des opérations de lecture et d'écriture dans le même jeu enregistré. En cas de perte de connexion temporaire, puis de reconnexion d'un appareil, des conflits de données peuvent se produire, car le jeu enregistré stocké sur l'appareil local du joueur n'est plus synchronisé avec la version distante stockée sur les serveurs de Google.

L'API Snapshots fournit un mécanisme de résolution des conflits qui présente les deux jeux enregistrés en conflit au moment de la lecture et vous permet d'implémenter une stratégie de résolution adaptée à votre jeu.

Lorsque les services de jeux Google Play détectent un conflit de données, la méthode SnapshotsClient.DataOrConflict.isConflict() renvoie la valeur true. Dans cet événement, la classe SnapshotsClient.SnapshotConflict fournit deux versions du jeu enregistré:

  • Version du serveur: la version la plus récente connue des services de jeux Google Play pour être exacte sur l'appareil du joueur.
  • Version locale: version modifiée détectée sur l'un des appareils du lecteur et dont le contenu ou les métadonnées sont en conflit. Il peut s'agir d'une version différente de celle que vous avez essayé d'enregistrer.

Votre jeu doit déterminer comment résoudre le conflit en choisissant l'une des versions fournies ou en fusionnant les données des deux versions enregistrées.

Pour détecter et résoudre les conflits liés aux jeux enregistrés :

  1. Appelez SnapshotsClient.open(). Le résultat de la tâche contient une classe SnapshotsClient.DataOrConflict.
  2. Appelez la méthode SnapshotsClient.DataOrConflict.isConflict(). Si le résultat est "true", vous devez résoudre un conflit.
  3. Appelez SnapshotsClient.DataOrConflict.getConflict() pour récupérer une instance SnaphotsClient.snapshotConflict.
  4. Appelez SnapshotsClient.SnapshotConflict.getConflictId() pour récupérer l'ID de conflit qui identifie de manière unique le conflit détecté. Votre jeu a besoin de cette valeur pour vous envoyer une requête de résolution de conflit ultérieurement.
  5. Appelez SnapshotsClient.SnapshotConflict.getConflictingSnapshot() pour obtenir la version locale.
  6. Appelez SnapshotsClient.SnapshotConflict.getSnapshot() pour obtenir la version du serveur.
  7. Pour résoudre le conflit de jeu enregistré, sélectionnez une version que vous souhaitez enregistrer sur le serveur en tant que version finale, puis transmettez-la à la méthode SnapshotsClient.resolveConflict().

L'extrait de code suivant montre comment votre jeu peut gérer un conflit entre jeux enregistrés en sélectionnant celui dont les modifications sont les plus récentes comme version finale à enregistrer :


private static final int MAX_SNAPSHOT_RESOLVE_RETRIES = 10;

Task<Snapshot> processSnapshotOpenResult(SnapshotsClient.DataOrConflict<Snapshot> result,
                                         final int retryCount) {

  if (!result.isConflict()) {
    // There was no conflict, so return the result of the source.
    TaskCompletionSource<Snapshot> source = new TaskCompletionSource<>();
    source.setResult(result.getData());
    return source.getTask();
  }

  // There was a conflict.  Try resolving it by selecting the newest of the conflicting snapshots.
  // This is the same as using RESOLUTION_POLICY_MOST_RECENTLY_MODIFIED as a conflict resolution
  // policy, but we are implementing it as an example of a manual resolution.
  // One option is to present a UI to the user to choose which snapshot to resolve.
  SnapshotsClient.SnapshotConflict conflict = result.getConflict();

  Snapshot snapshot = conflict.getSnapshot();
  Snapshot conflictSnapshot = conflict.getConflictingSnapshot();

  // Resolve between conflicts by selecting the newest of the conflicting snapshots.
  Snapshot resolvedSnapshot = snapshot;

  if (snapshot.getMetadata().getLastModifiedTimestamp() <
      conflictSnapshot.getMetadata().getLastModifiedTimestamp()) {
    resolvedSnapshot = conflictSnapshot;
  }

  return PlayGames.getSnapshotsClient(theActivity)
      .resolveConflict(conflict.getConflictId(), resolvedSnapshot)
      .continueWithTask(
          new Continuation<
              SnapshotsClient.DataOrConflict<Snapshot>,
              Task<Snapshot>>() {
            @Override
            public Task<Snapshot> then(
                @NonNull Task<SnapshotsClient.DataOrConflict<Snapshot>> task)
                throws Exception {
              // Resolving the conflict may cause another conflict,
              // so recurse and try another resolution.
              if (retryCount < MAX_SNAPSHOT_RESOLVE_RETRIES) {
                return processSnapshotOpenResult(task.getResult(), retryCount + 1);
              } else {
                throw new Exception("Could not resolve snapshot conflicts");
              }
            }
          });
}

Modifier les jeux enregistrés pour résoudre les conflits

Si vous souhaitez fusionner les données de plusieurs jeux enregistrés ou modifier un Snapshot existant pour l'enregistrer sur le serveur en tant que version finale résolue, procédez comme suit:

  1. Appelez SnapshotsClient.open() .
  2. Appelez SnapshotsClient.SnapshotConflict.getResolutionSnapshotsContent() pour obtenir un nouvel objet SnapshotContents.
  3. Fusionnez les données de SnapshotsClient.SnapshotConflict.getConflictingSnapshot() et SnapshotsClient.SnapshotConflict.getSnapshot() dans l'objet SnapshotContents de l'étape précédente.
  4. Vous pouvez également créer une instance SnapshotMetadataChange si vous apportez des modifications aux champs de métadonnées.
  5. Appelez SnapshotsClient.resolveConflict(). Dans votre appel de méthode, transmettez SnapshotsClient.SnapshotConflict.getConflictId() comme premier argument, et les objets SnapshotMetadataChange et SnapshotContents que vous avez modifiés respectivement en tant que deuxième et troisième arguments.
  6. Si l'appel SnapshotsClient.resolveConflict() aboutit, l'API stocke l'objet Snapshot sur le serveur et tente d'ouvrir l'objet Instantané sur votre appareil local.