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