Unterstützung für gespeicherte Spiele in Android-Spielen

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

Hinweis

Sehen Sie sich gegebenenfalls die Spielkonzepte für gespeicherte Spiele an.

Snapshot-Client herunterladen

Damit du die Snapshots API verwenden kannst, muss dein Spiel zuerst ein SnapshotsClient-Objekt abrufen. Rufe dazu die Methode Games.getSnapshotsClient() auf und übergebe die Aktivität und die GoogleSignInAccount für den aktuellen Spieler. Informationen zum Abrufen der Informationen zum Spielerkonto findest du unter Anmeldung in Android-Spielen.

Drive-Bereich angeben

Die Snapshots API nutzt die Google Drive API für den Speicher von gespeicherten Spielen. Damit Ihre App auf die Drive API zugreifen kann, muss beim Erstellen des Google Sign-in-Clients der Umfang Drive.SCOPE_APPFOLDER angegeben werden.

Hier ein Beispiel dafür, wie Sie dies in der Methode onResume() für Ihre Anmeldeaktivität tun:

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
          }
        }
      });
}

Gespeicherte Spiele anzeigen

Sie können die Snapshots API überall einbinden, wo Spieler in Ihrem Spiel ihren Fortschritt speichern oder wiederherstellen können. In Ihrem Spiel kann eine solche Option an bestimmten Speicher-/Wiederherstellungspunkten angezeigt werden oder Spieler können den Fortschritt jederzeit speichern oder wiederherstellen.

Wenn Spieler die Option zum Speichern/Wiederherstellen in Ihrem Spiel auswählen, kann optional ein Bildschirm angezeigt werden, auf dem sie aufgefordert werden, Informationen für ein neues gespeichertes Spiel einzugeben oder ein vorhandenes gespeichertes Spiel auszuwählen, das wiederhergestellt werden soll.

Zur Vereinfachung der Entwicklung bietet die Snapshots API eine standardmäßige Benutzeroberfläche für die Auswahl gespeicherter Spiele, die Sie direkt verwenden können. Über die Benutzeroberfläche für die Auswahl gespeicherter Spiele können Spieler ein neues Spiel speichern, Details zu vorhandenen gespeicherten Spielen aufrufen und zuvor gespeicherte Spiele laden.

So starten Sie die Standard-Benutzeroberfläche für gespeicherte Spiele:

  1. Rufen Sie SnapshotsClient.getSelectSnapshotIntent() auf, um eine Intent zu erhalten, mit der die Standard-UI für die Auswahl gespeicherter Spiele gestartet wird.
  2. Rufen Sie startActivityForResult() auf und übergeben Sie Intent. Wenn der Aufruf erfolgreich ist, wird im Spiel die Benutzeroberfläche für die Auswahl der gespeicherten Spiele zusammen mit den von dir angegebenen Optionen angezeigt.

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

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);
    }
  });
}

Wenn der Spieler ein neues Spiel speichern oder ein vorhandenes Spiel laden möchte, sendet die Benutzeroberfläche eine Anfrage an die Google Play Spieledienste. Wenn die Anfrage erfolgreich ist, gibt Google Play Spiele-Dienste über den onActivityResult()-Callback Informationen zum Erstellen oder Wiederherstellen des gespeicherten Spiels zurück. Ihr Spiel kann diesen Rückruf ü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 speichern Sie Inhalte in einem gespeicherten Spiel:

  1. Snapshots asynchron über SnapshotsClient.open() öffnen Rufen Sie dann das Snapshot-Objekt aus dem Ergebnis der Aufgabe ab, indem Sie SnapshotsClient.DataOrConflict.getData() aufrufen.
  2. Rufen Sie eine SnapshotContents-Instanz über SnapshotsClient.SnapshotConflict ab.
  3. Rufe SnapshotContents.writeBytes() auf, um die Daten des Spielers im Byteformat zu speichern.
  4. Wenn Sie alle Änderungen vorgenommen haben, geben Sie SnapshotsClient.commitAndClose() ein, um sie an die Google-Server zu senden. Im Methodenaufruf kann Ihr Spiel optional zusätzliche Informationen angeben, um Google Play Spieledienste darüber zu informieren, wie dieses gespeicherte Spiel den Spielern präsentiert werden soll. Diese Informationen werden in einem SnapshotMetaDataChange-Objekt dargestellt, das in Ihrem Spiel mit SnapshotMetadataChange.Builder erstellt wird.

Das folgende Snippet zeigt, wie Ihr Spiel Änderungen an einem gespeicherten Spiel vornehmen könnte:

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);
}

Wenn das Gerät des Spielers nicht mit einem Netzwerk verbunden ist, wenn Ihre App SnapshotsClient.commitAndClose() aufruft, speichert Google Play Spiele die gespeicherten Spieldaten lokal auf dem Gerät. Wenn die Verbindung des Geräts wiederhergestellt wird, synchronisieren die Google Play-Spieldienste die lokal im Cache gespeicherten Änderungen an gespeicherten Spielen mit den Google-Servern.

Gespeicherte Spiele laden

So rufst du gespeicherte Spiele für den aktuell angemeldeten Spieler ab:

  1. Snapshots asynchron über SnapshotsClient.open() öffnen Rufen Sie dann das Snapshot-Objekt aus dem Ergebnis der Aufgabe ab, indem Sie SnapshotsClient.DataOrConflict.getData() aufrufen. Alternativ kann Ihr Spiel einen bestimmten Snapshot auch über die Benutzeroberfläche für die Auswahl der gespeicherten Spiele abrufen, wie unter Gespeicherte Spiele anzeigen beschrieben.
  2. Rufen Sie die Instanz SnapshotContents über SnapshotsClient.SnapshotConflict ab.
  3. Rufen Sie SnapshotContents.readFully() auf, um den Inhalt des Snapshots zu lesen.

Im folgenden Snippet wird gezeigt, 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 =
      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.
          // ...
        }
      });
}

Konflikte bei gespeicherten Spielen behandeln

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 später wieder eine Verbindung herstellt, kann dies zu Datenkonflikten führen, bei denen das auf dem lokalen Gerät eines Spielers gespeicherte Spiel nicht mit der Remoteversion synchronisiert ist, die auf den Google-Servern gespeichert ist.

Die Snapshots API bietet einen Konfliktlösungsmechanismus, der beim Lesen beide Konfliktsätze von gespeicherten Spielen präsentiert und es Ihnen ermöglicht, eine Lösungsstrategie zu implementieren, die für Ihr Spiel geeignet ist.

Wenn die Google Play-Spieldienste 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 aktuellste Version, die von den Google Play-Spieldiensten für das Gerät des Spielers als korrekt erkannt wird.
  • Lokale Version: Eine geänderte Version, die auf einem der Geräte des Spielers erkannt wurde und in Konflikt stehende Inhalte oder Metadaten enthält. Diese Version ist möglicherweise nicht mit der Version identisch, die Sie speichern wollten.

Ihr Spiel muss entscheiden, wie der Konflikt gelöst werden soll, indem eine der bereitgestellten Versionen ausgewählt oder die Daten der beiden gespeicherten Spielversionen zusammengeführt werden.

So erkennen und beheben Sie Konflikte bei gespeicherten Spielen:

  1. Rufen Sie SnapshotsClient.open() an. Das Aufgabenergebnis enthält eine SnapshotsClient.DataOrConflict-Klasse.
  2. Rufen Sie die Methode SnapshotsClient.DataOrConflict.isConflict() auf. Wenn das Ergebnis „wahr“ ist, müssen Sie einen Konflikt beheben.
  3. Rufen Sie SnapshotsClient.DataOrConflict.getConflict() auf, um eine SnaphotsClient.snapshotConflict-Instanz abzurufen.
  4. Rufen Sie SnapshotsClient.SnapshotConflict.getConflictId() auf, um die Konflikt-ID abzurufen, mit der der erkannte Konflikt eindeutig identifiziert wird. Dieser Wert ist für Ihr Spiel erforderlich, um später eine Anfrage zur Konfliktbehebung senden zu können.
  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. Wählen Sie zum Beheben des Konflikts mit dem gespeicherten Spiel eine Version aus, die Sie als endgültige Version auf dem Server speichern möchten, und übergeben Sie sie an die Methode SnapshotsClient.resolveConflict().

Im folgenden Snippet wird ein Beispiel dafür gezeigt, wie Ihr Spiel einen Konflikt bei gespeicherten Spielen behandeln könnte, indem das zuletzt geänderte Spiel als endgültige 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 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");
              }
            }
          });
}

Gespeicherte Spiele zur Konfliktbehebung ändern

Wenn Sie Daten aus mehreren gespeicherten Spielen zusammenführen oder eine vorhandene Snapshot ändern möchten, um sie 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ügen 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 geändert haben.
  5. Rufen Sie SnapshotsClient.resolveConflict() an. Geben Sie im Methodenaufruf SnapshotsClient.SnapshotConflict.getConflictId() als erstes Argument und die Objekte SnapshotMetadataChange und SnapshotContents, die Sie zuvor geändert haben, als zweites und drittes Argument an.
  6. Wenn der SnapshotsClient.resolveConflict()-Aufruf erfolgreich ist, speichert die API das Snapshot-Objekt auf dem Server und versucht, das Snapshot-Objekt auf Ihrem lokalen Gerät zu öffnen.