Compatibilidad de Juegos guardados con Juegos de Android

En esta guía, se muestra cómo implementar la función de juegos guardados con la La API de snapshots que proporcionan los Servicios de juego de Google Play. Se pueden encontrar las APIs en los paquetes com.google.android.gms.games.snapshot y com.google.android.gms.games.

Antes de comenzar

Si aún no lo has hecho, puede resultarte útil consultar el Conceptos de Juegos guardados

Obtén el cliente de instantáneas

Para comenzar a usar la API de snapshots, el juego primero debe obtener un objeto SnapshotsClient. Puedes hacerlo llamando al Games.getSnapshotsClient() y pasa el y el GoogleSignInAccount del jugador actual. Para aprender a hacer lo siguiente: recuperar la información de la cuenta del jugador, consulta Acceso a juegos para Android.

Cómo especificar el alcance de Drive

La API de snapshots se basa en la API de Google Drive para el almacenamiento de juegos guardados. Para acceder a la API de Drive, tu aplicación debe especificar la Drive.SCOPE_APPFOLDER cuando se compila el cliente de Acceso con Google.

Este es un ejemplo de cómo hacerlo en el método onResume() para tu actividad de acceso:

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

Cómo mostrar los juegos guardados

Puedes integrar la API de snapshots en cualquier lugar que tu juego les brinde a los jugadores la opción de guardar o restablecer su progreso. Tu juego puede mostrar esta opción en los puntos designados de guardar/restablecer o permitir que los jugadores guarden o restablezcan el progreso en cualquier momento.

Una vez que los jugadores seleccionen la opción de guardar/restablecer el juego, este podrá mostrar, de manera opcional, una pantalla que les pedirá que introduzcan la información para un nuevo juego guardado o que seleccionen uno existente a fin de restablecerlo.

A fin de simplificar el desarrollo, la API de snapshots proporciona una interfaz de usuario (IU) de selección de juegos guardados predeterminada que está lista para usar. La IU selección de juegos guardados permite que los jugadores creen un nuevo juego guardado, consulten los detalles de los juegos guardados existentes y carguen los juegos guardados anteriores.

Para iniciar la IU predeterminada de juegos guardados, haz lo siguiente:

  1. Llama a SnapshotsClient.getSelectSnapshotIntent() para obtener una Intent para iniciar la configuración IU de selección de juegos guardados.
  2. Llama a startActivityForResult() y pasa ese Intent. Si la llamada se realiza de forma correcta, el juego muestra la IU de selección de juegos guardados, junto con las opciones que especificaste.

Este es un ejemplo de cómo iniciar la IU de selección de juegos guardados predeterminada:

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 el jugador selecciona crear un nuevo juego guardado o cargar uno existente, la IU envía una solicitud a los Servicios de juego de Google Play. Si la solicitud tiene éxito, Los Servicios de juego de Google Play devuelven información para crear o restablecer el juego guardado mediante el onActivityResult() devolución de llamada. Tu juego puede anular esta devolución de llamada para comprobar si se produjeron errores durante la solicitud.

El siguiente fragmento de código muestra una implementación de muestra 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
      // ...
    }
  }
}

Cómo escribir juegos guardados

Para almacenar contenido en un juego guardado, haz lo siguiente:

  1. Abre una instantánea de forma asíncrona a través de SnapshotsClient.open(). Luego, recupera el objeto Snapshot. a partir del resultado de la tarea llamando a SnapshotsClient.DataOrConflict.getData().
  2. Recupera una instancia de SnapshotContents a través de SnapshotsClient.SnapshotConflict.
  3. Llama a SnapshotContents.writeBytes() para almacenar los datos del reproductor en formato de bytes.
  4. Una vez que hayas escrito todos los cambios, llama SnapshotsClient.commitAndClose() para enviar los cambios a los servidores de Google. En la llamada de método, tu juego puede proporcionar información adicional para indicarles a los Servicios de juego de Google Play cómo presentar este juego guardado a los jugadores. Esta información se representa en un SnapshotMetaDataChange. que el juego crea con SnapshotMetadataChange.Builder.

En el siguiente fragmento, se muestra cómo tu juego puede confirmar los cambios en un juego guardado:

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 el dispositivo del jugador no está conectado a una red cuando llama la app SnapshotsClient.commitAndClose(), los Servicios de juego de Google Play almacenan los datos del juego guardado de forma local en el dispositivo. Cuando se vuelve a conectar el dispositivo, los Servicios de juego de Google Play sincronizan el juego que se guardó en la caché local en los servidores de Google.

Cargando juegos guardados

Si deseas recuperar los juegos guardados para el jugador actualmente conectado, haz lo siguiente:

  1. Abre una instantánea de forma asíncrona a través de SnapshotsClient.open(). Luego, recupera el objeto Snapshot. a partir del resultado de la tarea llamando a SnapshotsClient.DataOrConflict.getData(). Como alternativa, El juego también puede recuperar un resumen específico a través de la IU de selección de juegos guardados, como se describe en Cómo mostrar los juegos guardados
  2. Recupera la instancia SnapshotContents a través de SnapshotsClient.SnapshotConflict.
  3. Llama a SnapshotContents.readFully() para leer el contenido de la instantánea.

En el siguiente fragmento, se muestra cómo podrías cargar un juego guardado específico:

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

Manejo de conflictos de juegos guardados

Cuando se utiliza la API de snapshots en el juego, es posible que varios dispositivos realicen operaciones de lectura y escritura en el mismo juego guardado. En el caso de que un dispositivo pierda, de forma temporal, su conexión de red y se vuelva a conectar más tarde, se podrían producir conflictos de datos por los que el juego guardado que se almacenó en el dispositivo local del jugador no se sincronice con la versión remota que se almacenó en los servidores de Google.

La API de snapshots proporciona un mecanismo de resolución de conflictos que presenta ambos conjuntos de juegos guardados en conflicto en el momento de la lectura y te permite implementar una estrategia de resolución adecuada para tu juego.

Cuando los Servicios de juego de Google Play detectan un conflicto de datos, el El método SnapshotsClient.DataOrConflict.isConflict() devuelve un valor true. En este evento, el La clase SnapshotsClient.SnapshotConflict proporciona dos versiones del juego guardado:

  • Versión del servidor: Es la versión más actualizada que conocen los Servicios de juego de Google Play para que sea precisa. para el dispositivo del jugador y
  • Versión local: Una versión modificada detectada en uno de los dispositivos del jugador que contiene contenido o metadatos contradictorios. Es posible que no sea la misma que la versión que intentaste guardar.

Tu juego debe decidir cómo resolver el conflicto seleccionando una de las versiones proporcionadas o fusionando los datos de las dos versiones guardadas del juego.

Para detectar y resolver conflictos de juegos guardados, haz lo siguiente:

  1. Llama a SnapshotsClient.open(). El resultado de la tarea contiene una clase SnapshotsClient.DataOrConflict.
  2. Llama al método SnapshotsClient.DataOrConflict.isConflict(). Si el resultado es verdadero, tienes un conflicto por resolver.
  3. Llama a SnapshotsClient.DataOrConflict.getConflict() para recuperar un SnaphotsClient.snapshotConflict.
  4. Llama a SnapshotsClient.SnapshotConflict.getConflictId() para recuperar el ID de conflicto que identifica el conflicto detectado. Tu juego necesita este valor para enviar una solicitud de resolución de conflictos más adelante.
  5. Llama a SnapshotsClient.SnapshotConflict.getConflictingSnapshot() para obtener la versión local.
  6. Llama a SnapshotsClient.SnapshotConflict.getSnapshot() para obtener la versión del servidor.
  7. Para resolver el conflicto del juego guardado, selecciona la versión que quieras guardar en el servidor como el versión final y pasarla al método SnapshotsClient.resolveConflict().

En el siguiente fragmento, se muestra un ejemplo de cómo el juego podría controlar un conflicto de juego guardado seleccionando el que se modificó recientemente como la versión final para guardar:

private static final int MAX_SNAPSHOT_RESOLVE_RETRIES = 10;

TaskS<napshot >processSnapshotOpenResult(SnapshotsClient.DataOrConflictS<napshot >result,
                                         final int retryCount) {

  if (!result.isConflict()) {
    // There was no conflict, so return the result of the source.
    TaskCompletionSourceS<napshot >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.DataOrConflictS<napshot,>
              TaskS<napshot(>>) {
            @Override
            public TaskS<napshot >then(
                @NonNull TaskS<napshotsClient.DataOrConflictS<napshot >>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(C"ould not resolve snapshot conflicts)";
              }
            }
          });
}

Modificación de los juegos guardados para la resolución de conflictos

Si quieres combinar datos de varios juegos guardados o modificar un Snapshot existente para guardar en el servidor como la versión final resuelta, sigue estos pasos:

  1. Llama a SnapshotsClient.open() .
  2. Llama a SnapshotsClient.SnapshotConflict.getResolutionSnapshotsContent() para obtener uno nuevo. objeto SnapshotContents.
  3. Combina los datos de SnapshotsClient.SnapshotConflict.getConflictingSnapshot() y SnapshotsClient.SnapshotConflict.getSnapshot() al objeto SnapshotContents de paso anterior.
  4. De forma opcional, crea una instancia de SnapshotMetadataChange si se realizan cambios en los metadatos. .
  5. Llama a SnapshotsClient.resolveConflict(). En tu llamada de método, pasa SnapshotsClient.SnapshotConflict.getConflictId() como primer argumento Los objetos SnapshotMetadataChange y SnapshotContents que modificaste antes como la segunda y terceros, respectivamente.
  6. Si la llamada a SnapshotsClient.resolveConflict() se realiza de forma correcta, la API almacena el Snapshot al servidor y, luego, intenta abrir el objeto Snapshot en tu dispositivo local.