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