Android oyunlarında kaydedilmiş oyunlar desteği

Bu kılavuzda, Google Play Games Hizmetleri tarafından sağlanan anlık görüntüler API'sini kullanarak kayıtlı oyunları nasıl uygulayacağınız gösterilmektedir. API'leri com.google.android.gms.games.snapshot ve com.google.android.gms.games paketlerinde bulabilirsiniz.

Başlamadan önce

Henüz yapmadıysanız Kayıtlı Oyunlar oyun kavramlarını incelemenizi öneririz.

Anlık görüntü istemcisini edinme

Snapshots API'yi kullanmaya başlamak için oyununuzun önce bir SnapshotsClient nesnesi alması gerekir. Bunu, Games.getSnapshotsClient() yöntemini çağırıp etkinliği ve mevcut oynatıcı için GoogleSignInAccount öğesini ileterek yapabilirsiniz. Oyuncu hesabı bilgilerini nasıl alacağınızı öğrenmek için Android Oyunlar'da oturum açma başlıklı makaleyi inceleyin.

Drive kapsamını belirtin

Snapshots API, kayıtlı oyunların depolanması için Google Drive API'yi kullanır. Drive API'ye erişmek için uygulamanızın, Google ile oturum açma istemcisini oluştururken Drive.SCOPE_APPFOLDER kapsamını belirtmesi gerekir.

Giriş etkinliğinizin onResume() yönteminde bunu nasıl yapacağınıza dair bir örnek aşağıda verilmiştir:

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

Kaydedilen oyunları görüntüleme

Oyununuzda oyuncuların ilerleme durumlarını kaydetme veya geri yükleme seçeneği sunduğunuz her yerde anlık görüntü API'sini entegre edebilirsiniz. Oyununuz, belirlenen kayıt/geri yükleme noktalarında bu tür bir seçenek gösterebilir veya oyuncuların ilerleme durumunu istedikleri zaman kaydetmelerine ya da geri yüklemelerine izin verebilir.

Oyuncular oyununuzda kaydetme/geri yükleme seçeneğini belirledikten sonra oyununuz isteğe bağlı olarak oyuncuların yeni bir kayıtlı oyunla ilgili bilgi girmesini veya geri yüklenecek mevcut bir kayıtlı oyunu seçmesini isteyen bir ekran gösterebilir.

Geliştirme sürecinizi basitleştirmek için anlık görüntüler API'si, hazır olarak kullanabileceğiniz varsayılan bir kayıtlı oyun seçimi kullanıcı arayüzü (UI) sağlar. Kaydedilmiş oyunlar seçim kullanıcı arayüzü, oyuncuların yeni kayıtlı oyun oluşturmasına, mevcut kayıtlı oyunlarla ilgili ayrıntıları görüntülemesine ve önceki kayıtlı oyunları yüklemesine olanak tanır.

Varsayılan Kayıtlı Oyunlar kullanıcı arayüzünü başlatmak için:

  1. Varsayılan kayıtlı oyunlar seçim kullanıcı arayüzünü başlatmak için SnapshotsClient.getSelectSnapshotIntent() numaralı telefonu arayıp Intent kodunu alın.
  2. startActivityForResult() numarasını arayın ve Intent değerini iletin. Çağrı başarılı olursa oyun, kayıtlı oyun seçim kullanıcı arayüzünü ve belirttiğiniz seçenekleri gösterir.

Varsayılan kayıtlı oyunlar seçim kullanıcı arayüzünün nasıl başlatılacağına dair bir örnek aşağıda verilmiştir:

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

Oyuncu yeni bir kayıtlı oyun oluşturmayı veya mevcut bir kayıtlı oyunu yüklemeyi seçerse kullanıcı arayüzü Google Play Games Hizmetleri'ne bir istek gönderir. İstek başarılı olursa Google Play Games Hizmetleri, onActivityResult() geri çağırma işlevi aracılığıyla kayıtlı oyunu oluşturmak veya geri yüklemek için bilgileri döndürür. Oyununuz, istek sırasında herhangi bir hata olup olmadığını kontrol etmek için bu geri çağırmayı geçersiz kılabilir.

Aşağıdaki kod snippet'inde onActivityResult() için örnek bir uygulama gösterilmektedir:

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

Kaydedilmiş oyunları yazma

Kayıtlı bir oyuna içerik kaydetmek için:

  1. SnapshotsClient.open() aracılığıyla bir anlık görüntüyü eşzamansız olarak açma. Ardından, SnapshotsClient.DataOrConflict.getData() işlevini çağırarak görev sonucundan Snapshot nesnesini alın.
  2. SnapshotsClient.SnapshotConflict aracılığıyla bir SnapshotContents örneği alın.
  3. Oynatıcının verilerini bayt biçiminde depolamak için SnapshotContents.writeBytes() işlevini çağırın.
  4. Tüm değişiklikleriniz yazıldıktan sonra, değişikliklerinizi Google'ın sunucularına göndermek için SnapshotsClient.commitAndClose() numarasını arayın. Oyununuz, yöntem çağrısında isteğe bağlı olarak Google Play Games Hizmetleri'ne bu kayıtlı oyunu oyunculara nasıl sunacağını bildirmek için ek bilgiler sağlayabilir. Bu bilgiler, oyununuzun SnapshotMetadataChange.Builder kullanarak oluşturduğu bir SnapshotMetaDataChange sınıfında temsil edilir.

Aşağıdaki snippet'te, oyununuzun kayıtlı bir oyuna nasıl değişiklik kaydedebileceği gösterilmektedir:

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

Uygulamanız SnapshotsClient.commitAndClose() çağrısını yaptığında oyuncunun cihazı ağa bağlı değilse Google Play Games Hizmetleri, kayıtlı oyun verilerini cihazda yerel olarak depolar. Cihaz yeniden bağlandığında Google Play Games Hizmetleri, yerel olarak önbelleğe alınan kaydedilmiş oyun değişikliklerini Google'ın sunucularıyla senkronize eder.

Kayıtlı oyunları yükleme

Şu anda oturum açmış olan oyuncunun kayıtlı oyunlarını almak için:

  1. SnapshotsClient.open() aracılığıyla bir anlık görüntüyü eşzamansız olarak açma. Ardından, SnapshotsClient.DataOrConflict.getData() işlevini çağırarak görev sonucundan Snapshot nesnesini alın. Alternatif olarak oyununuz, Kayıtlı oyunları görüntüleme bölümünde açıklandığı gibi kayıtlı oyunlar seçim kullanıcı arayüzü üzerinden belirli bir anlık görüntü de alabilir.
  2. SnapshotsClient.SnapshotConflict aracılığıyla SnapshotContents örneğini alın.
  3. Anlık görüntünün içeriğini okumak için SnapshotContents.readFully() işlevini çağırın.

Aşağıdaki snippet'te, belirli bir kayıtlı oyunu nasıl yükleyebileceğiniz gösterilmektedir:

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

Kaydedilmiş oyun çakışmalarını yönetme

Oyununuzda anlık görüntüler API'si kullanılırken birden fazla cihazın aynı kayıtlı oyunda okuma ve yazma yapması mümkündür. Bir cihazın ağ bağlantısı geçici olarak kesilip daha sonra yeniden bağlanırsa oyuncunun yerel cihazında depolanan kayıtlı oyunun Google sunucularında depolanan uzak sürümle senkronize olmamasına neden olabilecek veri çakışmaları yaşanabilir.

Snapshots API, hem çelişen kayıtlı oyun gruplarını okuma sırasında sunan hem de oyununuza uygun bir çözüm stratejisi uygulamanıza olanak tanıyan bir anlaşmazlık çözüm mekanizması sağlar.

Google Play Games Hizmetleri bir veri çakışması algıladığında SnapshotsClient.DataOrConflict.isConflict() yöntemi true değerini döndürür. Bu durumda SnapshotsClient.SnapshotConflict sınıfı, kayıtlı oyunun iki sürümünü sağlar:

  • Sunucu sürümü: Google Play Games Hizmetleri'nin oyuncunun cihazı için doğru olduğunu bildiği en güncel sürüm ve
  • Yerel sürüm: Oynatıcının cihazlarından birinde algılanan, çakışan içerik veya meta veriler içeren değiştirilmiş sürüm. Bu, kaydetmeye çalıştığınız sürümle aynı olmayabilir.

Oyununuz, sağlanan sürümlerden birini seçerek veya kayıtlı iki oyun sürümünün verilerini birleştirerek çakışmayı nasıl çözeceğine karar vermelidir.

Kayıtlı oyun çakışmalarını tespit edip çözmek için:

  1. SnapshotsClient.open() numaralı telefonu arayın. Görev sonucu bir SnapshotsClient.DataOrConflict sınıfı içeriyor.
  2. SnapshotsClient.DataOrConflict.isConflict() yöntemini çağırın. Sonuç doğruysa çözmeniz gereken bir çakışma vardır.
  3. SnaphotsClient.snapshotConflict örneği almak için SnapshotsClient.DataOrConflict.getConflict() numaralı telefonu arayın.
  4. Tespit edilen çakışmayı benzersiz şekilde tanımlayan çakışma kimliğini almak için SnapshotsClient.SnapshotConflict.getConflictId() işlevini çağırın. Oyununuzun daha sonra bir çakışma çözümü isteği göndermesi için bu değere ihtiyacı vardır.
  5. Yerel sürümü almak için SnapshotsClient.SnapshotConflict.getConflictingSnapshot() numaralı telefonu arayın.
  6. Sunucu sürümünü öğrenmek için SnapshotsClient.SnapshotConflict.getSnapshot() numaralı telefonu arayın.
  7. Kayıtlı oyun çakışmasını çözmek için sunucuya nihai sürüm olarak kaydetmek istediğiniz bir sürümü seçin ve SnapshotsClient.resolveConflict() yöntemine iletin.

Aşağıdaki snippet'te, oyununuzun kayıtlı oyun çakışmasını nasıl ele alabileceğine dair bir örnek gösterilmektedir. Bu örnekte, kaydedilecek nihai sürüm olarak en son değiştirilen kayıtlı oyun seçilmektedir:

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

Çakışan oyunları çözümlemek için değiştirme

Birden fazla kayıtlı oyundaki verileri birleştirmek veya mevcut bir Snapshot dosyasını, çözüldükten sonra nihai sürüm olarak sunucuya kaydetmek için değiştirmek istiyorsanız aşağıdaki adımları uygulayın:

  1. SnapshotsClient.open() numaralı telefonu arayın .
  2. Yeni bir SnapshotContents nesnesi almak için SnapshotsClient.SnapshotConflict.getResolutionSnapshotsContent() işlevini çağırın.
  3. SnapshotsClient.SnapshotConflict.getConflictingSnapshot() ve SnapshotsClient.SnapshotConflict.getSnapshot()'teki verileri önceki adımdaki SnapshotContents nesnesi ile birleştirin.
  4. İsteğe bağlı olarak, meta veri alanlarında değişiklik varsa bir SnapshotMetadataChange örneği oluşturun.
  5. SnapshotsClient.resolveConflict() numaralı telefonu arayın. Yöntem çağrınızda, ilk bağımsız değişken olarak SnapshotsClient.SnapshotConflict.getConflictId(), daha önce değiştirdiğiniz SnapshotMetadataChange ve SnapshotContents nesnelerini ise sırasıyla ikinci ve üçüncü bağımsız değişken olarak iletin.
  6. SnapshotsClient.resolveConflict() çağrısı başarılı olursa API, Snapshot nesnesini sunucuda depolar ve Snapshot nesnesini yerel cihazınızda açmaya çalışır.