Android 遊戲的遊戲進度存檔

本指南將說明如何使用 Google Play 遊戲服務的快照 API。如需 API,請前往 com.google.android.gms.games.snapshot敬上 和 com.google.android.gms.games。 套件

事前準備

如需關於功能的詳細資訊,請參閱 遊戲進度存檔總覽

取得快照用戶端

若要使用快照 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 傳送要求給 Play 遊戲服務。如果要求成功 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()

  2. 擷取 Snapshot 物件,方法是呼叫 SnapshotsClient.DataOrConflict.getData()

  3. 擷取 SnapshotContents敬上 含有 SnapshotsClient.SnapshotConflict

  4. 致電 SnapshotContents.writeBytes()敬上 以位元組格式儲存玩家的資料。

  5. 寫入所有變更後,請呼叫 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()、 Play 遊戲服務會將遊戲進度存檔資料儲存在本機裝置。在裝置上 Play 遊戲服務會將本機快取的遊戲進度存檔同步變更為重新連線 Google 伺服器。

載入遊戲進度存檔

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

  1. 以非同步方式開啟快照 SnapshotsClient.open()

  2. 開張 Snapshot敬上 將物件從工作結果的 SnapshotsClient.DataOrConflict.getData()。此外,遊戲也可以擷取 透過遊戲進度存檔選擇 UI 建立快照, 顯示遊戲進度存檔

  3. 擷取 SnapshotContents敬上 含有 SnapshotsClient.SnapshotConflict

  4. 致電 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 提供衝突解決機制,可顯示 且可在閱讀時提供衝突的遊戲進度存檔 定義遊戲所需的策略

當 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() 做為第一個引數,而 SnapshotMetadataChange。 和 您稍早修改的 SnapshotContents 物件,並將 。

  6. 如果 SnapshotsClient.resolveConflict()敬上 呼叫成功,API 會將 Snapshot 物件儲存至伺服器, 會嘗試開啟本機裝置上的 Snapshot 物件。