Dukungan game tersimpan di game Android

Panduan ini menunjukkan cara menerapkan game tersimpan menggunakan snapshot API yang disediakan oleh Layanan game Google Play. API ini dapat ditemukan dalam paket com.google.android.gms.games.snapshot dan com.google.android.gms.games.

Sebelum memulai

Jika Anda belum melakukannya, sebaiknya tinjau konsep game Game Tersimpan.

Mendapatkan klien snapshot

Untuk mulai menggunakan snapshot API, game Anda harus mendapatkan objek SnapshotsClient terlebih dahulu. Anda dapat melakukannya dengan memanggil metode Games.getSnapshotsClient() dan meneruskan aktivitas serta GoogleSignInAccount untuk pemutar saat ini. Untuk mempelajari cara mengambil informasi akun pemain, lihat Login di Game Android.

Menentukan cakupan Drive

Snapshot API bergantung pada Google Drive API untuk penyimpanan game tersimpan. Untuk mengakses Drive API, aplikasi Anda harus menentukan cakupan Drive.SCOPE_APPFOLDER saat mem-build klien login dengan Google.

Berikut adalah contoh cara melakukannya di metode onResume() untuk aktivitas login Anda:

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

Menampilkan game tersimpan

Anda dapat mengintegrasikan snapshot API di mana pun game Anda menyediakan opsi kepada pemain untuk menyimpan atau memulihkan progresnya. Game Anda mungkin menampilkan opsi tersebut di titik penyimpanan/pemulihan yang ditentukan, atau memungkinkan pemain untuk menyimpan atau memulihkan progres kapan saja.

Setelah pemain memilih opsi simpan/pulihkan dalam game, game Anda dapat secara opsional menampilkan layar yang meminta pemain untuk memasukkan informasi game tersimpan yang baru atau memilih game tersimpan yang sudah ada untuk dipulihkan.

Untuk menyederhanakan pengembangan, snapshot API menyediakan antarmuka pengguna (UI) pilihan game tersimpan default yang dapat Anda gunakan secara langsung. UI pilihan game tersimpan memungkinkan pemain membuat game tersimpan baru, melihat detail tentang game tersimpan yang ada, dan memuat game tersimpan sebelumnya.

Untuk meluncurkan UI Game Tersimpan default:

  1. Panggil SnapshotsClient.getSelectSnapshotIntent() untuk mendapatkan Intent guna meluncurkan UI pilihan game tersimpan default.
  2. Panggil startActivityForResult() dan teruskan Intent tersebut. Jika panggilan berhasil, game akan menampilkan UI pilihan game tersimpan, beserta opsi yang Anda tentukan.

Berikut adalah contoh cara meluncurkan UI pilihan game tersimpan default:

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

Jika pemain memilih untuk membuat game tersimpan baru atau memuat game tersimpan yang sudah ada, UI akan mengirimkan permintaan ke Layanan game Google Play. Jika permintaan berhasil, Layanan game Google Play akan menampilkan informasi untuk membuat atau memulihkan game tersimpan melalui callback onActivityResult(). Game Anda dapat mengganti callback ini untuk memeriksa apakah terjadi error selama permintaan.

Cuplikan kode berikut menunjukkan contoh implementasi 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
      // ...
    }
  }
}

Menulis game tersimpan

Untuk menyimpan konten ke game tersimpan:

  1. Buka snapshot secara asinkron melalui SnapshotsClient.open(). Kemudian, ambil objek Snapshot dari hasil tugas dengan memanggil SnapshotsClient.DataOrConflict.getData().
  2. Ambil instance SnapshotContents melalui SnapshotsClient.SnapshotConflict.
  3. Panggil SnapshotContents.writeBytes() untuk menyimpan data pemain dalam format byte.
  4. Setelah semua perubahan ditulis, panggil SnapshotsClient.commitAndClose() untuk mengirim perubahan ke server Google. Dalam panggilan metode, game Anda dapat secara opsional memberikan informasi tambahan untuk memberi tahu Layanan game Google Play cara menampilkan game tersimpan ini kepada pemain. Informasi ini ditunjukkan dalam objek SnapshotMetaDataChange, yang dibuat game Anda menggunakan SnapshotMetadataChange.Builder.

Cuplikan berikut menunjukkan cara game Anda dapat melakukan perubahan pada game tersimpan:

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

Jika perangkat pemain tidak terhubung ke jaringan saat aplikasi memanggil SnapshotsClient.commitAndClose(), Layanan game Google Play akan menyimpan data game tersimpan secara lokal di perangkat. Setelah perangkat dihubungkan kembali, Layanan game Google Play akan menyinkronkan perubahan game tersimpan yang di-cache secara lokal ke server Google.

Memuat game tersimpan

Untuk mengambil game tersimpan dari pemain yang saat ini login:

  1. Buka snapshot secara asinkron melalui SnapshotsClient.open(). Kemudian, ambil objek Snapshot dari hasil tugas dengan memanggil SnapshotsClient.DataOrConflict.getData(). Atau, game Anda juga dapat mengambil snapshot tertentu melalui UI pilihan game tersimpan, seperti yang dijelaskan dalam Menampilkan game tersimpan.
  2. Ambil instance SnapshotContents melalui SnapshotsClient.SnapshotConflict.
  3. Panggil SnapshotContents.readFully() untuk membaca konten snapshot.

Cuplikan berikut menunjukkan cara memuat game tersimpan tertentu:

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

Menangani konflik game tersimpan

Saat menggunakan snapshot API dalam game, beberapa perangkat dapat melakukan pembacaan dan penulisan di game tersimpan yang sama. Jika koneksi jaringan terputus untuk sementara dan kemudian terhubung kembali, hal ini dapat menyebabkan konflik data saat game tersimpan yang disimpan di perangkat lokal pemain tidak sinkron dengan versi jarak jauh yang tersimpan di server Google.

Snapshot API memberikan mekanisme resolusi konflik yang menyajikan serangkaian game tersimpan yang mengalami konflik pada waktu baca dan memungkinkan Anda menerapkan strategi penyelesaian yang sesuai untuk game Anda.

Saat Layanan game Google Play mendeteksi konflik data, metode SnapshotsClient.DataOrConflict.isConflict() akan menampilkan nilai true. Dalam hal ini, class SnapshotsClient.SnapshotConflict akan menyediakan dua versi game tersimpan:

  • Versi server: Versi terbaru yang diketahui oleh Layanan game Google Play agar akurat untuk perangkat pemain; dan
  • Versi lokal: Versi modifikasi yang terdeteksi di salah satu perangkat pemain yang berisi konten atau metadata yang mengalami konflik. Versi ini mungkin tidak sama dengan versi yang Anda coba simpan.

Game Anda harus menentukan cara menyelesaikan konflik dengan memilih salah satu versi yang disediakan atau menggabungkan data dari kedua versi game tersimpan.

Untuk mendeteksi dan menyelesaikan konflik game tersimpan:

  1. Panggil SnapshotsClient.open(). Hasil tugas berisi class SnapshotsClient.DataOrConflict.
  2. Panggil metode SnapshotsClient.DataOrConflict.isConflict(). Jika hasilnya benar, Anda memiliki konflik yang harus diselesaikan.
  3. Panggil SnapshotsClient.DataOrConflict.getConflict() untuk mengambil instance SnaphotsClient.snapshotConflict.
  4. Panggil SnapshotsClient.SnapshotConflict.getConflictId() untuk mengambil ID konflik yang secara unik mengidentifikasi konflik yang terdeteksi. Game Anda memerlukan nilai ini untuk mengirim permintaan penyelesaian konflik di lain waktu.
  5. Panggil SnapshotsClient.SnapshotConflict.getConflictingSnapshot() untuk mendapatkan versi lokal.
  6. Panggil SnapshotsClient.SnapshotConflict.getSnapshot() untuk mendapatkan versi server.
  7. Untuk menyelesaikan konflik game tersimpan, pilih versi yang ingin Anda simpan ke server sebagai versi akhir, lalu teruskan ke metode SnapshotsClient.resolveConflict().

Cuplikan berikut menunjukkan dan memberikan contoh cara game Anda dapat menangani konflik game tersimpan dengan memilih game tersimpan yang terakhir diubah sebagai versi final untuk disimpan:

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

Mengubah game tersimpan untuk resolusi konflik

Jika Anda ingin menggabungkan data dari beberapa game tersimpan atau mengubah Snapshot yang ada untuk disimpan ke server sebagai versi final yang telah diselesaikan, ikuti langkah-langkah berikut:

  1. Panggil SnapshotsClient.open() .
  2. Panggil SnapshotsClient.SnapshotConflict.getResolutionSnapshotsContent() untuk mendapatkan objek SnapshotContents baru.
  3. Gabungkan data dari SnapshotsClient.SnapshotConflict.getConflictingSnapshot() dan SnapshotsClient.SnapshotConflict.getSnapshot() ke dalam objek SnapshotContents dari langkah sebelumnya.
  4. Secara opsional, buat instance SnapshotMetadataChange jika ada perubahan pada kolom metadata.
  5. Panggil SnapshotsClient.resolveConflict(). Dalam panggilan metode Anda, teruskan SnapshotsClient.SnapshotConflict.getConflictId() sebagai argumen pertama, serta objek SnapshotMetadataChange dan SnapshotContents yang sebelumnya Anda ubah sebagai argumen kedua dan ketiga.
  6. Jika panggilan SnapshotsClient.resolveConflict() berhasil, API akan menyimpan objek Snapshot ke server dan mencoba membuka objek Snapshot di perangkat lokal Anda.