In diesem Leitfaden wird erläutert, wie Sie gespeicherte Spiele mit der Snapshots API implementieren, die von den Google Play-Spieldiensten bereitgestellt wird. Die APIs finden Sie in den Paketen com.google.android.gms.games.snapshot
und com.google.android.gms.games
.
Hinweis
Falls Sie es noch nicht getan haben, kann es hilfreich sein, sich die Spielekonzepte für gespeicherte Spiele durchzulesen.
- Aktiviere die Unterstützung für gespeicherte Spiele für dein Spiel in der Google Play Console.
- Du kannst das gespeicherte Codebeispiel für Spiele auf der Android-Beispielseite herunterladen und prüfen.
- Mache dich mit den in der Qualitäts-Checkliste beschriebenen Empfehlungen vertraut.
Snapshot-Client abrufen
Damit Sie die Snapshots API verwenden können, muss Ihr Spiel zuerst ein SnapshotsClient
-Objekt abrufen. Rufen Sie dazu die Methode Games.getSnapshotsClient()
auf und übergeben Sie die Aktivität.
Drive-Bereich angeben
Die Snapshots API nutzt die Google Drive API zum Speichern gespeicherter Spiele. Ihre Anwendung muss für den Zugriff auf die Drive API beim Erstellen des Google-Anmeldeclients den Bereich Drive.SCOPE_APPFOLDER
angeben.
Hier ein Beispiel dafür, wie dies in der Methode onResume()
für deine Anmeldeaktivität funktioniert:
@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 } } }); }
Gespeicherte Spiele werden angezeigt
Sie können die Snapshots API überall dort einbinden, wo ein Spiel für Spieler verfügbar ist und sie ihren Fortschritt speichern oder wiederherstellen können. Ihr Spiel könnte eine solche Option an bestimmten Speicherpunkten zum Wiederherstellen oder Wiederherstellen anzeigen oder den Spielern ermöglichen, den Fortschritt jederzeit zu speichern oder wiederherzustellen.
Nachdem Spieler die Option „Speichern/Wiederherstellen“ in Ihrem Spiel ausgewählt haben, kann Ihr Spiel optional einen Bildschirm aufrufen, auf dem Spieler aufgefordert werden, Informationen für ein neues gespeichertes Spiel einzugeben oder ein vorhandenes gespeichertes Spiel wiederherzustellen.
Um die Entwicklung zu vereinfachen, bietet die Snapshot API eine standardmäßige gespeicherte Benutzeroberfläche für Spiele, die Sie sofort verwenden können. Über die UI zur Auswahl von gespeicherten Spielen können Spieler ein neues gespeichertes Spiel erstellen, Details zu vorhandenen gespeicherten Spielen ansehen und zuvor gespeicherte Spiele laden.
So starten Sie die standardmäßige Benutzeroberfläche für gespeicherte Spiele:
- Rufen Sie
SnapshotsClient.getSelectSnapshotIntent()
auf, um einenIntent
zum Starten der Standard-UI für die Auswahl gespeicherter Spiele zu erhalten. - Rufen Sie
startActivityForResult()
auf und übergeben SieIntent
. Wenn der Aufruf erfolgreich ist, zeigt das Spiel die gespeicherte UI zur Spieleauswahl sowie die von Ihnen angegebenen Optionen an.
Hier ein Beispiel für die Einführung der standardmäßigen UI für die Auswahl gespeicherter Spiele:
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); } }); }
Wenn der Spieler ein neues gespeichertes Spiel erstellt oder ein gespeichertes Spiel lädt, sendet die UI eine Anfrage an die Google Play-Spieldienste. Wenn die Anfrage erfolgreich ist, geben die Google Play-Spieldienste Informationen zum Erstellen oder Wiederherstellen des gespeicherten Spiels über den onActivityResult()
-Callback zurück. Dein Spiel kann diesen Callback überschreiben, um zu prüfen, ob während der Anfrage Fehler aufgetreten sind.
Das folgende Code-Snippet zeigt eine Beispielimplementierung von 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 // ... } } }
Gespeicherte Spiele schreiben
So speichern Sie Inhalte in einem gespeicherten Spiel:
- Öffnen Sie einen Snapshot über
SnapshotsClient.open()
asynchron. Rufen Sie dann das ObjektSnapshot
aus dem Ergebnis der Aufgabe ab. Rufen Sie dazuSnapshotsClient.DataOrConflict.getData()
auf. - Rufen Sie eine
SnapshotContents
-Instanz überSnapshotsClient.SnapshotConflict
ab. - Rufe
SnapshotContents.writeBytes()
auf, um die Spielerdaten im Byteformat zu speichern. - Nachdem alle Änderungen geschrieben sind, rufen Sie
SnapshotsClient.commitAndClose()
auf, um Ihre Änderungen an die Google-Server zu senden. Im Methodenaufruf kann Ihr Spiel optional zusätzliche Informationen enthalten, um den Google Play-Spieldiensten mitzuteilen, wie das gespeicherte Spiel den Spielern präsentiert werden soll. Diese Informationen werden in einemSnapshotMetaDataChange
-Objekt dargestellt, das Ihr Spiel mitSnapshotMetadataChange.Builder
erstellt.
Das folgende Snippet zeigt, wie in einem Spiel Änderungen an einem gespeicherten Spiel angewendet werden:
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); }
Wenn das Gerät des Spielers nicht mit einem Netzwerk verbunden ist, wenn deine App SnapshotsClient.commitAndClose()
aufruft, speichern die Google Play-Spieldienste die gespeicherten Spieldaten lokal auf dem Gerät. Wenn das Gerät wieder verbunden ist, synchronisieren die Google Play-Spieldienste die lokal im Cache gespeicherten Spieländerungen mit den Google-Servern.
Gespeicherte Spiele werden geladen
So rufen Sie gespeicherte Spiele für den aktuell angemeldeten Spieler ab:
- Öffnen Sie einen Snapshot über
SnapshotsClient.open()
asynchron. Rufen Sie dann das ObjektSnapshot
aus dem Ergebnis der Aufgabe ab. Rufen Sie dazuSnapshotsClient.DataOrConflict.getData()
auf. Alternativ kann Ihr Spiel auch einen bestimmten Snapshot über die Benutzeroberfläche für die Auswahl gespeicherter Spiele abrufen, wie unter Gespeicherte Spiele anzeigen beschrieben. - Rufen Sie die Instanz
SnapshotContents
überSnapshotsClient.SnapshotConflict
ab. - Rufen Sie
SnapshotContents.readFully()
auf, um den Inhalt des Snapshots zu lesen.
Das folgende Snippet zeigt, wie Sie ein bestimmtes gespeichertes Spiel laden können:
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. // ... } }); }
Umgang mit gespeicherten Spielkonflikten
Wenn Sie die Snapshot API in Ihrem Spiel verwenden, können mehrere Geräte Lese- und Schreibvorgänge für dasselbe gespeicherte Spiel ausführen. Falls ein Gerät vorübergehend die Netzwerkverbindung verliert und sich später wieder verbindet, kann es zu Datenkonflikten kommen. Das gespeicherte Spiel auf dem lokalen Gerät des Spielers ist dann nicht mehr mit der Remote-Version kompatibel, die auf den Servern von Google gespeichert ist.
Die Snapshots API bietet einen Mechanismus zur Konfliktlösung, bei dem beide Gruppen von in Konflikt stehenden gespeicherten Spielen zum Lesezeitpunkt aufgeführt werden. Außerdem können Sie eine geeignete Lösung für Ihr Spiel implementieren.
Wenn die Google Play-Spieldienste einen Datenkonflikt erkennen, gibt die Methode SnapshotsClient.DataOrConflict.isConflict()
den Wert true
zurück. In diesem Fall bietet die Klasse SnapshotsClient.SnapshotConflict
zwei Versionen des gespeicherten Spiels:
- Serverversion: die aktuelle Version, die Google Play-Spieldienste für die Verwendung auf dem Gerät des Spielers aktuell kennen
- Lokale Version: Eine geänderte Version auf einem der Geräte des Players, die Inhalte oder Metadaten enthält, die miteinander in Konflikt stehen. Sie muss nicht mit der Version übereinstimmen, die Sie speichern möchten.
Ihr Spiel muss entscheiden, wie der Konflikt gelöst werden soll, indem eine der angegebenen Versionen ausgewählt oder die Daten der beiden gespeicherten Spieleversionen zusammengeführt werden.
So können Sie gespeicherte Spielkonflikte erkennen und beheben:
- Rufen Sie
SnapshotsClient.open()
auf. Das Ergebnis der Aufgabe enthält die KlasseSnapshotsClient.DataOrConflict
. - Rufen Sie die Methode
SnapshotsClient.DataOrConflict.isConflict()
auf. Wenn das Ergebnis wahr ist, müssen Sie einen Konflikt lösen. - Rufen Sie
SnapshotsClient.DataOrConflict.getConflict()
auf, um eineSnaphotsClient.snapshotConflict
-Instanz abzurufen. - Rufen Sie
SnapshotsClient.SnapshotConflict.getConflictId()
auf, um die Konflikt-ID abzurufen, die den erkannten Konflikt eindeutig identifiziert. Ihr Spiel benötigt diesen Wert, um später eine Anfrage zur Konfliktlösung senden zu können. - Rufen Sie
SnapshotsClient.SnapshotConflict.getConflictingSnapshot()
auf, um die lokale Version abzurufen. - Rufen Sie
SnapshotsClient.SnapshotConflict.getSnapshot()
auf, um die Serverversion abzurufen. - Zum Lösen des gespeicherten Spielkonflikts wählen Sie eine Version aus, die Sie als endgültige Version auf dem Server speichern möchten, und übergeben sie an die Methode
SnapshotsClient.resolveConflict()
.
Das folgende Snippet zeigt und zeigt, wie mit einem gespeicherten Konflikt ein Spiel möglicherweise gehandhabt wird, indem das zuletzt gespeicherte Spiel als finale Version zum Speichern ausgewählt wird:
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"); } } }); }
Gespeicherte Spiele zur Konfliktlösung ändern
Wenn Sie Daten aus mehreren gespeicherten Spielen zusammenführen oder eine vorhandene Snapshot
ändern möchten, um sie als aufgelöste endgültige Version auf dem Server zu speichern, gehen Sie so vor:
- Rufen Sie
SnapshotsClient.open()
auf. - Rufen Sie
SnapshotsClient.SnapshotConflict.getResolutionSnapshotsContent()
auf, um ein neuesSnapshotContents
-Objekt abzurufen. - Führen Sie die Daten aus
SnapshotsClient.SnapshotConflict.getConflictingSnapshot()
undSnapshotsClient.SnapshotConflict.getSnapshot()
mit dem ObjektSnapshotContents
aus dem vorherigen Schritt zusammen. - Erstellen Sie optional eine
SnapshotMetadataChange
-Instanz, wenn Änderungen an den Metadatenfeldern vorgenommen wurden. - Rufen Sie
SnapshotsClient.resolveConflict()
auf. Übergeben Sie in Ihrem MethodenaufrufSnapshotsClient.SnapshotConflict.getConflictId()
als erstes Argument und dieSnapshotMetadataChange
- undSnapshotContents
-Objekte, die Sie zuvor als zweites bzw. drittes Argument geändert haben. - Wenn der
SnapshotsClient.resolveConflict()
-Aufruf erfolgreich war, speichert die API dasSnapshot
-Objekt auf dem Server und versucht, das Snapshot-Objekt auf Ihrem lokalen Gerät zu öffnen.- Bei einem Konflikt gibt
SnapshotsClient.DataOrConflict.isConflict()
true
zurück. In diesem Fall sollte Ihr Spiel zu Schritt 2 zurückkehren und die Schritte zum Ändern des Snapshots wiederholen, bis Konflikte behoben sind. - Wenn kein Konflikt vorliegt, gibt
SnapshotsClient.DataOrConflict.isConflict()
false
zurück und das ObjektSnapshot
kann von deinem Spiel geändert werden.
- Bei einem Konflikt gibt