Este guia mostra como implementar Jogos salvos usando a API de snapshots
fornecida pelos serviços relacionados a jogos do Google Play. As APIs podem ser encontradas nos pacotes
com.google.android.gms.games.snapshot
e
com.google.android.gms.games
.
Antes de começar
Para mais informações sobre o recurso, consulte a Visão geral de Jogos salvos.
- Ative a compatibilidade com jogos salvos no seu jogo no Google Play Console.
- Faça o download e analise o exemplo de código dos jogos salvos na página de exemplos do Android.
- Conheça as recomendações descritas na Lista de verificação de qualidade.
Acessar o cliente de snapshots
Para começar a usar a API de snapshots, o jogo precisa ter um
objeto
SnapshotsClient
. Para isso, chame o método
Games.getSnapshotsContents()
e transmita a atividade.
Especificar o escopo do drive
A API de snapshots depende da API Google
Drive para armazenar os jogos salvos. Para
acessar a API Drive, seu app precisa especificar o escopo
Drive.SCOPE_APPFOLDER
ao criar o cliente de login do Google.
Confira um exemplo de como fazer isso no método
onResume()
para sua
atividade de login:
@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 } } }); }
Mostrar jogos salvos
É possível integrar a API de snapshots sempre que o jogo oferecer aos jogadores a opção de salvar ou restaurar o progresso deles. Seu jogo pode mostrar essa opção em pontos designados para salvar ou restaurar ou permitir que os jogadores salvem ou restaurem o progresso a qualquer momento.
Depois que os jogadores selecionam a opção de salvar ou restaurar o jogo, uma tela pode ser exibida, solicitando a inserção de informações de um novo jogo salvo ou a seleção de um jogo salvo para ser restaurado.
Para simplificar o desenvolvimento, a API de snapshots fornece uma interface do usuário (interface) padrão para seleção de jogos salvos que pode ser usada imediatamente. A IU de seleção de jogos salvos permite que os jogadores criem um novo jogo salvo, confiram detalhes sobre os já existentes e carreguem os anteriores.
Para iniciar a IU padrão de Jogos salvos.
- Chame
SnapshotsClient.getSelectSnapshotIntent()
para acessar umaIntent
e iniciar a interface de seleção padrão de jogos salvos. - Chame
startActivityForResult()
e transmita aquelaIntent
. Se a chamada for bem-sucedida, o jogo vai exibir a IU de seleção de jogos salvos e as opções especificadas.
Confira um exemplo de como abrir a interface padrão de seleção de jogos salvos:
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 o jogador criar um jogo novo ou carregar um jogo já salvo,
a IU vai enviar uma solicitação aos serviços relacionados a jogos do Google Play. Se a solicitação for bem-sucedida,
os serviços relacionados a jogos do Google Play vão retornar informações para criar ou restaurar o jogo salvo
usando o callback
onActivityResult()
. Seu jogo pode substituir esse callback para
verificar se ocorreram erros durante a solicitação.
O snippet de código a seguir mostra um exemplo de implementação 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 // ... } } }
Gravar jogos salvos
Para armazenar conteúdo em um jogo salvo, faça o seguinte:
Abra um snapshot de forma assíncrona usando
SnapshotsClient.open()
.Extraia o objeto
Snapshot
do resultado da tarefa chamandoSnapshotsClient.DataOrConflict.getData()
.Extraia uma instância do
SnapshotContents
comSnapshotsClient.SnapshotConflict
.Chame
SnapshotContents.writeBytes()
para armazenar os dados do jogador em formato de bytes.Depois de gravar todas as mudanças, chame
SnapshotsClient.commitAndClose()
para enviá-las aos servidores do Google. Na chamada de método, o jogo pode fornecer mais informações para informar aos serviços relacionados a jogos do Google Play como apresentar esse jogo salvo aos jogadores. Essas informações são representadas por um objetoSnapshotMetaDataChange
, que o jogo cria usandoSnapshotMetadataChange.Builder
.
O snippet a seguir mostra como o jogo pode confirmar mudanças em um jogo salvo:
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 o dispositivo do jogador não estiver conectado a uma rede quando o app chamar
SnapshotsClient.commitAndClose()
,
os serviços relacionados a jogos do Google Play vão armazenar os dados de jogos salvos localmente no dispositivo. Após a
reconexão do dispositivo, os serviços relacionados a jogos do Google Play sincronizam as mudanças no jogo salvo localmente em cache com os
servidores do Google.
Carregar jogos salvos
Para recuperar jogos salvos do jogador conectado no momento:
Abra um snapshot de forma assíncrona com
SnapshotsClient.open()
.Extraia o objeto
Snapshot
do resultado da tarefa chamandoSnapshotsClient.DataOrConflict.getData()
. Como alternativa, seu jogo também pode recuperar um snapshot específico pela interface de seleção de jogos salvos, conforme descrito em Mostrar jogos salvos.Extraia a instância
SnapshotContents
comSnapshotsClient.SnapshotConflict
.Chame
SnapshotContents.readFully()
para ler o conteúdo do snapshot.
O snippet a seguir mostra como carregar um jogo salvo específico:
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. // ... } }); }
Lidar com conflitos de jogos salvos
Ao usar a API de snapshots no seu jogo, é possível que vários dispositivos realizem leituras e gravações no mesmo jogo salvo. No caso de um dispositivo perder temporariamente a conexão de rede e depois se reconectar, isso pode causar conflitos de dados em que o jogo salvo no dispositivo local de um jogador esteja dessincronizado com a versão remota armazenada nos servidores do Google.
A API de snapshots fornece um mecanismo de resolução de conflitos que apresenta os dois conjuntos de jogos salvos conflitantes no tempo de leitura, além de permitir que você implemente uma estratégia de resolução adequada para o jogo.
Quando os serviços relacionados a jogos do Google Play detectam um conflito de dados, o método
SnapshotsClient.DataOrConflict.isConflict()
retorna um valor de true
. Nesse caso, a classe
SnapshotsClient.SnapshotConflict
fornece duas versões do jogo salvo:
Versão do servidor: a versão mais atualizada conhecida pelos serviços relacionados a jogos do Google Play como exata no dispositivo do jogador.
Versão local: é uma versão modificada detectada em um dos dispositivos do jogador que contém metadados ou conteúdo conflitantes. Essa versão pode não ser igual à que você tentou salvar.
Seu jogo precisa decidir como resolver o conflito, escolhendo uma das versões fornecidas ou mesclando os dados das duas versões do jogo salvo.
Para detectar e resolver conflitos de jogos salvos:
Chame
SnapshotsClient.open()
. O resultado da tarefa contém uma classeSnapshotsClient.DataOrConflict
.Chamando o método
SnapshotsClient.DataOrConflict.isConflict()
. Se o resultado for verdadeiro, há um conflito para resolver.Chame
SnapshotsClient.DataOrConflict.getConflict()
para recuperar uma instânciaSnapshotsClient.snapshotConflict
.Chame
SnapshotsClient.SnapshotConflict.getConflictId()
para recuperar o ID de conflito que identifica exclusivamente o conflito detectado. Seu jogo precisa desse valor para enviar uma solicitação de resolução de conflitos posteriormente.Chame
SnapshotsClient.SnapshotConflict.getConflictingSnapshot()
para acessar a versão local.Chame
SnapshotsClient.SnapshotConflict.getSnapshot()
para acessar a versão do servidor.Para resolver o conflito do jogo salvo, selecione uma versão que você quer salvar no servidor como a final e a transmita para o método
SnapshotsClient.resolveConflict()
.
O snippet a seguir mostra um exemplo de como seu jogo pode lidar com um conflito selecionando o jogo salvo modificado mais recentemente como a versão final a ser salva:
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"); } } }); }
Modificar jogos salvos
Se você quiser mesclar dados de vários jogos salvos ou modificar um
Snapshot
já existente para salvá-lo no servidor como a versão final resolvida, siga estas
etapas:
Chame
SnapshotsClient.open()
.Chame
SnapshotsClient.SnapshotConflict.getResolutionSnapshotsContent()
para receber um novo objetoSnapshotContents
.Mescle os dados de
SnapshotsClient.SnapshotConflict.getConflictingSnapshot()
eSnapshotsClient.SnapshotConflict.getSnapshot()
no objetoSnapshotContents
da etapa anterior.Se quiser, crie uma instância
SnapshotMetadataChange
se houver alterações nos campos de metadados.Chame
SnapshotsClient.resolveConflict()
. Na chamada de método, transmitaSnapshotsClient.SnapshotConflict.getConflictId()
como o primeiro argumento e os objetosSnapshotMetadataChange
eSnapshotContents
que você modificou anteriormente como o segundo e o terceiro argumentos, respectivamente.Se a chamada
SnapshotsClient.resolveConflict()
for bem-sucedida, a API vai armazenar o objetoSnapshot
no servidor e tentar abrir o objeto Snapshot no dispositivo local.- Se houver um conflito,
SnapshotsClient.DataOrConflict.isConflict()
vai retornartrue
. Nesse caso, o jogo precisa retornar à etapa 2 e repetir as etapas para modificar o snapshot até que os conflitos sejam resolvidos. - Se não houver conflito,
SnapshotsClient.DataOrConflict.isConflict()
vai retornarfalse
, e o objetoSnapshot
estará aberto para o jogo modificar.
- Se houver um conflito,