במדריך הזה נסביר איך להטמיע משחקים שמורים באמצעות ממשק ה-API של קובצי snapshot שסופק על ידי שירותי Google Play Games. ממשקי ה-API נמצאים בחבילות com.google.android.gms.games.snapshot
ו-com.google.android.gms.games
.
לפני שמתחילים
אם עדיין לא עשיתם זאת, מומלץ לעיין במושגי המשחקים של 'משחקים שמורים'.
- חשוב להפעיל תמיכה במשחקים שמורים במשחק ב-Google Play Console.
- מורידים את דוגמת הקוד של המשחקים השמורים ומעיינים בה בדף הדוגמאות ל-Android.
- כדאי לעיין בהמלצות שמפורטות ברשימת המשימות לבדיקת איכות.
אחזור הלקוח של קובצי snapshot
כדי להתחיל להשתמש ב-Snapshots API, המשחק צריך לקבל קודם אובייקט SnapshotsClient
. כדי לעשות זאת, צריך להפעיל את ה-method Games.getSnapshotsClient()
ולהעביר את הפעילות ואת GoogleSignInAccount
של הנגן הנוכחי. במאמר כניסה למשחקים ל-Android מוסבר איך לאחזר את פרטי חשבון השחקן.
ציון היקף הרשאות הגישה ב-Drive
ממשק ה-API של קובצי snapshot מסתמך על Google Drive API לאחסון משחקים שמורים. כדי לגשת ל-Drive API, האפליקציה צריכה לציין את ההיקף Drive.SCOPE_APPFOLDER
כשאתם יוצרים את לקוח הכניסה לחשבון Google.
דוגמה לאופן שבו עושים זאת בשיטה 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); } }); }
אם השחקן בוחר ליצור משחק שמאוחסן או לטעון משחק שמאוחסן, ממשק המשתמש שולח בקשה ל-Google Play Games Services. אם הבקשה תאושר, שירותי Google Play Games יחזירו מידע ליצירה או לשחזור של המשחק השמור דרך הפונקציה החוזרת (callback) onActivityResult()
. המשחק יכול לשנות את קריאת החזרה (callback) הזו כדי לבדוק אם אירעו שגיאות במהלך הבקשה.
קטע הקוד הבא מציג הטמעה לדוגמה של 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 // ... } } }
כתיבת משחקים שמורים
כדי לשמור תוכן במשחק ששמור:
- פתיחה אסינכרונית של קובץ snapshot באמצעות
SnapshotsClient.open()
. לאחר מכן, מאחזרים את האובייקטSnapshot
מתוצאת המשימה באמצעות קריאה ל-SnapshotsClient.DataOrConflict.getData()
. - אחזור מופע של
SnapshotContents
באמצעותSnapshotsClient.SnapshotConflict
. - קוראים ל-
SnapshotContents.writeBytes()
כדי לשמור את נתוני הנגן בפורמט בייט. - אחרי שכותבים את כל השינויים, קוראים ל-
SnapshotsClient.commitAndClose()
כדי לשלוח את השינויים לשרתים של Google. בקריאה ל-method, אפשר לספק מידע נוסף כדי להורות ל-Google Play Games Services איך להציג את המשחק השמור הזה לשחקנים. המידע הזה מיוצג באובייקט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 Games שומרים את נתוני המשחק השמורים באופן מקומי במכשיר. כשמקשרים מחדש את המכשיר, שירותי Google Play Games מסנכרנים את השינויים במשחקים שנשמרו במטמון המקומי עם השרתים של Google.
טעינת משחקים שמורים
כדי לאחזר משחקים שמורים של השחקן שמחובר לחשבון:
- פתיחה אסינכרונית של קובץ snapshot באמצעות
SnapshotsClient.open()
. לאחר מכן, מאחזרים את האובייקטSnapshot
מתוצאת המשימה באמצעות קריאה ל-SnapshotsClient.DataOrConflict.getData()
. לחלופין, המשחק יכול לאחזר תמונת מצב ספציפית דרך ממשק המשתמש לבחירת משחקים שמורים, כפי שמתואר בקטע הצגת משחקים שמורים. - מאחזרים את המכונה
SnapshotContents
באמצעותSnapshotsClient.SnapshotConflict
. - קוראים את תוכן קובץ snapshot באמצעות הפונקציה
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.
ממשק ה-API של קובצי snapshot מספק מנגנון פתרון סכסוכים שמציג את שתי הקבוצות של המשחקים השמורים שנמצאים בסכסוך בזמן הקריאה, ומאפשר לכם להטמיע אסטרטגיית פתרון שמתאימה למשחק שלכם.
כשמערכת Google Play Games Services מזהה התנגשויות נתונים, השיטה SnapshotsClient.DataOrConflict.isConflict()
מחזירה את הערך true
. במקרה כזה, הכיתה SnapshotsClient.SnapshotConflict
מספקת שתי גרסאות של המשחק השמור:
- גרסת השרת: הגרסה העדכנית ביותר שמערכת Google Play Games Services מזהה כמדויקת למכשיר של השחקן.
- גרסה מקומית: גרסה ששונתה ונמצאה באחד מהמכשירים של הנגן, שמכילה תוכן או מטא-נתונים סותרים. יכול להיות שהגרסה הזו לא זהה לגרסה שניסיתם לשמור.
המשחק צריך להחליט איך לפתור את הסתירה על ידי בחירה באחת מהגרסאות שסופקו או על ידי מיזוג הנתונים של שתי הגרסאות השמורות של המשחק.
כדי לזהות התנגשויות במשחקים שנשמרו ולפתור אותן:
- קוראים לפונקציה
SnapshotsClient.open()
. תוצאת המשימה מכילה מחלקה מסוגSnapshotsClient.DataOrConflict
. - קוראים ל-method
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
. - קוראים לפונקציה
SnapshotsClient.resolveConflict()
. בקריאה לשיטה, מעבירים את הערךSnapshotsClient.SnapshotConflict.getConflictId()
כארגומנט הראשון, ואת האובייקטיםSnapshotMetadataChange
ו-SnapshotContents
ששיניתם מקודם כארגומנטים השני והשלישי, בהתאמה. - אם הקריאה ל-
SnapshotsClient.resolveConflict()
תתבצע בהצלחה, ה-API יאחסן את האובייקטSnapshot
בשרת וינסה לפתוח את אובייקט ה-Snapshot במכשיר המקומי.- אם יש התנגשויות, הפונקציה
SnapshotsClient.DataOrConflict.isConflict()
מחזירה את הערךtrue
. במקרה כזה, צריך לחזור לשלב 2 ולחזור על השלבים לשינוי קובץ ה-snapshot עד שההתנגשויות ייפתרו. - אם אין התנגשות, הפונקציה
SnapshotsClient.DataOrConflict.isConflict()
מחזירה את הערךfalse
והאובייקטSnapshot
פתוח לשינוי על ידי המשחק.
- אם יש התנגשויות, הפונקציה