Gespeicherte Spiele für Android-Spiele

In diesem Leitfaden erfahren Sie, wie Sie gespeicherte Spiele mit der Snapshots API implementieren, die von Google Play-Spieldienste bereitgestellt wird. Die APIs finden Sie in den Paketen com.google.android.gms.games.snapshot und com.google.android.gms.games.

Hinweis

Weitere Informationen zu diesem Feature finden Sie in der Übersicht über gespeicherte Spiele.

Snapshots-Client abrufen

Bevor Sie die Snapshots API verwenden können, muss Ihr Spiel zuerst ein SnapshotsClient-Objekt abrufen. Rufen Sie dazu die Methode Games.getSnapshotsContents() auf und übergeben Sie die Aktivität.

Gespeicherte Spiele anzeigen

Sie können die Snapshots API überall dort einbinden, wo Spieler in Ihrem Spiel die Möglichkeit haben, ihren Fortschritt zu speichern oder wiederherzustellen. In Ihrem Spiel wird eine solche Option möglicherweise an bestimmten Speicher- oder Wiederherstellungspunkten angezeigt oder Spieler können den Fortschritt jederzeit speichern oder wiederherstellen.

Sobald Spieler in Ihrem Spiel die Option zum Speichern oder Wiederherstellen auswählen, 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 zum Wiederherstellen auszuwählen.

Um die Entwicklung zu vereinfachen, bietet die Snapshots API eine standardmäßige Benutzeroberfläche für die Auswahl gespeicherter Spiele, die Sie sofort verwenden können. Über die Benutzeroberfläche für gespeicherte Spiele können Spieler ein neues gespeichertes Spiel erstellen, Details zu vorhandenen gespeicherten Spielen aufrufen und frühere gespeicherte Spiele laden.

So starten Sie die Standard-UI für gespeicherte Spiele:

  1. Rufen Sie SnapshotsClient.getSelectSnapshotIntent() auf, um ein Intent zum Starten der Standard-UI für die Auswahl gespeicherter Spiele abzurufen.
  2. Rufen Sie startActivityForResult() auf und übergeben Sie die Intent. Wenn der Aufruf erfolgreich ist, wird im Spiel die Benutzeroberfläche zur Auswahl gespeicherter Spiele mit den von Ihnen angegebenen Optionen angezeigt.

Hier ist ein Beispiel dafür, wie Sie die Standard-Benutzeroberfläche für die Auswahl gespeicherter Spiele starten:

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 vorhandenes gespeichertes Spiel lädt, sendet die Benutzeroberfläche eine Anfrage an die Play-Spieldienste. Wenn die Anfrage erfolgreich ist, gibt Play Games Services Informationen zum Erstellen oder Wiederherstellen des gespeicherten Spiels über den onActivityResult()-Callback zurück. Ihr Spiel kann diesen Callback überschreiben, um zu prüfen, ob bei 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 speicherst du Inhalte in einem gespeicherten Spiel:

  1. Öffnen Sie einen Snapshot asynchron mit SnapshotsClient.open().

  2. Rufen Sie das Snapshot-Objekt aus dem Ergebnis der Aufgabe ab, indem Sie SnapshotsClient.DataOrConflict.getData() aufrufen.

  3. Rufen Sie mit SnapshotsClient.SnapshotConflict eine SnapshotContents-Instanz ab.

  4. Rufe SnapshotContents.writeBytes() auf, um die Daten des Spielers im Byteformat zu speichern.

  5. Sobald alle Änderungen geschrieben wurden, rufen Sie SnapshotsClient.commitAndClose() auf, um die Änderungen an die Server von Google zu senden. Im Methodenaufruf kann Ihr Spiel optional zusätzliche Informationen angeben, um den Play-Spieldiensten mitzuteilen, wie dieses gespeicherte Spiel den Spielern präsentiert werden soll. Diese Informationen werden in einem SnapshotMetaDataChange-Objekt dargestellt, das von Ihrem Spiel mit SnapshotMetadataChange.Builder erstellt wird.

Das folgende Snippet zeigt, wie Ihr Spiel Änderungen an einem gespeicherten Spiel vornehmen kann:

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 Ihre App SnapshotsClient.commitAndClose() aufruft, speichern die Play-Spieledienste die gespeicherten Spieldaten lokal auf dem Gerät. Wenn das Gerät wieder verbunden ist, synchronisiert Play-Spieldienste die lokal im Cache gespeicherten Änderungen am gespeicherten Spiel mit den Google-Servern.

Gespeicherte Spiele laden

So rufen Sie gespeicherte Spiele für den authentifizierten Spieler ab:

  1. Öffnen Sie einen Snapshot asynchron mit SnapshotsClient.open().

  2. Rufen Sie das Snapshot-Objekt aus dem Ergebnis der Aufgabe ab, indem Sie SnapshotsClient.DataOrConflict.getData() aufrufen. Alternativ kann Ihr Spiel auch einen bestimmten Snapshot über die Benutzeroberfläche zur Auswahl gespeicherter Spiele abrufen, wie unter Gespeicherte Spiele anzeigen beschrieben.

  3. Rufen Sie die SnapshotContents-Instanz mit SnapshotsClient.SnapshotConflict ab.

  4. 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.
          // ...
        }
      });
}

Konflikte bei gespeicherten Spielen beheben

Wenn Sie die Snapshots API in Ihrem Spiel verwenden, können mehrere Geräte Lese- und Schreibvorgänge für dasselbe gespeicherte Spiel ausführen. Wenn ein Gerät vorübergehend die Netzwerkverbindung verliert und sich später wieder verbindet, kann dies zu Datenkonflikten führen. Das auf dem lokalen Gerät eines Spielers gespeicherte Spiel ist dann nicht mit der Remote-Version auf den Google-Servern synchronisiert.

Die Snapshots API bietet einen Mechanismus zur Konfliktlösung, der beide Sätze von in Konflikt stehenden gespeicherten Spielen zur Lesezeit präsentiert und es Ihnen ermöglicht, eine für Ihr Spiel geeignete Lösungsstrategie zu implementieren.

Wenn die Play-Spieledienste einen Datenkonflikt erkennen, gibt die Methode SnapshotsClient.DataOrConflict.isConflict() den Wert true zurück. In diesem Fall stellt die Klasse SnapshotsClient.SnapshotConflict zwei Versionen des gespeicherten Spiels bereit:

  • Serverversion: Die aktuelle Version, die von den Play-Spieldiensten als korrekt für das Gerät des Spielers erkannt wird.

  • Lokale Version: Eine geänderte Version, die auf einem der Geräte des Spielers erkannt wurde und die in Konflikt stehende Inhalte oder Metadaten enthält. Das muss nicht dieselbe Version sein, die Sie speichern wollten.

Ihr Spiel muss entscheiden, wie der Konflikt behoben werden soll. Dazu kann es eine der bereitgestellten Versionen auswählen oder die Daten der beiden gespeicherten Spielversionen zusammenführen.

So erkennen und beheben Sie Konflikte bei gespeicherten Spielen:

  1. Rufen Sie SnapshotsClient.open() an. Das Ergebnis der Aufgabe enthält eine SnapshotsClient.DataOrConflict-Klasse.

  2. Rufen Sie die Methode SnapshotsClient.DataOrConflict.isConflict() auf. Wenn das Ergebnis „true“ ist, müssen Sie einen Konflikt beheben.

  3. Rufen Sie SnapshotsClient.DataOrConflict.getConflict() auf, um eine SnapshotsClient.snapshotConflict-Instanz abzurufen.

  4. 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 zu senden.

  5. Rufen Sie SnapshotsClient.SnapshotConflict.getConflictingSnapshot() auf, um die lokale Version zu erhalten.

  6. Rufen Sie SnapshotsClient.SnapshotConflict.getSnapshot() auf, um die Serverversion abzurufen.

  7. Um den Konflikt mit dem gespeicherten Spiel zu beheben, wählen Sie eine Version aus, die Sie als endgültige Version auf dem Server speichern möchten, und übergeben Sie sie an die Methode SnapshotsClient.resolveConflict().

Das folgende Snippet zeigt ein Beispiel dafür, wie Ihr Spiel einen Konflikt mit einem gespeicherten Spiel behandeln kann, indem es das zuletzt geänderte gespeicherte Spiel als endgültige Version zum Speichern auswählt:

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 ändern

Wenn Sie Daten aus mehreren gespeicherten Spielen zusammenführen oder ein vorhandenes Snapshot ändern möchten, um es als endgültige Version auf dem Server zu speichern, gehen Sie so vor:

  1. Rufen Sie SnapshotsClient.open() an.

  2. Rufen Sie SnapshotsClient.SnapshotConflict.getResolutionSnapshotsContent() auf, um ein neues SnapshotContents-Objekt zu erhalten.

  3. Führen Sie die Daten aus SnapshotsClient.SnapshotConflict.getConflictingSnapshot() und SnapshotsClient.SnapshotConflict.getSnapshot() in das SnapshotContents-Objekt aus dem vorherigen Schritt ein.

  4. Optional: Erstellen Sie eine SnapshotMetadataChange-Instanz, wenn sich die Metadatenfelder ändern.

  5. Rufen Sie SnapshotsClient.resolveConflict() an. Übergeben Sie in Ihrem Methodenaufruf SnapshotsClient.SnapshotConflict.getConflictId() als erstes Argument und die Objekte SnapshotMetadataChange und SnapshotContents, die Sie zuvor geändert haben, als zweites bzw. drittes Argument.

  6. Wenn der Aufruf von SnapshotsClient.resolveConflict() erfolgreich ist, speichert die API das Snapshot-Objekt auf dem Server und versucht, das Snapshot-Objekt auf Ihrem lokalen Gerät zu öffnen.