支援 Android 遊戲的遊戲進度存檔功能

本指南將說明如何使用 Google Play 遊戲服務的快照 API。您可以在 com.google.android.gms.games.snapshotcom.google.android.gms.games 套件中取得此 API。

事前準備

如果您還沒有這樣做,建議您詳閱 遊戲進度存檔概念

取得快照用戶端

若要使用快照 API,遊戲必須先取得 SnapshotsClient 物件。要取得此物件,您可以呼叫 Games.getSnapshotsClient() 方法,然後在活動內傳遞。

指定雲端硬碟範圍

快照 API 需要使用 Google Drive API 才能儲存遊戲進度存檔。目的地: 存取 Drive API,您的應用程式必須指定 Drive.SCOPE_APPFOLDER敬上 。

以下舉例說明如何透過 onResume()敬上 方法操作:


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

顯示遊戲進度存檔

您可以在遊戲的任何位置整合快照 API 儲存或還原進度的選項。您的遊戲可能會顯示這類 或讓玩家儲存或還原點數 並隨時補充進度

玩家在遊戲中選取儲存/還原選項後,遊戲就可以 可視需要顯示提示畫面,提示玩家輸入新儲存的資訊 遊戲,或是選取要還原的現有遊戲進度存檔。

為簡化開發作業,快照 API 會提供預設的遊戲進度存檔使用者選擇使用者 使用者介面 (UI)。遊戲進度存檔選擇 UI 可讓玩家 建立新的遊戲進度存檔、查看現有遊戲進度存檔,以及載入先前儲存的遊戲進度存檔。

如要啟動預設的遊戲進度存檔 UI:

  1. 呼叫 SnapshotsClient.getSelectSnapshotIntent() 即可 Intent 用於啟動預設值 遊戲進度存檔選擇 UI。
  2. 呼叫 startActivityForResult() 並傳入 Intent。 如果呼叫成功,遊戲會顯示遊戲進度存檔選擇 UI 。

以下範例說明如何啟動預設的遊戲進度存檔選擇 UI:

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

如果玩家選擇建立新的遊戲進度存檔,或載入現有的遊戲進度存檔, UI 傳送要求至 Google Play 遊戲服務。如果要求成功 Google Play 遊戲服務會傳回資訊,以便透過以下方式建立或還原遊戲進度存檔 onActivityResult() 回呼。遊戲可能會覆寫此回呼,以確認要求期間是否發生任何錯誤。

下列程式碼片段顯示 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
      // ...
    }
  }
}

正在寫入遊戲進度存檔

在遊戲進度存檔中儲存內容:

  1. 透過 SnapshotsClient.open(),以非同步方式開啟快照。然後,擷取 Snapshot 物件 呼叫 SnapshotsClient.DataOrConflict.getData(),從工作結果的結果中呼叫。
  2. 透過 SnapshotsClient.SnapshotConflict 擷取 SnapshotContents 執行個體。
  3. 呼叫 SnapshotContents.writeBytes(),以位元組格式儲存玩家的資料。
  4. 寫入所有變更後,請呼叫 將變更傳送至 Google 的伺服器。SnapshotsClient.commitAndClose()。在方法呼叫中 您可以選擇提供額外資訊,讓 Google Play 遊戲服務 向玩家呈現這個遊戲進度存檔。這項資訊會以 SnapshotMetaDataChange 表示 物件,遊戲會使用 SnapshotMetadataChange.Builder 建立。

下列程式碼片段顯示遊戲如何對遊戲進度存檔做出變更:

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

應用程式呼叫時,如果玩家的裝置未連上網路 SnapshotsClient.commitAndClose(),Google Play 遊戲服務會將遊戲進度存檔儲存在本機 裝置。一旦裝置重新連線,Google Play 遊戲服務就會同步處理本機快取的遊戲進度存檔 Google 伺服器的變化

正在載入遊戲進度存檔

擷取目前登入玩家的遊戲進度存檔:

  1. 透過 SnapshotsClient.open(),以非同步方式開啟快照。然後,擷取 Snapshot 物件 呼叫 SnapshotsClient.DataOrConflict.getData(),從工作結果的結果中呼叫。或者,您也可以 遊戲也能透過遊戲進度存檔選擇 UI 擷取特定快照,如 顯示遊戲進度存檔
  2. 透過 SnapshotsClient.SnapshotConflict 擷取 SnapshotContents 執行個體。
  3. 呼叫 SnapshotContents.readFully() 以讀取快照的內容。

下列程式碼片段說明如何載入特定的遊戲進度存檔:

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

處理遊戲進度存檔衝突

在遊戲中使用快照 API 時, 讓裝置以同一遊戲進度存檔執行讀取和寫入作業。如果 裝置的網路連線暫時中斷,稍後又重新連線,這可能 將遊戲進度存檔儲存在玩家的本機裝置上,會造成資料衝突 與 Google 伺服器中儲存的遠端版本不同步。

快照 API 提供衝突解決機制,可顯示 且可在閱讀時提供衝突的遊戲進度存檔 定義遊戲所需的策略

當 Google Play 遊戲服務偵測到資料衝突時, SnapshotsClient.DataOrConflict.isConflict() 方法會傳回 true 的值。在此事件中, SnapshotsClient.SnapshotConflict 類別提供兩種遊戲進度存檔:

  • 伺服器版本:Google Play 遊戲服務已知的最新版本,以提供準確資訊 玩家的裝置;和
  • 本機版本:在其中一部玩家裝置中偵測到修改過的版本, 相衝突的內容或中繼資料這可能與您嘗試儲存的版本不同。

您的遊戲必須決定如何解決衝突,選擇以下其中一種方法: 或合併兩個遊戲進度存檔版本的資料。

偵測並解決遊戲進度存檔衝突問題:

  1. 呼叫 SnapshotsClient.open()。工作結果包含 SnapshotsClient.DataOrConflict 類別。
  2. 呼叫 SnapshotsClient.DataOrConflict.isConflict()方法。如果結果為 true, 問題解決。
  3. 呼叫 SnapshotsClient.DataOrConflict.getConflict() 以擷取 SnaphotsClient.snapshotConflict 執行個體。
  4. 呼叫 SnapshotsClient.SnapshotConflict.getConflictId() 擷取不重複的衝突 ID 就會識別偵測到的衝突您的遊戲需要使用這個值傳送衝突解決要求
  5. 呼叫 SnapshotsClient.SnapshotConflict.getConflictingSnapshot() 即可取得本機版本。
  6. 呼叫 SnapshotsClient.SnapshotConflict.getSnapshot() 即可取得伺服器版本。
  7. 如要解決遊戲進度存檔衝突問題,請選取要儲存至伺服器的版本,做為 最終版本,並傳遞至 SnapshotsClient.resolveConflict() 方法。

以下程式碼片段範例說明遊戲在處理進度存檔時,可能會如何處理遊戲進度存檔 選取最近修改過的遊戲進度存檔做為最終儲存版本:


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

修改遊戲進度存檔以解決衝突

如何合併多個遊戲進度存檔的資料或修改現有的Snapshot 如要在伺服器中儲存為已解決的最終版本,請按照下列步驟操作:

  1. 呼叫 SnapshotsClient.open()
  2. 呼叫 SnapshotsClient.SnapshotConflict.getResolutionSnapshotsContent() 以取得新的 SnapshotContents 物件。
  3. 合併 SnapshotsClient.SnapshotConflict.getConflictingSnapshot() 與 從 SnapshotsClient.SnapshotConflict.getSnapshot() 傳入 SnapshotContents 物件 上一個步驟
  4. 視需要建立 SnapshotMetadataChange 執行個體 (如果中繼資料有任何變更) 只要使用來自這些領域的 小型資料集訓練即可
  5. 呼叫 SnapshotsClient.resolveConflict()。在方法呼叫中,傳入 SnapshotsClient.SnapshotConflict.getConflictId() 做為第一個引數,而 您之前修改的 SnapshotMetadataChangeSnapshotContents 物件,變成第二項並 。
  6. 如果 SnapshotsClient.resolveConflict() 呼叫成功,API 會儲存 Snapshot 物件,並嘗試在本機裝置中開啟快照物件。