В связи с прекращением поддержки API Google Sign-In , мы удаляем SDK для игр версии 1 в 2026 году. После февраля 2025 года вы не сможете публиковать в Google Play игры, которые были интегрированы с SDK для игр версии 1. Мы рекомендуем использовать вместо него SDK для игр версии 2.
Хотя существующие игры с интеграцией предыдущих версий v1 будут продолжать работать еще пару лет, мы рекомендуем перейти на версию v2, начиная с июня 2025 года.
Данное руководство предназначено для использования SDK Play Games Services v1. Информацию о последней версии SDK см. в документации v2 .
В этом руководстве показано, как реализовать сохранение игр с помощью API снимков, предоставляемого сервисами Google Play Games. API можно найти в пакетах com.google.android.gms.games.snapshot и com.google.android.gms.games .
Прежде чем начать
Если вы еще этого не сделали, вам может быть полезно ознакомиться с основными принципами игры в разделе «Сохраненные игры» .
- Обязательно включите поддержку сохранений для вашей игры в консоли Google Play.
- Скачайте и просмотрите пример кода сохраненных игр на странице примеров для Android .
- Ознакомьтесь с рекомендациями, описанными в Контрольном списке качества .
Загрузите клиент для создания снимков.
Для начала использования API снимков экрана ваша игра должна получить объект SnapshotsClient . Это можно сделать, вызвав метод Games.getSnapshotsClient() и передав в него активность и учетную GoogleSignInAccount текущего игрока. Чтобы узнать, как получить информацию об учетной записи игрока, см. раздел «Вход в систему в играх Android» .
Укажите область действия диска.
API для создания снимков экрана использует API Google Drive для хранения сохраненных игр. Для доступа к API Drive ваше приложение должно указать область видимости Drive.SCOPE_APPFOLDER при создании клиента авторизации Google.
Вот пример того, как это сделать в методе onResume() для вашей активности входа в систему:
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 } } }); }
Отображение сохраненных игр
Вы можете интегрировать API снимков прогресса везде, где ваша игра предоставляет игрокам возможность сохранять или восстанавливать свой прогресс. Ваша игра может отображать такую опцию в специально отведенных точках сохранения/восстановления или позволять игрокам сохранять или восстанавливать прогресс в любое время.
После того, как игроки выберут опцию сохранения/восстановления в вашей игре, может появиться экран, предлагающий ввести информацию для нового сохраненного файла или выбрать существующий сохраненный файл для восстановления.
Для упрощения разработки API снимков предоставляет стандартный пользовательский интерфейс выбора сохраненных игр, который можно использовать сразу же. Интерфейс выбора сохраненных игр позволяет игрокам создавать новые сохраненные игры, просматривать подробную информацию о существующих сохраненных играх и загружать предыдущие сохраненные игры.
Чтобы запустить стандартный интерфейс сохраненных игр:
- Вызовите
SnapshotsClient.getSelectSnapshotIntent(), чтобы получитьIntentдля запуска стандартного интерфейса выбора сохраненных игр. - Вызовите
startActivityForResult()и передайте в негоIntent. Если вызов будет успешным, игра отобразит интерфейс выбора сохраненной игры вместе с указанными вами параметрами.
Вот пример того, как запустить стандартный интерфейс выбора сохраненных игр:
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); } }); }
Если игрок выбирает создание нового сохранённого файла или загрузку существующего, пользовательский интерфейс отправляет запрос в Google Play Games Services. В случае успешного запроса Google Play Games Services возвращает информацию для создания или восстановления сохранённого файла через функцию обратного вызова 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(). Затем получите объектSnapshotиз результата задачи, вызвав методSnapshotsClient.DataOrConflict.getData(). - Получите экземпляр
SnapshotContentsс помощьюSnapshotsClient.SnapshotConflict. - Вызовите
SnapshotContents.writeBytes()для сохранения данных игрока в байтовом формате. - После записи всех изменений вызовите метод
SnapshotsClient.commitAndClose(), чтобы отправить изменения на серверы Google. В вызове метода ваша игра может дополнительно указать Google Play Games Services, как отображать сохраненную игру игрокам. Эта информация представлена в объекте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 = Games.getSnapshotsClient(this, GoogleSignIn.getLastSignedInAccount(this)); // Commit the operation return snapshotsClient.commitAndClose(snapshot, metadataChange); }
Если устройство игрока не подключено к сети, когда ваше приложение вызывает SnapshotsClient.commitAndClose() , Google Play Games Services сохраняет данные игры локально на устройстве. После повторного подключения устройства Google Play Games Services синхронизирует локально кэшированные изменения игры с серверами Google.
Загрузить сохраненные игры
Чтобы восстановить сохраненные игры для текущего пользователя:
- Асинхронно откройте снимок с помощью метода
SnapshotsClient.open(). Затем получите объектSnapshotиз результата задачи, вызвав методSnapshotsClient.DataOrConflict.getData(). В качестве альтернативы, ваша игра также может получить конкретный снимок через пользовательский интерфейс выбора сохраненных игр, как описано в разделе «Отображение сохраненных игр» . - Получите экземпляр
SnapshotContentsс помощьюSnapshotsClient.SnapshotConflict. - Вызовите метод
SnapshotContents.readFully()для чтения содержимого снимка.
Следующий фрагмент кода показывает, как можно загрузить определённое сохранённое изображение игры:
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. // ... } }); }
Обработка конфликтов сохранений игры
При использовании API снимков в вашей игре несколько устройств могут одновременно выполнять операции чтения и записи в одно и то же сохраненное изображение. В случае временной потери сетевого соединения и последующего его восстановления может возникнуть конфликт данных, в результате которого сохраненное изображение на локальном устройстве игрока окажется несинхронизированным с удаленной версией, хранящейся на серверах Google.
API снимков предоставляет механизм разрешения конфликтов, который отображает оба набора конфликтующих сохраненных игр во время чтения и позволяет реализовать стратегию разрешения, подходящую для вашей игры.
Когда Google Play Games Services обнаруживает конфликт данных, метод SnapshotsClient.DataOrConflict.isConflict() возвращает значение true В этом случае класс SnapshotsClient.SnapshotConflict предоставляет две версии сохраненной игры:
- Версия сервера : Самая актуальная версия, известная Google Play Games Services как подходящая для устройства игрока;
- Локальная версия : Измененная версия, обнаруженная на одном из устройств игрока, содержащая конфликтующее содержимое или метаданные. Она может отличаться от версии, которую вы пытались сохранить.
Вашей игре предстоит решить, как разрешить конфликт, выбрав одну из предложенных версий или объединив данные двух сохраненных версий игры.
Для обнаружения и разрешения конфликтов сохранений игры:
- Вызовите метод
SnapshotsClient.open(). Результатом выполнения задачи станет объект классаSnapshotsClient.DataOrConflict. - Вызовите метод
SnapshotsClient.DataOrConflict.isConflict(). Если результат истинный, значит, у вас есть конфликт, который необходимо разрешить. - Вызовите
SnapshotsClient.DataOrConflict.getConflict()чтобы получить экземплярSnapshotsClient.snapshotConflict. - Вызовите
SnapshotsClient.SnapshotConflict.getConflictId()чтобы получить идентификатор конфликта, который однозначно идентифицирует обнаруженный конфликт. Вашей игре потребуется это значение для последующей отправки запроса на разрешение конфликта. - Вызовите
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 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"); } } }); }
Изменяйте сохраненные игры для разрешения конфликтов.
Если вы хотите объединить данные из нескольких сохраненных игр или изменить существующий 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на сервере и попытается открыть объект Snapshot на вашем локальном устройстве.- Если возникает конфликт,
SnapshotsClient.DataOrConflict.isConflict()возвращаетtrue. В этом случае ваша игра должна вернуться к шагу 2 и повторять шаги по изменению снимка до тех пор, пока конфликты не будут разрешены. - Если конфликта нет,
SnapshotsClient.DataOrConflict.isConflict()возвращаетfalse, и объектSnapshotстановится доступным для изменения вашей игрой.
- Если возникает конфликт,