Assistenza per i giochi salvati nei giochi Android

Questa guida mostra come implementare il gioco di gioco salvato utilizzando l'API snapshot fornita da Google Play Giochi Services. Le API sono disponibili nei pacchetti com.google.android.gms.games.snapshot e com.google.android.gms.games.

Prima di iniziare

Se non l'hai già fatto, potresti trovare utile rileggere i concetti dei giochi salvati.

Recupero del client snapshot

Per iniziare a utilizzare l'API Snapshot, il gioco deve prima ottenere un oggetto SnapshotsClient. Per farlo, chiama il metodo Games.getSnapshotsClient() e trasmetti l'attività.

Specificare l'ambito di Drive

L'API Snapshot si basa sull'API Google Drive per l'archiviazione dei giochi salvati. Per accedere all'API Drive, l'app deve specificare l'ambito Drive.SCOPE_APPFOLDER durante la creazione del client di accesso Google.

Ecco un esempio di come eseguire questa operazione nel metodo onResume() per l'attività di accesso:


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

Visualizzazione dei giochi salvati

Puoi integrare l'API Snapshot ovunque il tuo gioco fornisca ai giocatori la possibilità di salvare o ripristinare i loro progressi. Il tuo gioco potrebbe mostrare questa opzione in punti di salvataggio/ripristino designati oppure consentire ai giocatori di salvare o ripristinare i progressi in qualsiasi momento.

Dopo che i giocatori hanno selezionato l'opzione di salvataggio/ripristino nel gioco, il gioco può visualizzare facoltativamente una schermata che chiede ai giocatori di inserire informazioni per un nuovo gioco salvato o per selezionare un gioco salvato esistente da ripristinare.

Per semplificare lo sviluppo, l'API Snapshot fornisce un'interfaccia utente predefinita per la selezione dei giochi salvati (UI) che puoi utilizzare immediatamente. L'interfaccia utente di selezione per i giochi salvati consente ai giocatori di creare un nuovo gioco salvato, visualizzare i dettagli dei giochi salvati esistenti e caricare giochi salvati precedenti.

Per avviare l'UI predefinita per i giochi salvati:

  1. Chiama il numero SnapshotsClient.getSelectSnapshotIntent() per ricevere un Intent per il lancio dell'UI di selezione predefinita dei giochi salvati.
  2. Chiama il numero startActivityForResult() e inseriscilo Intent. Se la chiamata ha esito positivo, il gioco mostra l'UI di selezione del gioco salvata, insieme alle opzioni specificate.

Ecco un esempio di come lanciare l'UI di selezione predefinita per i giochi salvati:

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

Se il giocatore sceglie di creare un nuovo gioco salvato o di caricare un gioco salvato esistente, l'UI invia una richiesta ai servizi per i giochi di Google Play. Se la richiesta va a buon fine, i servizi per i giochi di Google Play restituiscono informazioni per creare o ripristinare il gioco salvato tramite il callback onActivityResult(). Il tuo gioco può ignorare questo callback per controllare se si sono verificati errori durante la richiesta.

Il seguente snippet di codice mostra un'implementazione di esempio di 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
      // ...
    }
  }
}

Scrittura di giochi salvati

Per memorizzare contenuti in un gioco salvato:

  1. Aprire in modo asincrono uno snapshot tramite SnapshotsClient.open(). Quindi, recupera l'oggetto Snapshot dal risultato dell'attività chiamando SnapshotsClient.DataOrConflict.getData().
  2. Recupera un'istanza SnapshotContents tramite SnapshotsClient.SnapshotConflict.
  3. Richiama SnapshotContents.writeBytes() per memorizzare i dati del player in formato byte.
  4. Dopo aver scritto tutte le modifiche, chiama SnapshotsClient.commitAndClose() per inviare le modifiche ai server di Google. Nella chiamata metodo, il tuo gioco può fornire facoltativamente ulteriori informazioni per indicare ai servizi per i giochi di Google Play come presentare il gioco salvato ai giocatori. Queste informazioni vengono rappresentate in un oggetto SnapshotMetaDataChange, creato dal tuo gioco utilizzando SnapshotMetadataChange.Builder.

Il seguente snippet mostra in che modo il tuo gioco potrebbe eseguire il commit delle modifiche a un gioco salvato:

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

Se il dispositivo del player non è connesso a una rete quando la tua app chiama SnapshotsClient.commitAndClose(), i servizi per i giochi di Google Play archiviano localmente i dati dei giochi salvati sul dispositivo. Dopo la riconnessione del dispositivo, i servizi per i giochi di Google Play sincronizzano le modifiche al gioco salvate nella cache locale sui server di Google.

Caricamento delle partite salvate

Per recuperare le partite salvate per il giocatore attualmente connesso:

  1. Aprire in modo asincrono uno snapshot tramite SnapshotsClient.open(). Quindi, recupera l'oggetto Snapshot dal risultato dell'attività chiamando SnapshotsClient.DataOrConflict.getData(). In alternativa, il gioco può recuperare anche un'istantanea specifica tramite l'interfaccia utente di selezione dei giochi salvati, come descritto nella sezione Visualizzazione delle partite salvate.
  2. Recupera l'istanza SnapshotContents tramite SnapshotsClient.SnapshotConflict.
  3. Chiama il numero SnapshotContents.readFully() per leggere i contenuti dello snapshot.

Il seguente snippet mostra come potresti caricare uno specifico gioco salvato:

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

Gestire i conflitti tra giochi salvati

Quando utilizzi l'API Snapshot nel tuo gioco, è possibile che più dispositivi eseguano operazioni di lettura e scrittura sullo stesso gioco salvato. Nel caso in cui un dispositivo perde temporaneamente la connessione di rete e poi si riconnette nuovamente, il problema potrebbe essere causato da conflitti di dati a causa dei quali il gioco salvato memorizzato sul dispositivo locale del player non è sincronizzato con la versione remota archiviata nei server di Google.

L'API Snapshot fornisce un meccanismo di risoluzione dei conflitti che presenta entrambi gli insiemi di giochi salvati in conflitto in fase di lettura e ti consente di implementare una strategia di risoluzione appropriata per il gioco.

Quando Google Play Games Services rileva un conflitto di dati, il metodo SnapshotsClient.DataOrConflict.isConflict() restituisce il valore true. In questo evento, la classe SnapshotsClient.SnapshotConflict fornisce due versioni del gioco salvato:

  • Versione server: la versione più aggiornata nota dai servizi per i giochi di Google Play per la precisione del dispositivo del player; e
  • Versione locale: una versione modificata rilevata su uno dei dispositivi del player con contenuti o metadati in conflitto. Potrebbe non corrispondere alla versione che hai cercato di salvare.

È necessario che il tuo gioco decida come risolvere il conflitto scegliendo una delle versioni fornite o unendo i dati delle due versioni salvate.

Per rilevare e risolvere i conflitti relativi ai giochi salvati:

  1. Chiama il numero SnapshotsClient.open(). Il risultato dell'attività contiene una classe SnapshotsClient.DataOrConflict.
  2. Chiama il metodo SnapshotsClient.DataOrConflict.isConflict(). Se il risultato è vero, devi risolvere un conflitto.
  3. Chiama SnapshotsClient.DataOrConflict.getConflict() per recuperare un'istanza SnaphotsClient.snapshotConflict.
  4. Richiama SnapshotsClient.SnapshotConflict.getConflictId() per recuperare l'ID conflitto che identifica in modo univoco il conflitto rilevato. Il tuo gioco ha bisogno di questo valore per inviare una richiesta di risoluzione dei conflitti in un secondo momento.
  5. Chiama SnapshotsClient.SnapshotConflict.getConflictingSnapshot() per ottenere la versione locale.
  6. Chiama SnapshotsClient.SnapshotConflict.getSnapshot() per ottenere la versione server.
  7. Per risolvere il conflitto di giochi salvato, seleziona una versione che vuoi salvare come server come versione finale e trasmettila al metodo SnapshotsClient.resolveConflict().

Lo snippet che segue mostra l'esempio del modo in cui il gioco potrebbe gestire un conflitto di giochi salvato selezionando come versione finale il gioco salvato modificato più di recente:


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

Modifica di giochi salvati per risolvere i conflitti

Se vuoi unire i dati di più giochi salvati o modificare una versione di Snapshot esistente per salvarla sul server come versione finale risolta, segui questi passaggi:

  1. Chiama il numero SnapshotsClient.open() .
  2. Chiama SnapshotsClient.SnapshotConflict.getResolutionSnapshotsContent() per creare un nuovo oggetto SnapshotContents.
  3. Unisci i dati di SnapshotsClient.SnapshotConflict.getConflictingSnapshot() e SnapshotsClient.SnapshotConflict.getSnapshot() nell'oggetto SnapshotContents del passaggio precedente.
  4. Facoltativamente, crea un'istanza SnapshotMetadataChange in caso di modifiche ai campi dei metadati.
  5. Chiama il numero SnapshotsClient.resolveConflict(). Nella chiamata del metodo, inserisci SnapshotsClient.SnapshotConflict.getConflictId() come primo argomento e gli oggetti SnapshotMetadataChange e SnapshotContents che hai modificato in precedenza rispettivamente come secondo e terzo argomento.
  6. Se la chiamata SnapshotsClient.resolveConflict() va a buon fine, l'API archivia l'oggetto Snapshot sul server e tenta di aprire l'oggetto Snapshot sul tuo dispositivo locale.