En esta guía, se muestra cómo implementar juegos guardados con 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 hiciste, puede resultarte útil consultar los conceptos de juegos de Juegos guardados.
- Asegúrate de habilitar la compatibilidad con los juegos guardados para tu juego en Google Play Console.
- Descarga y revisa la muestra de código de los juegos guardados en la página de muestras de Android.
- Familiarízate con las recomendaciones que se describen en la lista de tareas de calidad.
Obtén el cliente de instantáneas
Para comenzar a usar la API de snapshots, el juego primero debe obtener un objeto SnapshotsClient
. Para ello, llama al método Games.getSnapshotsClient()
y pasa la actividad y el GoogleSignInAccount
del reproductor actual. Si quieres saber cómo recuperar la información de la cuenta del jugador, consulta Información sobre el acceso a juegos para Android.
Especifica 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, la app debe especificar el alcance de 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 } } }); }
Muestra 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:
- Llama a
SnapshotsClient.getSelectSnapshotIntent()
para obtener unIntent
con el objetivo de iniciar la IU de selección de juegos guardados predeterminada. - Llama a
startActivityForResult()
y pasa eseIntent
. 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 elige 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 se realiza de forma correcta, los Servicios de juego de Google Play mostrarán información para crear o restablecer el juego guardado a través de la devolución de llamada onActivityResult()
. 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 // ... } } }
Escribe los juegos guardados
Para almacenar contenido en un juego guardado, haz lo siguiente:
- Abre una instantánea de manera asíncrona a través de
SnapshotsClient.open()
. Luego, recupera el objetoSnapshot
del resultado de la tarea llamando aSnapshotsClient.DataOrConflict.getData()
. - Recupera una instancia de
SnapshotContents
a través deSnapshotsClient.SnapshotConflict
. - Llama a
SnapshotContents.writeBytes()
para almacenar los datos del jugador en formato de bytes. - Una vez que todos los cambios estén escritos, llama a
SnapshotsClient.commitAndClose()
para enviarlos a los servidores de Google. En la llamada de método, el juego puede brindar, de manera opcional, información adicional para indicarles a los Servicios de juego de Google Play cómo mostrarles este juego guardado a los jugadores. Esta información se representa en un objetoSnapshotMetaDataChange
, que el juego crea medianteSnapshotMetadataChange.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 tu app llama a SnapshotsClient.commitAndClose()
, los Servicios de juego de Google Play almacenan los datos del juego guardado de forma local en el dispositivo. Cuando se vuelva a conectar el dispositivo, los Servicios de juego de Google Play sincronizan los cambios del juego guardado que se almacenaron en la caché local con los servidores de Google.
Carga los juegos guardados
Si deseas recuperar los juegos guardados para el jugador actualmente conectado, haz lo siguiente:
- Abre una instantánea de manera asíncrona a través de
SnapshotsClient.open()
. Luego, recupera el objetoSnapshot
del resultado de la tarea llamando aSnapshotsClient.DataOrConflict.getData()
. Como alternativa, tu juego también puede recuperar una instantánea específica a través de la IU de selección de juegos guardados, como se describe en Cómo mostrar juegos guardados. - Recupera la instancia de
SnapshotContents
a través deSnapshotsClient.SnapshotConflict
. - 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. // ... } }); }
Controla los 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 método SnapshotsClient.DataOrConflict.isConflict()
muestra un valor de true
. En este evento, la clase SnapshotsClient.SnapshotConflict
proporciona dos versiones del juego guardado:
- Versión del servidor: La versión más actualizada que conocen los Servicios de juego de Google Play para que sea precisa en el dispositivo del jugador.
- Versión local: Una versión modificada que se detecta en uno de los dispositivos del jugador y que tiene contenido o metadatos en conflicto. 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:
- Llama a
SnapshotsClient.open()
. El resultado de la tarea contiene una claseSnapshotsClient.DataOrConflict
. - Llama al método
SnapshotsClient.DataOrConflict.isConflict()
. Si el resultado es verdadero, tienes un conflicto para resolver. - Llama a
SnapshotsClient.DataOrConflict.getConflict()
para recuperar una instancia deSnaphotsClient.snapshotConflict
. - Llama a
SnapshotsClient.SnapshotConflict.getConflictId()
para recuperar el ID de conflicto que identifica, de forma única, el conflicto detectado. El juego necesita este valor para enviar una solicitud de resolución de conflicto más adelante. - Llama a
SnapshotsClient.SnapshotConflict.getConflictingSnapshot()
para obtener la versión local. - Llama a
SnapshotsClient.SnapshotConflict.getSnapshot()
para obtener la versión del servidor. - Para resolver el conflicto del juego guardado, selecciona una versión que desees guardar en el servidor como la versión final y pásala 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; 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"); } } }); }
Modifica los juegos guardados para resolver conflictos
Si deseas combinar datos de varios juegos guardados o modificar un Snapshot
existente para guardarlo en el servidor como la versión final resuelta, sigue estos pasos:
- Llama a
SnapshotsClient.open()
. - Llama a
SnapshotsClient.SnapshotConflict.getResolutionSnapshotsContent()
para obtener un objetoSnapshotContents
nuevo. - Combina los datos de
SnapshotsClient.SnapshotConflict.getConflictingSnapshot()
ySnapshotsClient.SnapshotConflict.getSnapshot()
en el objetoSnapshotContents
del paso anterior. - De manera opcional, crea una instancia de
SnapshotMetadataChange
si hay cambios en los campos de metadatos. - Llama a
SnapshotsClient.resolveConflict()
. En la llamada de método, pasaSnapshotsClient.SnapshotConflict.getConflictId()
como primer argumento, y los objetosSnapshotMetadataChange
ySnapshotContents
que modificaste antes como el segundo y el tercer argumento, respectivamente. - Si la llamada a
SnapshotsClient.resolveConflict()
se realiza de forma correcta, la API almacena el objetoSnapshot
en el servidor y, luego, intenta abrir el objeto Snapshot en tu dispositivo local.- Si hay un conflicto,
SnapshotsClient.DataOrConflict.isConflict()
muestratrue
. En este caso, el juego debería volver al paso 2 y repetir los pasos para modificar la instantánea hasta que se resuelvan los conflictos. - Si no hay ningún conflicto,
SnapshotsClient.DataOrConflict.isConflict()
muestrafalse
, y el objetoSnapshot
está abierto para que lo modifique tu juego.
- Si hay un conflicto,