الألعاب المحفوظة لألعاب Android

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

قبل البدء

للحصول على معلومات عن الميزة، يمكنك مراجعة نظرة عامة على الألعاب المحفوظة

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

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

تحديد نطاق Drive

تعتمد واجهة لقطات الشاشة على Google Drive API للاحتفاظ بمساحة تخزين الألعاب للوصول إلى 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 OnCompleteListenerG<oogleSignInAccount(>) {
        @Override
        public void onComplete(@NonNull TaskG<oogleSignInAccount >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 =
      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);
    }
  });
}

إذا اختار اللاعب إنشاء لعبة جديدة محفوظة أو تحميل لعبة محفوظة حالية، ترسل واجهة المستخدم طلبًا إلى "خدمات ألعاب Play". إذا تم الطلب بنجاح، تعرض "خدمات ألعاب 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()

  2. استرداد Snapshot من نتيجة المهمة من خلال استدعاء SnapshotsClient.DataOrConflict.getData()

  3. استرداد SnapshotContents مثيل مع SnapshotsClient.SnapshotConflict

  4. اتصل SnapshotContents.writeBytes() لتخزين بيانات المشغل بتنسيق بايت.

  5. بعد كتابة جميع التغييرات، اتصل SnapshotsClient.commitAndClose() لإرسال التغييرات إلى خوادم 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()، وتخزِّن "خدمات ألعاب Play" بيانات الألعاب المحفوظة محليًا على الجهاز. على الجهاز إعادة الاتصال، تعمل "خدمات ألعاب Play" على مزامنة تغييرات الألعاب المحفوظة محليًا والتي تم حفظها إلى خوادم Google.

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

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

  1. فتح لقطة بشكل غير متزامن باستخدام SnapshotsClient.open()

  2. استمتِع Snapshot من نتيجة المهمة عن طريق استدعاء SnapshotsClient.DataOrConflict.getData() وبدلاً من ذلك، يمكن للعبتك أيضًا استرداد ملف لقطة من خلال واجهة المستخدم الخاصة باختيار الألعاب المحفوظة، كما هو موضح في عرض الألعاب المحفوظة:

  3. استرداد SnapshotContents مثيل مع SnapshotsClient.SnapshotConflict

  4. اتصل 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.
          // ...
        }
      });
}

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

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

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

عندما ترصد "خدمات ألعاب Play" تعارضًا في البيانات، يتم SnapshotsClient.DataOrConflict.isConflict() تُرجع قيمة true في هذه الحالة، SnapshotsClient.SnapshotConflict إصدارين من اللعبة المحفوظة:

  • إصدار الخادم: أحدث إصدار من "خدمات ألعاب 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;

TaskS<napshot >processSnapshotOpenResult(SnapshotsClient.DataOrConflictS<napshot >result,
                                         final int retryCount) {

  if (!result.isConflict()) {
    // There was no conflict, so return the result of the source.
    TaskCompletionSourceS<napshot >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.DataOrConflictS<napshot,>
              TaskS<napshot(>>) {
            @Override
            public TaskS<napshot >then(
                @NonNull TaskS<napshotsClient.DataOrConflictS<napshot >>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(C"ould 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 مفتوحًا لتعديلها في لعبتك.