Ce guide explique comment implémenter des jeux enregistrés à 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, n'hésitez pas à revoir les concepts de jeux de sauvegarde.
- Veillez à activer la prise en charge des jeux enregistrés pour votre jeu dans la Google Play Console.
- Téléchargez et examinez l'exemple de code de jeu enregistré sur la page des exemples Android.
- Familiarisez-vous avec les recommandations décrites dans la checklist de contrôle qualité.
Obtenir le client Snapshots
Pour commencer à utiliser l'API Snapshots, votre jeu doit d'abord se procurer un objet SnapshotsClient
. Pour ce faire, appelez la méthode Games.getSnapshotsClient()
et transmettez l'activité et le GoogleSignInAccount
pour le lecteur actuel. Pour savoir comment récupérer les informations du compte du joueur, consultez Se connecter dans les jeux Android.
Spécifier le champ d'application de Drive
L'API Snapshots s'appuie sur 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 :
private GoogleSignInClient mGoogleSignInClient; @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 } } }); }
Afficher les 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 :
- Appelez
SnapshotsClient.getSelectSnapshotIntent()
pour obtenir un élémentIntent
permettant de lancer l'UI de sélection de jeux enregistrés par défaut. - Appelez
startActivityForResult()
et transmettez cet élémentIntent
. 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 = Games.getSnapshotsClient(this, GoogleSignIn.getLastSignedInAccount(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'UI envoie une requête aux services de jeux Google Play. Si la requête aboutit, les services de jeux Google Play renvoient les informations permettant de créer ou de 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 // ... } } }
Écrire des jeux enregistrés
Pour stocker du contenu dans un jeu enregistré :
- Ouvrez un instantané de manière asynchrone via
SnapshotsClient.open()
. Ensuite, récupérez l'objetSnapshot
à partir du résultat de la tâche en appelantSnapshotsClient.DataOrConflict.getData()
. - Récupérez une instance
SnapshotContents
viaSnapshotsClient.SnapshotConflict
. - Appelez
SnapshotContents.writeBytes()
pour stocker les données du joueur au format octet. - Une fois toutes vos modifications écrites, appelez
SnapshotsClient.commitAndClose()
pour les envoyer aux serveurs de Google. Dans l'appel de méthode, votre jeu peut fournir des informations supplémentaires afin d'indiquer aux services de jeux Google Play comment présenter le jeu enregistré aux joueurs. Ces informations sont représentées dans un objetSnapshotMetaDataChange
, que votre jeu crée à l'aide deSnapshotMetadataChange.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 = Games.getSnapshotsClient(this, GoogleSignIn.getLastSignedInAccount(this)); // Commit the operation return snapshotsClient.commitAndClose(snapshot, metadataChange); }
Si l'appareil du joueur n'est pas connecté à un réseau au moment où votre application appelle SnapshotsClient.commitAndClose()
, les services de jeux Google Play stockent les données du jeu enregistré localement sur l'appareil. Lors de la reconnexion de l'appareil, les services de jeux Google Play synchronisent les modifications du jeu enregistré qui ont été mises en cache localement avec les serveurs de Google.
Charger des jeux enregistrés
Pour récupérer les jeux enregistrés du joueur actuellement connecté :
- Ouvrez un instantané de manière asynchrone via
SnapshotsClient.open()
. Ensuite, récupérez l'objetSnapshot
à partir du résultat de la tâche en appelantSnapshotsClient.DataOrConflict.getData()
. Votre jeu peut également récupérer un instantané spécifique via l'UI de sélection de jeux enregistrés, comme décrit dans Afficher les jeux enregistrés. - Récupérez l'instance
SnapshotContents
viaSnapshotsClient.SnapshotConflict
. - 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 = Games.getSnapshotsClient(this, GoogleSignIn.getLastSignedInAccount(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 liés aux 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 ce cas, la classe SnapshotsClient.SnapshotConflict
fournit deux versions du jeu enregistré:
- Version du serveur: version la plus récente connue des services Google Play Jeux comme étant exacte pour l'appareil du joueur.
- Local version (Version locale) : version modifiée détectée sur l'un des appareils du joueur et qui comporte du contenu ou des métadonnées en conflit. Elle peut être différente de la version 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 :
- Appelez
SnapshotsClient.open()
. Le résultat de la tâche contient une classeSnapshotsClient.DataOrConflict
. - Appelez la méthode
SnapshotsClient.DataOrConflict.isConflict()
. Si le résultat est "true", cela signifie que vous avez un conflit à résoudre. - Appelez
SnapshotsClient.DataOrConflict.getConflict()
pour récupérer une instance deSnaphotsClient.snapshotConflict
. - Appelez
SnapshotsClient.SnapshotConflict.getConflictId()
pour récupérer l'identifiant unique du conflit détecté. Votre jeu a besoin de cette valeur pour envoyer ultérieurement une requête de résolution de conflit. - Appelez
SnapshotsClient.SnapshotConflict.getConflictingSnapshot()
pour obtenir la version locale. - Appelez
SnapshotsClient.SnapshotConflict.getSnapshot()
pour obtenir la version du serveur. - Pour résoudre le conflit entre jeux enregistrés, sélectionnez la 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 Games.getSnapshotsClient(theActivity, GoogleSignIn.getLastSignedInAccount(this)) .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 des jeux enregistrés pour résoudre les conflits
Si vous souhaitez fusionner les données de plusieurs jeux enregistrés ou modifier un objet Snapshot
existant pour l'enregistrer sur le serveur en tant que version finale résolue, procédez comme suit:
- Appelez
SnapshotsClient.open()
. - Appelez
SnapshotsClient.SnapshotConflict.getResolutionSnapshotsContent()
pour obtenir un nouvel objetSnapshotContents
. - Fusionnez les données de
SnapshotsClient.SnapshotConflict.getConflictingSnapshot()
etSnapshotsClient.SnapshotConflict.getSnapshot()
dans l'objetSnapshotContents
de l'étape précédente. - Vous pouvez également créer une instance de
SnapshotMetadataChange
si des modifications sont apportées aux champs de métadonnées. - Appelez
SnapshotsClient.resolveConflict()
. Dans votre appel de méthode, transmettezSnapshotsClient.SnapshotConflict.getConflictId()
comme premier argument, et les objetsSnapshotMetadataChange
etSnapshotContents
que vous avez modifiés précédemment comme deuxième et troisième arguments. - Si l'appel
SnapshotsClient.resolveConflict()
aboutit, l'API stocke l'objetSnapshot
sur le serveur et tente d'ouvrir l'objet Snapshot sur votre appareil local.- En cas de conflit,
SnapshotsClient.DataOrConflict.isConflict()
renvoietrue
. Dans ce cas, votre jeu doit revenir à l'étape 2 et répéter les étapes de modification de l'instantané jusqu'à ce que les conflits soient résolus. - En l'absence de conflit,
SnapshotsClient.DataOrConflict.isConflict()
renvoiefalse
, et l'objetSnapshot
est ouvert pour permettre à votre jeu de le modifier.
- En cas de conflit,