دعم خدمة "حفظ التقدم في الألعاب" في ألعاب Android

يوضِّح لك هذا الدليل كيفية تطبيق ميزة "الألعاب المحفوظة" باستخدام لقطات شاشة من واجهة برمجة التطبيقات التي توفّرها "خدمات ألعاب Google Play". يمكن العثور على واجهات برمجة التطبيقات في com.google.android.gms.games.snapshot وcom.google.android.gms.games حزم.

قبل البدء

إذا لم تكن قد فعلت ذلك بالفعل، فقد تجد أنه من المفيد مراجعة مفاهيم ألعاب محفوظة في الألعاب:

جارٍ الحصول على برنامج اللقطات

لبدء استخدام واجهة برمجة تطبيقات لقطات الشاشة، يجب أن تحصل لعبتك أولاً على SnapshotsClient. يمكنك القيام بذلك عن طريق استدعاء Games.getSnapshotsClient() وتمرير النشاط وGoogleSignInAccount للّاعب الحالي. للتعرّف على كيفية واسترداد معلومات حساب اللاعب، اطلع على سجِّل الدخول إلى "ألعاب Android".

تحديد نطاق Drive

تعتمد واجهة لقطات الشاشة على Google Drive API لتوفير مساحة تخزين الألعاب المحفوظة. إلى الوصول إلى Drive API، يجب أن يحدّد تطبيقك 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
          }
        }
      });
}

جارٍ عرض الألعاب المحفوظة

يمكنك دمج واجهة برمجة تطبيقات اللقطات في أي مكان توفّر فيه لعبتك للّاعبين خيار حفظ مستوى تقدمه أو استعادته. قد تعرض لعبتك عند تحديد نقاط معيّنة لحفظها أو استعادتها أو السماح للّاعبين بحفظ الملفات أو استعادتها التقدم في أي وقت.

بعد أن ينقر اللاعبون على خيار الحفظ/الاستعادة في لعبتك، يمكن للّعبة يمكنك اختياريًا إظهار شاشة تطلب من اللاعبين إدخال معلومات عن شاشة جديدة محفوظة لعبة أو اختيار لعبة محفوظة حاليًا لاستعادتها.

لتبسيط عملية التطوير، توفّر واجهة Lookers API مستخدمًا تلقائيًا لاختيار الألعاب المحفوظة واجهة المستخدم (UI) التي يمكنك استخدامها بطريقة مبتكرة. تسمح واجهة المستخدم الخاصة بالألعاب المحفوظة للاعبين إنشاء لعبة جديدة محفوظة وعرض تفاصيل عن الألعاب المحفوظة الحالية وتحميل الألعاب المحفوظة السابقة

لتشغيل واجهة المستخدم التلقائية للألعاب المحفوظة:

  1. يمكنك الاتصال بالرقم SnapshotsClient.getSelectSnapshotIntent() للحصول على Intent لإطلاق الإعداد التلقائي واجهة مستخدم اختيار الألعاب المحفوظة
  2. الاتصال بالرقم 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". إذا تم الطلب بنجاح، تعرض "خدمات ألعاب 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() بعد ذلك، يمكنك استرداد الكائن Snapshot. من نتيجة المهمة من خلال استدعاء SnapshotsClient.DataOrConflict.getData().
  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 =
      Games.getSnapshotsClient(this, GoogleSignIn.getLastSignedInAccount(this));

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

إذا كان جهاز المشغّل غير متصل بشبكة عند اتصال تطبيقك SnapshotsClient.commitAndClose()، تخزِّن "خدمات ألعاب Google Play" بيانات الألعاب المحفوظة محليًا على الجهاز. بعد إعادة الاتصال بالجهاز، تعمل "خدمات ألعاب Google Play" على مزامنة اللعبة المحفوظة محليًا والتي تم تخزينها مؤقتًا التغييرات التي تطرأ على خوادم Google.

جارٍ تحميل الألعاب المحفوظة

لاسترداد الألعاب المحفوظة للمشغّل الذي سجّلت الدخول إليه حاليًا، عليك اتّباع الخطوات التالية:

  1. فتح لقطة بشكل غير متزامن من خلال SnapshotsClient.open() بعد ذلك، يمكنك استرداد الكائن Snapshot. من نتيجة المهمة من خلال استدعاء SnapshotsClient.DataOrConflict.getData(). بدلاً من ذلك، يمكن للعبة أيضًا استرداد لقطة معينة من خلال واجهة مستخدم اختيار الألعاب المحفوظة، كما هو موضح في عرض الألعاب المحفوظة:
  2. يمكنك استرداد مثيل SnapshotContents من خلال SnapshotsClient.SnapshotConflict.
  3. عليك طلب 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.
          // ...
        }
      });
}

التعامل مع التعارضات المحفوظة في الألعاب

عند استخدام واجهة برمجة تطبيقات لقطات الشاشة في لعبتك، يمكن لعدة على الأجهزة لإجراء عمليات القراءة والكتابة على اللعبة المحفوظة نفسها. في حالة انقطاع اتصال الجهاز بالشبكة مؤقتًا وإعادة الاتصال لاحقًا، قد يؤدي ذلك تؤدي إلى حدوث تعارضات في البيانات، حيث يتم تخزين اللعبة المحفوظة على جهاز محلي للّاعب غير متزامن مع الإصدار البعيد المخزن في خوادم Google.

توفّر واجهة برمجة التطبيقات للقطات الشاشة آلية لتسوية التعارضات توفّر كلاً من مجموعات من الألعاب المحفوظة المتعارضة في وقت القراءة وتتيح لك تنفيذ حلّ استراتيجيتك المناسبة للعبتك.

عندما تكتشف "خدمات ألعاب Google Play" وجود تعارض في البيانات، تعرض طريقة SnapshotsClient.DataOrConflict.isConflict() القيمة true في هذا الحدث، توفّر الفئة SnapshotsClient.SnapshotConflict نسختين من اللعبة المحفوظة:

  • إصدار الخادم: أحدث إصدار معروف من خدمات ألعاب Google Play للدقة لجهاز المشغّل أو
  • إصدار محلي: نسخة معدَّلة تم رصدها على أحد أجهزة المشغّل التي تحتوي على محتوى أو بيانات وصفية متعارضة وقد لا تكون هذه النسخة هي نفسها النسخة التي حاولت حفظها.

يجب أن تحدّد لعبتك كيفية حل التضارب من خلال اختيار أحد توفير إصدارات أو دمج بيانات إصداري اللعبة المحفوظين.

لاكتشاف تعارضات الألعاب المحفوظة وحلها:

  1. يمكنك الاتصال بالرقم SnapshotsClient.open(). تحتوي نتيجة المهمة على فئة SnapshotsClient.DataOrConflict.
  2. يمكنك استدعاء الطريقة SnapshotsClient.DataOrConflict.isConflict(). إذا كانت النتيجة true، يكون لديك أي نزاع لحله.
  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 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 حالية للحفظ في الخادم كالنسخة النهائية التي تم حلها، اتبع الخطوات التالية:

  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() بنجاح، تخزِّن واجهة برمجة التطبيقات Snapshot. إلى الخادم ويحاول فتح كائن اللقطة على جهازك المحلي.
    • في حال وجود تعارض، تعرض SnapshotsClient.DataOrConflict.isConflict() القيمة true. في هذه الدورة، في هذه الحالة، يجب أن تعود لعبتك إلى الخطوة 2 وتكرر الخطوات لتعديل اللقطة حتى يتم حل النزاعات.
    • إذا لم يكن هناك تعارض، ستعرض SnapshotsClient.DataOrConflict.isConflict() القيمة false العنصر Snapshot مفتوح لتعديله في لعبتك.