תמיכה במשחקים שמורים במשחקי Android

במדריך הזה מוסבר איך מטמיעים משחקי משחקים שמורים באמצעות snapshots API שמסופק על ידי Google Play Games Services. תוכלו למצוא את ממשקי ה-API com.google.android.gms.games.snapshot ו-com.google.android.gms.games חבילות.

לפני שמתחילים

אם עדיין לא עשית זאת, מומלץ לעיין מושגים של משחקים שמורים.

קבלת קובץ ה-snapshot

כדי להתחיל להשתמש ב-snapshot API, המשחק שלך צריך קודם לקבל אובייקט SnapshotsClient. אפשר לעשות זאת באמצעות קריאה ל Games.getSnapshotsClient() ומעבירים את פעילות.

ציון ההיקף של Drive

ה-snapshot API מסתמך על 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
          }
        }
      });
}

מוצגים משחקים שמורים

אפשר לשלב את ה-API של קובצי snapshot בכל מקום שבו המשחק מספק לשחקנים אפשרות לשמור או לשחזר את ההתקדמות שלהם. המשחק שלך עשוי להציג בנקודות שמירה/שחזור ייעודיות, או מתן אפשרות לשחקנים לשמור או לשחזר בכל שלב.

אחרי שהשחקנים בוחרים באפשרות השמירה או השחזור במשחק, הם יכולים אפשר גם להציג מסך שיבקש מהשחקנים להזין מידע לקובץ חדש שנשמר משחק או לבחור משחק שמור קיים לשחזור.

כדי לפשט את תהליך הפיתוח, ה-snapshot API מספק משתמש ברירת מחדל לבחירת משחקים שמורים ממשק (UI) שאפשר להשתמש בו מחוץ לאריזה. ממשק המשתמש לבחירת משחקים שמורים מאפשר לשחקנים ליצור משחק שמור חדש, להציג פרטים על משחקים שמורים קיימים ולטעון משחקים קודמים שנשמרו.

כדי להפעיל את ממשק המשתמש של המשחקים השמורים שמוגדר כברירת מחדל:

  1. התקשרו למספר SnapshotsClient.getSelectSnapshotIntent() כדי לקבל Intent להפעלת ברירת המחדל ממשק המשתמש לבחירת משחקים שמורים.
  2. התקשרות אל 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 Games Services מחזיר מידע כדי ליצור או לשחזר את המשחק השמור באמצעות 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
      // ...
    }
  }
}

משחקים שמורים לכתיבה

כדי לאחסן תוכן במשחק שמור:

  1. פתיחה אסינכרונית של קובץ snapshot דרך SnapshotsClient.open(). לאחר מכן, מאחזרים את האובייקט Snapshot מתוצאת המשימה באמצעות SnapshotsClient.DataOrConflict.getData().
  2. אחזור של מופע SnapshotContents באמצעות SnapshotsClient.SnapshotConflict.
  3. קוראים לפונקציה SnapshotContents.writeBytes() כדי לאחסן את נתוני הנגן בפורמט בייטים.
  4. אחרי שכל השינויים נכתבים, קוראים 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 =
      PlayGames.getSnapshotsClient(this);

  // Commit the operation
  return snapshotsClient.commitAndClose(snapshot, metadataChange);
}

אם המכשיר של הנגן לא מחובר לרשת כשהאפליקציה מתקשרת SnapshotsClient.commitAndClose(), נתוני המשחקים השמורים של Google Play Services נשמרים באופן מקומי במכשיר במכשיר. אחרי חיבור מחדש של המכשיר, שירותי המשחקים של Google Play מסנכרנים את המשחק שנשמר במטמון המקומי שינויים בשרתי Google.

המשחקים השמורים בטעינה

כדי לאחזר משחקים שנשמרו עבור השחקן שמחובר כרגע:

  1. פתיחה אסינכרונית של קובץ snapshot דרך SnapshotsClient.open(). לאחר מכן, מאחזרים את האובייקט Snapshot מתוצאת המשימה באמצעות SnapshotsClient.DataOrConflict.getData(). לחלופין, יכול גם לאחזר תמונת מצב ספציפית דרך ממשק המשתמש לבחירת משחקים שמורים, כפי שמתואר הצגת משחקים שמורים.
  2. מאחזרים את המכונה SnapshotContents באמצעות SnapshotsClient.SnapshotConflict.
  3. קוראים לפונקציה SnapshotContents.readFully() כדי לקרוא את התוכן של קובץ ה-snapshot.

קטע הקוד הבא מראה איך אפשר לטעון משחק שמור ספציפי:

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.

ה-snapshot API מספק מנגנון לפתרון התנגשויות קבוצות של משחקים שמורים בעלי מאפיינים זהים לחשבון פעיל בזמן הקריאה ומאפשרים פתרון שמתאימה למשחק שלכם.

כששירותי המשחקים של Google Play מזהים התנגשות נתונים, השיטה SnapshotsClient.DataOrConflict.isConflict() מחזירה את הערך true באירוע הזה, במחלקה SnapshotsClient.SnapshotConflict יש שתי גרסאות של המשחק השמור:

  • גרסת השרת: הגרסה העדכנית ביותר שידועה בשירותי המשחקים של Google Play והיא מדויקת למכשיר של השחקן. וגם
  • גרסה מקומית: גרסה מקומית שזוהתה באחד מהמכשירים של הנגן שמכיל תוכן או מטא-נתונים בעלי מאפיינים זהים לחשבון פעיל. יכול להיות שהגרסה הזו לא זהה לגרסה שניסית לשמור.

המשחק שלך צריך להחליט איך לפתור את המחלוקת על ידי בחירה באחת מהאפשרויות שסופקו או מיזוג הנתונים של שתי הגרסאות השמורות של המשחקים.

כדי לזהות ולפתור התנגשויות בין משחקים שמורים:

  1. קוראים לפונקציה SnapshotsClient.open(). תוצאת המשימה מכילה מחלקה SnapshotsClient.DataOrConflict.
  2. מפעילים את השיטה SnapshotsClient.DataOrConflict.isConflict(). אם התוצאה נכונה, יש לך כדי לפתור את המחלוקת.
  3. התקשרו אל SnapshotsClient.DataOrConflict.getConflict() כדי לאחזר מופע של SnaphotsClient.snapshotConflict.
  4. קוראים לפונקציה SnapshotsClient.SnapshotConflict.getConflictId() כדי לאחזר את המזהה המתנגש משמש לזיהוי ההתנגשות שזוהה. המשחק שלך צריך את הערך הזה כדי לשלוח בקשה לפתרון התנגשויות מאוחר יותר.
  5. צריך להתקשר למספר SnapshotsClient.SnapshotConflict.getConflictingSnapshot() כדי לקבל את הגרסה המקומית.
  6. קוראים לפונקציה SnapshotsClient.SnapshotConflict.getSnapshot() כדי לקבל את גרסת השרת.
  7. כדי לפתור את ההתנגשות של משחקים שמורים, בוחרים גרסה שרוצים לשמור בשרת בתור את הגרסה הסופית, ומעבירים אותה לשיטה 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 קיים כדי לשמור בשרת כגרסה הסופית שטופלה, מבצעים את השלבים הבאים:

  1. קוראים לפונקציה SnapshotsClient.open() .
  2. אפשר להתקשר למספר SnapshotsClient.SnapshotConflict.getResolutionSnapshotsContent() כדי לקבל מספר אובייקט SnapshotContents.
  3. מיזוג הנתונים מ-SnapshotsClient.SnapshotConflict.getConflictingSnapshot() SnapshotsClient.SnapshotConflict.getSnapshot() לאובייקט SnapshotContents. לשלב הקודם.
  4. אופציונלי: אפשר ליצור מופע של SnapshotMetadataChange אם יש שינויים במטא-נתונים .
  5. קוראים לפונקציה SnapshotsClient.resolveConflict(). בהפעלת ה-method, מעבירים SnapshotsClient.SnapshotConflict.getConflictId() כארגומנט הראשון, SnapshotMetadataChange ו-SnapshotContents אובייקטים ששיניתם קודם לכן כאובייקטים השניים ארגומנטים של שלישי, בהתאמה.
  6. אם הקריאה ל-SnapshotsClient.resolveConflict() מבוצעת בהצלחה, ה-API יאחסן את Snapshot את האובייקט של השרת ומנסה לפתוח את האובייקט snapshot במכשיר המקומי.