پشتیبانی از بازی های ذخیره شده در بازی های اندروید

این راهنما به شما نشان می دهد که چگونه بازی های ذخیره شده را با استفاده از Snapshots API ارائه شده توسط سرویس های بازی های Google Play پیاده سازی کنید. APIها را می‌توانید در بسته‌های com.google.android.gms.games.snapshot و com.google.android.gms.games پیدا کنید.

قبل از اینکه شروع کنی

اگر قبلاً این کار را نکرده‌اید، مرور مفاهیم بازی‌های ذخیره‌شده برایتان مفید است.

دریافت سرویس گیرنده عکس های فوری

برای شروع استفاده از snapshots API، بازی شما باید ابتدا یک شی SnapshotsClient داشته باشد. می توانید این کار را با فراخوانی متد Games.getSnapshotsClient() و عبور از اکتیویتی انجام دهید.

تعیین محدوده Drive

Snapshots API برای ذخیره‌سازی بازی‌ها به API Google Drive متکی است. برای دسترسی به Drive API، برنامه شما باید محدوده Drive.SCOPE_APPFOLDER را هنگام ساخت کلاینت ورود به سیستم Google مشخص کند.

در اینجا مثالی از نحوه انجام این کار در متد 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 عکس‌های فوری را در هر جایی که بازی شما گزینه ذخیره یا بازیابی پیشرفت را در اختیار بازیکنان قرار می‌دهد، ادغام کنید. بازی شما ممکن است چنین گزینه ای را در نقاط ذخیره/بازیابی تعیین شده نمایش دهد یا به بازیکنان اجازه دهد پیشرفت را در هر زمانی ذخیره یا بازیابی کنند.

هنگامی که بازیکنان گزینه ذخیره/بازیابی را در بازی شما انتخاب می کنند، بازی شما به صورت اختیاری می تواند صفحه ای را نمایش دهد که از بازیکنان می خواهد اطلاعات یک بازی ذخیره شده جدید را وارد کنند یا یک بازی ذخیره شده موجود را برای بازیابی انتخاب کنند.

برای ساده‌سازی توسعه‌تان، Snapshots API یک رابط کاربری (UI) انتخاب بازی‌های ذخیره‌شده پیش‌فرض ارائه می‌کند که می‌توانید خارج از جعبه از آن استفاده کنید. رابط کاربری انتخاب بازی‌های ذخیره‌شده به بازیکنان اجازه می‌دهد یک بازی ذخیره‌شده جدید ایجاد کنند، جزئیات بازی‌های ذخیره‌شده موجود را مشاهده کنند و بازی‌های ذخیره‌شده قبلی را بارگیری کنند.

برای راه‌اندازی رابط کاربری پیش‌فرض Saved Games:

  1. با SnapshotsClient.getSelectSnapshotIntent() تماس بگیرید تا یک Intent برای راه اندازی رابط کاربری پیش فرض انتخاب بازی های ذخیره شده دریافت کنید.
  2. startActivityForResult() را فراخوانی کنید و آن Intent ارسال کنید. در صورت موفقیت آمیز بودن تماس، بازی 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);
    }
  });
}

اگر بازیکن انتخاب کند که یک بازی ذخیره شده جدید ایجاد کند یا یک بازی ذخیره شده موجود را بارگیری کند، رابط کاربری درخواستی را به خدمات بازی های 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
      // ...
    }
  }
}

نوشتن بازی های ذخیره شده

برای ذخیره محتوا در یک بازی ذخیره شده:

  1. به صورت ناهمزمان یک عکس فوری از طریق SnapshotsClient.open() باز کنید. سپس، با فراخوانی SnapshotsClient.DataOrConflict.getData() ، شی Snapshot را از نتیجه کار بازیابی کنید.
  2. یک نمونه SnapshotContents را از طریق SnapshotsClient.SnapshotConflict بازیابی کنید.
  3. برای ذخیره داده های پخش کننده در قالب بایت، SnapshotContents.writeBytes() را فراخوانی کنید.
  4. هنگامی که تمام تغییرات شما نوشته شد، با SnapshotsClient.commitAndClose() تماس بگیرید تا تغییرات خود را به سرورهای Google ارسال کنید. در فراخوانی روش، بازی شما به صورت اختیاری می‌تواند اطلاعات بیشتری را ارائه کند تا به خدمات بازی‌های Google Play بگوید چگونه این بازی ذخیره شده را به بازیکنان ارائه دهند. این اطلاعات در یک شی 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 =
      PlayGames.getSnapshotsClient(this);

  // Commit the operation
  return snapshotsClient.commitAndClose(snapshot, metadataChange);
}

اگر هنگامی که برنامه شما با SnapshotsClient.commitAndClose() تماس می گیرد، دستگاه پخش کننده به شبکه متصل نیست، خدمات بازی های Google Play داده های ذخیره شده بازی را به صورت محلی در دستگاه ذخیره می کند. پس از اتصال مجدد دستگاه، سرویس‌های بازی‌های Google Play تغییرات ذخیره‌شده بازی در حافظه پنهان محلی را با سرورهای Google همگام‌سازی می‌کند.

بارگیری بازی های ذخیره شده

برای بازیابی بازی های ذخیره شده برای بازیکنی که در حال حاضر به سیستم وارد شده است:

  1. به صورت ناهمزمان یک عکس فوری از طریق SnapshotsClient.open() باز کنید. سپس، با فراخوانی SnapshotsClient.DataOrConflict.getData() ، شی Snapshot را از نتیجه کار بازیابی کنید. از طرف دیگر، بازی شما همچنین می‌تواند یک عکس فوری خاص را از طریق رابط کاربری انتخاب بازی‌های ذخیره‌شده، همانطور که در نمایش بازی‌های ذخیره شده توضیح داده شده است، بازیابی کند.
  2. نمونه SnapshotContents را از طریق SnapshotsClient.SnapshotConflict بازیابی کنید.
  3. برای خواندن محتویات عکس فوری 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.
          // ...
        }
      });
}

مدیریت درگیری های ذخیره شده بازی

هنگام استفاده از Snapshots API در بازی خود، این امکان برای چندین دستگاه وجود دارد که روی یک بازی ذخیره شده خواندن و نوشتن انجام دهند. در صورتی که دستگاهی موقتاً اتصال شبکه خود را قطع کند و بعداً دوباره وصل شود، ممکن است باعث تضاد داده‌ها شود که به موجب آن بازی ذخیره شده در دستگاه محلی بازیکن با نسخه راه دور ذخیره شده در سرورهای Google هماهنگ نیست.

Snapshots API مکانیزم حل تعارض را ارائه می دهد که هر دو مجموعه بازی های ذخیره شده متناقض را در زمان خواندن ارائه می دهد و به شما امکان می دهد استراتژی حل و فصل مناسب برای بازی خود را پیاده سازی کنید.

هنگامی که سرویس‌های بازی‌های Google Play تضاد داده را شناسایی می‌کنند، متد SnapshotsClient.DataOrConflict.isConflict() مقدار true را برمی‌گرداند در این رویداد، کلاس SnapshotsClient.SnapshotConflict دو نسخه از بازی ذخیره‌شده را ارائه می‌کند:

  • نسخه سرور : به‌روزترین نسخه شناخته شده توسط سرویس‌های بازی‌های Google Play که برای دستگاه بازیکن دقیق است. و
  • نسخه محلی : نسخه تغییر یافته‌ای که در یکی از دستگاه‌های پخش‌کننده شناسایی می‌شود که حاوی محتوا یا ابرداده متناقض است. ممکن است این نسخه مشابه نسخه ای نباشد که سعی کردید ذخیره کنید.

بازی شما باید تصمیم بگیرد که چگونه با انتخاب یکی از نسخه های ارائه شده یا ادغام داده های دو نسخه ذخیره شده بازی، تضاد را حل کند.

برای شناسایی و حل تداخل های ذخیره شده بازی:

  1. SnapshotsClient.open() را فراخوانی کنید. نتیجه کار شامل یک کلاس SnapshotsClient.DataOrConflict است.
  2. متد SnapshotsClient.DataOrConflict.isConflict() را فراخوانی کنید. اگر نتیجه درست باشد، درگیری دارید که باید حل کنید.
  3. SnapshotsClient.DataOrConflict.getConflict() را برای بازیابی یک نمونه SnaphotsClient.snapshotConflict فراخوانی کنید.
  4. SnapshotsClient.SnapshotConflict.getConflictId() را فراخوانی کنید تا شناسه تضاد را که به طور منحصر به فرد تداخل شناسایی شده را شناسایی می کند، بازیابی کنید. بازی شما برای ارسال درخواست حل تداخل بعداً به این مقدار نیاز دارد.
  5. با SnapshotsClient.SnapshotConflict.getConflictingSnapshot() تماس بگیرید تا نسخه محلی را دریافت کنید.
  6. برای دریافت نسخه سرور، با SnapshotsClient.SnapshotConflict.getSnapshot() تماس بگیرید.
  7. برای رفع تضاد بازی ذخیره شده، نسخه ای را که می خواهید به عنوان نسخه نهایی در سرور ذخیره کنید، انتخاب کنید و آن را به متد 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 موجود را تغییر دهید تا به عنوان نسخه نهایی حل‌شده در سرور ذخیره شود، این مراحل را دنبال کنید:

  1. SnapshotsClient.open() را فراخوانی کنید.
  2. با SnapshotsClient.SnapshotConflict.getResolutionSnapshotsContent() تماس بگیرید تا یک شیء جدید SnapshotContents دریافت کنید.
  3. داده های SnapshotsClient.SnapshotConflict.getConflictingSnapshot() و SnapshotsClient.SnapshotConflict.getSnapshot() را در شیء SnapshotContents از مرحله قبل ادغام کنید.
  4. در صورت وجود هرگونه تغییر در فیلدهای ابرداده، به صورت اختیاری، یک نمونه SnapshotMetadataChange ایجاد کنید.
  5. SnapshotsClient.resolveConflict() را فراخوانی کنید. در فراخوانی متد خود، SnapshotsClient.SnapshotConflict.getConflictId() را به عنوان اولین آرگومان و اشیاء SnapshotMetadataChange و SnapshotContents را که قبلاً تغییر داده اید به ترتیب به عنوان آرگومان دوم و سوم ارسال کنید.
  6. اگر فراخوانی SnapshotsClient.resolveConflict() موفقیت آمیز باشد، API شی Snapshot را در سرور ذخیره می کند و سعی می کند شی Snapshot را در دستگاه محلی شما باز کند.
    • اگر تداخلی وجود داشته باشد، SnapshotsClient.DataOrConflict.isConflict() true را برمی گرداند. در این حالت، بازی شما باید به مرحله 2 برگردد و مراحل را برای اصلاح عکس فوری تا رفع تضادها تکرار کنید.
    • اگر تداخلی وجود نداشته باشد، SnapshotsClient.DataOrConflict.isConflict() false را برمی گرداند و شی Snapshot برای تغییر بازی شما باز است.