يشرح لك هذا الدليل كيفية تنفيذ ميزة "حفظ التقدم في الألعاب" باستخدام واجهة برمجة التطبيقات
snapshots API التي تقدّمها "خدمات ألعاب Google Play". يمكن العثور على واجهات برمجة التطبيقات في الحزم
com.google.android.gms.games.snapshot
وcom.google.android.gms.games
.
قبل البدء
ننصحك بمراجعة مفاهيم ألعاب "حفظ التقدم في الألعاب" إذا لم يسبق لك ذلك.
- احرص على تفعيل ميزة حفظ الألعاب للعبتك في Google Play Console.
- نزِّل نموذج رمز الألعاب المحفوظة وراجِعه في صفحة نماذج Android.
- اطّلِع على الاقتراحات الموضّحة في قائمة التحقّق من الجودة.
الحصول على عميل اللقطات
لبدء استخدام واجهة برمجة التطبيقات Snapshots API، يجب أن تحصل لعبتك أولاً على عنصر
SnapshotsClient
. يمكنك إجراء ذلك من خلال استدعاء الأسلوب
Games.getSnapshotsClient()
وضبط القيمة
activity وGoogleSignInAccount
للمشغّل الحالي. للتعرّف على كيفية retrieving the player account information، يُرجى الاطّلاع على تسجيل الدخول في ألعاب Android.
تحديد نطاق Drive
تعتمد واجهة برمجة التطبيقات Snapshots API على Google Drive API لتخزين الألعاب المحفوظة. للوصول إلى Drive API، يجب أن يحدِّد تطبيقك نطاق Drive.SCOPE_APPFOLDER
عند إنشاء برنامج Google Sign-in.
في ما يلي مثال على كيفية إجراء ذلك في 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 } } }); }
عرض الألعاب المحفوظة
يمكنك دمج واجهة برمجة التطبيقات Snapshots API في أيّ مكان تتيح فيه لعبتك للاعبين خيار حفظ مستوى تقدّمهم أو استعادته. قد تعرض لعبتك هذا الخيار عند نقاط حفظ/استعادة محدّدة أو تسمح للاعبين بحفظ التقدّم أو استعادته في أي وقت.
بعد أن يختار اللاعبون خيار الحفظ/الاستعادة في لعبتك، يمكن للعبة عرض شاشة اختيارية تطلب من اللاعبين إدخال معلومات عن لعبة جديدة محفوظة أو اختيار لعبة محفوظة حالية لاستعادتها.
لتبسيط عملية التطوير، توفّر واجهة برمجة التطبيقات Snapshots API واجهة مستخدم (UI) تلقائية لاختيار المباريات المحفوظة يمكنك استخدامها مباشرةً. تتيح واجهة المستخدم لاختيار "الألعاب المحفوظة" للاعبين إنشاء لعبة محفوظة جديدة وعرض تفاصيل عن الألعاب المحفوظة الحالية وتحميل الألعاب المحفوظة السابقة.
لتشغيل واجهة المستخدم التلقائية لميزة "الألعاب المحفوظة":
- اتّصل بالرقم
SnapshotsClient.getSelectSnapshotIntent()
للحصول على رمز التفعيلIntent
لتشغيل واجهة مستخدم اختيار الألعاب المحفوظة التلقائية. - اتصل برقم
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); } }); }
إذا اختار اللاعب إنشاء ملف جديد محفوظ للّعبة أو تحميل ملف محفوظ حالي،
يُرسِل واجهة المستخدم طلبًا إلى "خدمات ألعاب 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. في طلب الطريقة، يمكن لتطبيقك تقديم معلومات إضافية اختيارية لإعلام "خدمات ألعاب 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.
تحميل الألعاب المحفوظة
لاسترداد الألعاب المحفوظة الخاصة باللاعب الذي سجّل الدخول حاليًا:
- فتح لقطة شاشة بشكل غير متزامن من خلال
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 = 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. // ... } }); }
التعامل مع تعارضات الألعاب المحفوظة
عند استخدام واجهة برمجة التطبيقات Snapshots API في لعبتك، من الممكن أن تُجري عدة أجهزة عمليات قراءة وكتابة في اللعبة المحفوظة نفسها. في حال فقدان أحد الأجهزة الاتصال بالشبكة مؤقتًا ثم إعادة الاتصال لاحقًا، قد يؤدي ذلك إلى حدوث تعارض في البيانات، ما يؤدي إلى عدم مزامنة اللعبة المحفوظة على جهاز اللاعب المحلي مع النسخة البعيدة المخزَّنة على خوادم Google.
توفّر واجهة برمجة التطبيقات Snapshots API آلية لحلّ النزاعات تعرض كلّ من مجموعات الألعاب المحفوظة المتضاربة في وقت القراءة وتتيح لك تنفيذ استراتيجية حلّ مناسبة للعبة.
عندما ترصد "خدمات ألعاب 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; 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
حالي لحفظه على الخادم كنسخة نهائية تم حلّها، اتّبِع الخطوات التالية:
- يُرجى الاتصال على
SnapshotsClient.open()
. - اتصل بـ
SnapshotsClient.SnapshotConflict.getResolutionSnapshotsContent()
للحصول على عنصرSnapshotContents
جديد. - دمج البيانات من
SnapshotsClient.SnapshotConflict.getConflictingSnapshot()
وSnapshotsClient.SnapshotConflict.getSnapshot()
في عنصرSnapshotContents
من الخطوة السابقة - يمكنك اختياريًا إنشاء مثيل
SnapshotMetadataChange
في حال حدوث أي تغييرات على حقول metadata. - يُرجى الاتصال على
SnapshotsClient.resolveConflict()
. في طلب الطريقة، أدخِلSnapshotsClient.SnapshotConflict.getConflictId()
كوسيطة أولى، والعنصرَينSnapshotMetadataChange
وSnapshotContents
اللذَين عدّلتهما سابقًا كوسيطتَين ثانية وثالثة على التوالي. - إذا تم إكمال طلب
SnapshotsClient.resolveConflict()
، تخزِّن واجهة برمجة التطبيقات عنصرSnapshot
على الخادم وتحاول فتح عنصر "الملخّص" على جهازك.- في حال حدوث تعارض، تعرِض الدالة
SnapshotsClient.DataOrConflict.isConflict()
القيمةtrue
. في هذه الحالة، يجب أن تعود لعبتك إلى الخطوة 2 وتكرر الخطوات لتعديل اللقطة إلى أن يتم حلّ التعارضات. - في حال عدم حدوث تعارض، يعرض
SnapshotsClient.DataOrConflict.isConflict()
القيمةfalse
ويكون كائنSnapshot
مفتوحًا لتعديله في لعبتك.
- في حال حدوث تعارض، تعرِض الدالة