Android Oyunlar'da Kaydedilmiş Oyunlar Desteği

Bu kılavuzda, kayıtlı oyun oyununun Google Play oyun hizmetleri tarafından sağlanan anlık görüntü API'sını kullanarak nasıl uygulanacağı gösterilmektedir. API'ler com.google.android.gms.games.snapshot ve com.google.android.gms.games paketlerinde bulunabilir.

Başlamadan önce

Henüz yapmadıysanız Kayıtlı Oyunlar oyun kavramlarını incelemeniz faydalı olabilir.

Anlık görüntü istemcisi alınıyor

Anlık görüntüler API'sini kullanmaya başlamak için oyununuzun öncelikle bir SnapshotsClient nesnesi edinmesi gerekir. Bunu yapmak için Games.getSnapshotsClient() yöntemini çağırıp etkinliği iletebilirsiniz.

Drive kapsamını belirtme

Snapshot API'si, kayıtlı oyun depolama alanı için Google Drive API'yi kullanır. Drive API'ye erişmek için uygulamanızda, Google ile oturum açma istemcisi oluşturulurken Drive.SCOPE_APPFOLDER kapsamı belirtilmelidir.

Aşağıda, oturum açma etkinliğiniz için onResume() yönteminde bunu nasıl yapacağınıza dair bir örnek verilmiştir:


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

Kayıtlı oyunlar gösteriliyor

Oyununuzun ilerlemesini kaydetme veya geri yükleme seçeneği sunan oyunlar API'si, anlık görüntü API'siyle entegre edilebilir. Oyununuzda, belirtilen kaydetme/geri yükleme noktalarında böyle bir seçenek gösterilebilir veya oyuncuların istedikleri zaman ilerlemelerini kaydetmelerine veya geri yüklemelerine izin verilebilir.

Oyuncular oyununuzda kaydet/geri yükle seçeneğini belirledikten sonra, oyununuz, isteğe bağlı olarak oyunculardan yeni bir kaydedilmiş oyunla ilgili bilgileri girmelerini veya geri yüklenecek mevcut bir kayıtlı oyunları seçmelerini isteyen bir ekran açabilir.

Geliştirmenizi kolaylaştırmak için anlık görüntüler API'si, varsayılan olarak kullanabileceğiniz kayıtlı bir oyun seçimi kullanıcı arayüzü sağlar. Kayıtlı oyunlar seçimi kullanıcı arayüzü, yeni bir kayıtlı oyun oluşturma, mevcut kayıtlı oyunlarla ilgili ayrıntıları görüntüleme ve önceki kayıtlı oyunları yükleme.

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

  1. Varsayılan kaydedilmiş oyun seçimi kullanıcı arayüzünü başlatmak üzere Intent almak için SnapshotsClient.getSelectSnapshotIntent() numaralı telefonu arayın.
  2. startActivityForResult() numaralı telefonu arayın ve söz konusu Intent numarasını iletin. Görüşme başarılı olursa oyun, belirttiğiniz seçeneklerle birlikte kayıtlı oyun seçimi kullanıcı arayüzünü gösterir.

Varsayılan kaydedilmiş oyun seçimi kullanıcı arayüzünün nasıl başlatılacağına dair bir örneği aşağıda bulabilirsiniz:

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

Oyuncu yeni bir kayıtlı oyun oluşturmayı veya mevcut bir kayıtlı oyunu yüklemeyi seçerse kullanıcı arayüzü, Google Play oyun hizmetlerine bir istek gönderir. İstek başarılı olursa Google Play oyun hizmetleri, kayıtlı oyunu oluşturmak veya geri yüklemek için onActivityResult() geri çağırması yoluyla bilgi döndürür. Oyununuz, bu geri çağırmayı geçersiz kılabilir ve istek sırasında hata olup olmadığını kontrol edebilir.

Aşağıdaki kod snippet'i, onActivityResult() özelliğinin örnek uygulamasını göstermektedir:

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

Kaydedilmiş bir oyuna içerik depolamak için:

  1. SnapshotsClient.open() aracılığıyla eşzamansız olarak bir anlık görüntü açın. Ardından, SnapshotsClient.DataOrConflict.getData() yöntemini çağırarak görevin sonucundan Snapshot nesnesini alın.
  2. SnapshotsClient.SnapshotConflict aracılığıyla SnapshotContents örneği alın.
  3. Oynatıcının verilerini bayt biçiminde depolamak için SnapshotContents.writeBytes() yöntemini çağırın.
  4. Tüm değişikliklerinizi yazdıktan sonra değişikliklerinizi Google sunucularına göndermek için SnapshotsClient.commitAndClose() numaralı telefonu arayın. Yöntem çağrısında oyununuz, isteğe bağlı olarak Google Play oyun hizmetlerine oyunculara bu kayıtlı oyunu nasıl sunacağını bildirmek için ek bilgiler sağlayabilir. Bu bilgiler, oyununuzun SnapshotMetadataChange.Builder kullanarak oluşturduğu bir SnapshotMetaDataChange nesnesinde sunulur.

Aşağıdaki snippet, oyununuzun kayıtlı bir oyunda nasıl değişiklik yapabileceğini göstermektedir:

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

Uygulamanız SnapshotsClient.commitAndClose() çağırırken oyuncunun cihazı bir ağa bağlı değilse Google Play oyun hizmetleri, kayıtlı oyun verilerini cihazda yerel olarak depolar. Google Play oyun hizmetleri, cihaz yeniden bağlandıktan sonra yerel olarak önbelleğe alınan kaydedilmiş oyun değişikliklerini Google sunucularıyla senkronize eder.

Kaydedilmiş oyunlar yükleniyor

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

  1. SnapshotsClient.open() aracılığıyla eşzamansız olarak bir anlık görüntü açın. Ardından, SnapshotsClient.DataOrConflict.getData() yöntemini çağırarak görevin sonucundan Snapshot nesnesini alın. Alternatif olarak oyununuz, kayıtlı oyunlar seçim kullanıcı arayüzü aracılığıyla Kaydedilen Oyunları Görüntüleme bölümünde açıklandığı gibi belirli bir anlık görüntüyü 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() numaralı telefonu arayı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 =
      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.
          // ...
        }
      });
}

Kaydedilen oyun çakışmalarını işleme

Oyununuzda anlık görüntüler API'sini kullanırken birden fazla cihaz aynı kayıtlı oyunda okuma ve yazma işlemi gerçekleştirebilir. Bir cihazın ağ bağlantısını geçici olarak kaybetmesi ve daha sonra yeniden bağlanması, oyuncunun yerel cihazında depolanan kayıtlı oyunun Google sunucularında depolanan uzak sürümle senkronize olmamasına neden olabilecek veri çakışmalarına neden olabilir.

Snapshot API, her iki okuma sırasında hem çakışan kayıtlı oyunları sunan hem de oyununuz için uygun olan bir çözüm stratejisi uygulamanıza olanak tanıyan bir anlaşmazlık çözümü mekanizması sunar.

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

  • Sunucu sürümü: Google Play oyun hizmetleri tarafından oyuncunun cihazı için doğru olduğunun bilindiği en güncel sürüm
  • Yerel sürüm: Oyuncunun cihazlarından birinde çakışan içerik veya meta veri içeren değiştirilmiş bir sürüm. Bu sürüm, 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ışmanın nasıl çözüleceğine karar vermelidir.

Kaydedilmiş oyun çakışmalarını tespit etmek ve çö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 çözeceğiniz bir çakışma vardır.
  3. Bir SnaphotsClient.snapshotConflict örneği almak için SnapshotsClient.DataOrConflict.getConflict() yöntemini çağırın.
  4. Algılanan çatışmayı benzersiz şekilde tanımlayan çakışma kimliğini almak için SnapshotsClient.SnapshotConflict.getConflictId() numaralı telefonu arayın. Daha sonra anlaşmazlık çözümü isteği göndermek için oyununuzun 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ü almak için SnapshotsClient.SnapshotConflict.getSnapshot() numaralı telefonu arayın.
  7. Kaydedilen oyun çakışmasını çözmek için nihai sürüm olarak sunucuya kaydetmek istediğiniz bir sürümü seçin ve bunu SnapshotsClient.resolveConflict() yöntemine iletin.

Aşağıdaki snippet'te, kaydedilen son oyunu kaydetmek için son oyunun seçili oyununı seçerek oyununuzun kaydedilmiş bir oyun çakışmasını nasıl yönetebileceğiyle ilgili bir örnek gösterilmektedir:


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

Kaydedilen oyunların anlaşmazlık çözümü için değiştirilmesi

Birden fazla kayıtlı oyundan alınan verileri birleştirmek veya çözümlenmiş nihai sürüm olarak sunucuya kaydetmek için mevcut bir Snapshot üzerinde değişiklik yapmak 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() yöntemini çağırın.
  3. SnapshotsClient.SnapshotConflict.getConflictingSnapshot() ve SnapshotsClient.SnapshotConflict.getSnapshot() kaynaklı verileri, önceki adımda elde edilen SnapshotContents nesnesinde birleştirin.
  4. İsteğe bağlı olarak, meta veri alanlarında herhangi bir değişiklik olması halinde 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(), ikinci ve üçüncü bağımsız değişkenler olarak daha önce değiştirdiğiniz SnapshotMetadataChange ve SnapshotContents nesneleri iletin.
  6. SnapshotsClient.resolveConflict() çağrısı başarılı olursa API, Snapshot nesnesini sunucuya depolar ve anlık görüntüyü nesnesini yerel cihazınızda açmayı dener.