يوضّح لك هذا الدليل كيفية تنفيذ ميزة "حفظ التقدم في الألعاب" باستخدام واجهة برمجة التطبيقات الخاصة باللقطات التي توفّرها "خدمات ألعاب Google Play". يمكن العثور على واجهات برمجة التطبيقات في حزمتَي
com.google.android.gms.games.snapshot
و
com.google.android.gms.games
.
قبل البدء
للحصول على معلومات حول هذه الميزة، يُرجى الاطّلاع على نظرة عامة حول ميزة "حفظ التقدم في الألعاب".
- فعِّل ميزة "الألعاب المحفوظة" في لعبتك من خلال Google Play Console.
- تنزيل نموذج الرمز البرمجي للألعاب المحفوظة ومراجعته في صفحة نماذج Android
- تعرَّف على الاقتراحات الموضّحة في قائمة التحقّق من الجودة.
الحصول على عميل اللقطات
لبدء استخدام واجهة برمجة التطبيقات الخاصة باللقطات، يجب أن تحصل لعبتك أولاً على عنصر
SnapshotsClient
. يمكنك إجراء ذلك من خلال استدعاء الطريقة
Games.getSnapshotsContents()
وتمرير النشاط.
عرض الألعاب المحفوظة
يمكنك دمج واجهة برمجة التطبيقات الخاصة باللقطات في أي مكان تتيح فيه لعبتك للاعبين خيار حفظ مستوى تقدّمهم أو استعادته. قد تعرض لعبتك هذا الخيار في نقاط الحفظ أو الاستعادة المحدّدة أو تسمح للاعبين بحفظ مستوى التقدّم أو استعادته في أي وقت.
بعد أن يختار اللاعبون خيار الحفظ أو الاستعادة في لعبتك، يمكن للعبتك أن تعرض بشكل اختياري شاشة تطلب من اللاعبين إدخال معلومات عن لعبة محفوظة جديدة أو اختيار لعبة محفوظة حالية لاستعادتها.
لتسهيل عملية التطوير، توفّر واجهة برمجة التطبيقات الخاصة باللقطات واجهة مستخدم تلقائية لاختيار الألعاب المحفوظة يمكنك استخدامها بدون أي تعديل. تتيح واجهة مستخدم اختيار الألعاب المحفوظة للاعبين إنشاء لعبة محفوظة جديدة، وعرض تفاصيل حول الألعاب المحفوظة الحالية، وتحميل الألعاب المحفوظة السابقة.
لتشغيل واجهة المستخدم التلقائية لخدمة "حفظ التقدم في الألعاب"، اتّبِع الخطوات التالية:
- اتّصِل بالدالة
SnapshotsClient.getSelectSnapshotIntent()
للحصول علىIntent
من أجل تشغيل واجهة المستخدم التلقائية لاختيار الألعاب المحفوظة. - استدعِ الدالة
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 // ... } } }
كتابة الألعاب المحفوظة
لتخزين المحتوى في لعبة محفوظة، اتّبِع الخطوات التالية:
افتح لقطة بشكل غير متزامن باستخدام
SnapshotsClient.open()
.استرجِع عنصر
Snapshot
من نتيجة المهمة من خلال استدعاءSnapshotsClient.DataOrConflict.getData()
.استرداد مثيل
SnapshotContents
باستخدامSnapshotsClient.SnapshotConflict
استخدِم الأمر Call
SnapshotContents.writeBytes()
لتخزين بيانات اللاعب بتنسيق البايت.بعد كتابة جميع التغييرات، استدعِ الدالة
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.
تحميل الألعاب المحفوظة
لاسترداد الألعاب المحفوظة للاعب الذي تمت مصادقته، اتّبِع الخطوات التالية:
افتح لقطة بشكل غير متزامن باستخدام
SnapshotsClient.open()
.استرجِع عنصر
Snapshot
من نتيجة المهمة من خلال استدعاءSnapshotsClient.DataOrConflict.getData()
. بدلاً من ذلك، يمكن أن تسترد لعبتك أيضًا لقطة شاشة معيّنة من خلال واجهة المستخدم الخاصة باختيار الألعاب المحفوظة، كما هو موضّح في عرض الألعاب المحفوظة.استرداد مثيل
SnapshotContents
باستخدامSnapshotsClient.SnapshotConflict
اتّصِل
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" أنّه دقيق لجهاز اللاعب.
النسخة المحلية: نسخة معدَّلة تم رصدها على أحد أجهزة المشغّل تتضمّن محتوًى أو بيانات وصفية متعارضة. وقد لا يكون هذا الإصدار هو الإصدار الذي حاولت حفظه.
يجب أن تحدّد لعبتك كيفية حل التعارض من خلال اختيار أحد الإصدارَين المقدَّمَين أو دمج بيانات إصدارَي اللعبة المحفوظة.
لرصد التعارضات في الألعاب المحفوظة وحلّها، اتّبِع الخطوات التالية:
اتّصِل على
SnapshotsClient.open()
. تحتوي نتيجة المهمة على فئةSnapshotsClient.DataOrConflict
.استدعِ الطريقة
SnapshotsClient.DataOrConflict.isConflict()
. إذا كانت النتيجة true، عليك حلّ التعارض.اتّصِل بالرقم
SnapshotsClient.DataOrConflict.getConflict()
لاسترداد مثيلSnapshotsClient.snapshotConflict
.استخدِم الأمر Call
SnapshotsClient.SnapshotConflict.getConflictId()
لاسترداد معرّف التعارض الذي يحدّد التعارض الذي تم رصده بشكل فريد. تحتاج لعبتك إلى هذه القيمة لإرسال طلب حل التعارض لاحقًا.اتّصِل على
SnapshotsClient.SnapshotConflict.getConflictingSnapshot()
للحصول على النسخة المحلية.اتّصِل بالرقم
SnapshotsClient.SnapshotConflict.getSnapshot()
للحصول على إصدار الخادم.لحلّ تعارض اللعبة المحفوظة، اختَر إصدارًا تريد حفظه على الخادم كالإصدار النهائي، ومرِّره إلى الطريقة
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
حالي لحفظه على الخادم كالإصدار النهائي الذي تم حله، اتّبِع الخطوات التالية:
اتّصِل على
SnapshotsClient.open()
.اتّصِل
SnapshotsClient.SnapshotConflict.getResolutionSnapshotsContent()
للحصول على عنصرSnapshotContents
جديد.ادمِج البيانات من
SnapshotsClient.SnapshotConflict.getConflictingSnapshot()
وSnapshotsClient.SnapshotConflict.getSnapshot()
في الكائنSnapshotContents
من الخطوة السابقة.يمكنك اختياريًا إنشاء مثيل
SnapshotMetadataChange
إذا تم إجراء أي تغييرات على حقول البيانات الوصفية.اتّصِل على
SnapshotsClient.resolveConflict()
. في استدعاء الطريقة، مرِّرSnapshotsClient.SnapshotConflict.getConflictId()
كوسيطة أولى، ومرِّر الكائنَينSnapshotMetadataChange
وSnapshotContents
اللذين عدّلتهما سابقًا كوسيطتَين ثانية وثالثة على التوالي.في حال نجاح طلب
SnapshotsClient.resolveConflict()
، يخزّن واجهة برمجة التطبيقات العنصرSnapshot
على الخادم ويحاول فتح العنصر Snapshot على جهازك المحلي.- في حال حدوث تعارض،
SnapshotsClient.DataOrConflict.isConflict()
تعرضtrue
. في هذه الحالة، يجب أن تعود لعبتك إلى الخطوة 2 وتكرّر الخطوات لتعديل اللقطة إلى أن يتم حل التعارضات. - إذا لم يكن هناك تعارض،
ستعرض الدالة
SnapshotsClient.DataOrConflict.isConflict()
القيمةfalse
، وسيتم فتح العنصرSnapshot
لتتمكّن لعبتك من تعديله.
- في حال حدوث تعارض،