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