本指南說明如何使用 Google Play 遊戲服務提供的快照 API 實作已儲存的遊戲。您可以在 com.google.android.gms.games.snapshot
和 com.google.android.gms.games
套件中取得此 API。
事前準備
如果您尚未執行,請參閱遊戲進度存檔概念。
- 請務必在 Google Play 管理中心為遊戲啟用已儲存的遊戲支援。
- 請至 Android 範例頁面下載和檢閱遊戲進度存檔程式碼範例。
- 熟讀品質檢查清單說明的推薦事項。
取得快照用戶端
如要開始使用快照 API,遊戲必須先取得 SnapshotsClient
物件。要取得此物件,您可以呼叫 Games.getSnapshotsClient()
方法,然後在活動內傳遞。
指定雲端硬碟範圍
快照 API 需透過 Google Drive API 儲存遊戲儲存空間。如要存取 Drive API,您的應用程式必須在建立 Google 登入用戶端時指定 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:
- 呼叫
SnapshotsClient.getSelectSnapshotIntent()
以取得Intent
,用於啟動預設儲存的遊戲選擇 UI。 - 呼叫
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 // ... } } }
撰寫遊戲進度通知
在遊戲進度存檔中儲存內容:
- 透過
SnapshotsClient.open()
以非同步方式開啟快照。接著,請呼叫SnapshotsClient.DataOrConflict.getData()
,從工作結果中擷取Snapshot
物件。 - 透過
SnapshotsClient.SnapshotConflict
擷取SnapshotContents
執行個體。 - 呼叫
SnapshotContents.writeBytes()
即可以位元組格式儲存玩家的資料。 - 寫入所有變更之後,請呼叫
SnapshotsClient.commitAndClose()
將變更內容傳送至 Google 的伺服器。在方法呼叫中,您的遊戲可以選擇提供其他資訊,讓 Google Play 遊戲服務如何向玩家呈現這個已儲存遊戲。此資訊表示在遊戲使用SnapshotMetadataChange.Builder
建立的SnapshotMetaDataChange
物件中。
下列程式碼片段顯示遊戲如何對遊戲進度存檔做出變更:
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 的伺服器。
正在載入已儲存的遊戲
擷取目前登入玩家的遊戲進度存檔:
- 透過
SnapshotsClient.open()
以非同步方式開啟快照。接著,請呼叫SnapshotsClient.DataOrConflict.getData()
,從工作結果中擷取Snapshot
物件。或者,您的遊戲也可以透過已儲存的遊戲選取 UI 擷取特定快照,如顯示已儲存的遊戲一節所述。 - 透過
SnapshotsClient.SnapshotConflict
擷取SnapshotContents
執行個體。 - 呼叫
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 遊戲服務已知且符合玩家裝置的最新版本;以及
- 本機版本:在玩家的裝置上偵測到修改版本,當中含有衝突的內容或中繼資料。可能與您嘗試儲存的版本不同。
您的遊戲必須決定如何解決衝突問題:選擇其中一個版本,或合併兩個遊戲進度存檔版本的資料。
偵測並解決遊戲進度存檔衝突問題:
- 呼叫
SnapshotsClient.open()
。工作結果包含SnapshotsClient.DataOrConflict
類別。 - 呼叫
SnapshotsClient.DataOrConflict.isConflict()
方法。如果結果為 true,您就會得到衝突。 - 呼叫
SnapshotsClient.DataOrConflict.getConflict()
以擷取SnaphotsClient.snapshotConflict
執行個體。 - 呼叫
SnapshotsClient.SnapshotConflict.getConflictId()
以擷取可識別偵測到衝突的專屬衝突 ID。您的遊戲需要這個值來稍後傳送衝突解決要求。 - 呼叫
SnapshotsClient.SnapshotConflict.getConflictingSnapshot()
即可取得本機版本。 - 呼叫
SnapshotsClient.SnapshotConflict.getSnapshot()
以取得伺服器版本。 - 如要解決已儲存的遊戲衝突,請選取您要儲存至最終版本的版本做為最終版本,然後將該版本傳送至
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
以作為已解析的最終版本,並儲存至伺服器,請按照下列步驟操作:
- 呼叫
SnapshotsClient.open()
。 - 呼叫
SnapshotsClient.SnapshotConflict.getResolutionSnapshotsContent()
以取得新的SnapshotContents
物件。 - 將
SnapshotsClient.SnapshotConflict.getConflictingSnapshot()
和SnapshotsClient.SnapshotConflict.getSnapshot()
的資料合併到上一步中的SnapshotContents
物件。 - 如果中繼資料欄位有任何變更,可選擇建立
SnapshotMetadataChange
執行個體。 - 呼叫
SnapshotsClient.resolveConflict()
。在方法呼叫中傳入SnapshotsClient.SnapshotConflict.getConflictId()
做為第一個引數,並將之前修改的SnapshotMetadataChange
和SnapshotContents
物件分別做為第二個和第三個引數。 - 如果
SnapshotsClient.resolveConflict()
呼叫成功,API 會將Snapshot
物件儲存到伺服器,並嘗試在本機裝置上開啟快照物件。- 如果有衝突,
SnapshotsClient.DataOrConflict.isConflict()
會傳回true
。遇到這種情況時,遊戲應返回步驟 2,然後重複操作步驟以修改快照,直到衝突解決為止。 - 如果沒有任何衝突,
SnapshotsClient.DataOrConflict.isConflict()
會傳回false
,且遊戲會開啟Snapshot
物件並進行修改。
- 如果有衝突,