このガイドでは、Google Play Games サービスが提供するスナップショット API を使用して保存済みゲームを実装する方法について説明します。API は com.google.android.gms.games.snapshot
パッケージと com.google.android.gms.games
パッケージに含まれています。
始める前に
まだ読んでいない場合は、 保存済みゲームのコンセプト。
- 必ず保存済みゲームのサポートを有効にしてください。 追加することをおすすめします
- Android のサンプルページで、保存済みゲームのコードサンプルをダウンロードして確認します。
- 品質チェックリストで説明されている推奨事項を確認します。
スナップショット クライアントの取得
スナップショット API の使用を開始するには、まず SnapshotsClient
オブジェクトを取得する必要があります。そのためには、Games.getSnapshotsClient()
メソッドを呼び出してアクティビティを渡します。
ドライブのスコープの指定
スナップショット API は、保存済みゲームの保存に Google 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 を起動するには:
SnapshotsClient.getSelectSnapshotIntent()
を呼び出して、デフォルトの保存済みゲーム選択 UI を起動するためのIntent
を取得します。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 Games サービスにリクエストを送信します。リクエストが成功すると
Google Play Games サービスは、保存済みゲームを作成または復元するための情報を
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 Games サービスに この保存済みゲームをプレーヤーに提示します。この情報はSnapshotMetaDataChange
で表されます。 このオブジェクトは、SnapshotMetadataChange.Builder
を使用してゲームが作成します。
次のスニペットは、保存済みゲームに対する変更を commit する方法を示しています。
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 Games サービスは、保存済みゲームデータを
クリックします。デバイスが再接続されると、Google Play Games サービスは、ローカルにキャッシュされた保存済みゲームの変更を 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 Games サービスでデータ競合が検出されると、SnapshotsClient.DataOrConflict.isConflict()
メソッドは true
値を返します。この場合 SnapshotsClient.SnapshotConflict
クラスは、保存済みゲームの 2 つのバージョンを提供します。
- サーバーのバージョン: Google Play Games サービスで、正確性が確認されている最新バージョンです。 プレーヤーのデバイス用および
- ローカル バージョン: 以下を含むプレーヤーのデバイスで検出された修正版 競合するコンテンツまたはメタデータです。保存しようとしたバージョンとは異なる可能性があります。
ゲームは、提供されたバージョンのいずれかを選択するか、2 つの保存済みゲーム バージョンのデータを統合することで、競合の解決方法を決定する必要があります。
保存済みゲームの競合を検出して解決するには:
SnapshotsClient.open()
を呼び出します。タスクの結果にSnapshotsClient.DataOrConflict
クラスが含まれます。SnapshotsClient.DataOrConflict.isConflict()
メソッドを呼び出します。結果が真の場合、 競合を解決する必要があります。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
オブジェクトが開かれていて、ゲームで変更できるようにします。
- 競合がある場合、