משחקים שמורים למשחקי 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() ומעבירים את פעילות.

ציון ההיקף של האחסון

ה-Snaps 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);
    }
  });
}

אם השחקן בוחר ליצור משחק שמור חדש או לטעון משחק שמור קיים, ממשק המשתמש שולח בקשה ל-Play Games Services. אם הבקשה מצליחה, שירות 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()

  2. מאחזרים את אובייקט Snapshot מתוצאת המשימה באמצעות קריאה לאובייקט SnapshotsClient.DataOrConflict.getData()

  3. אחזור SnapshotContents מופע עם SnapshotsClient.SnapshotConflict

  4. שיחת טלפון SnapshotContents.writeBytes() כדי לאחסן את נתוני הנגן בפורמט בייטים.

  5. אחרי שכל השינויים נכתבים, קוראים 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.

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

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

  1. פתיחה אסינכרונית של קובץ snapshot באמצעות SnapshotsClient.open()

  2. הוצאה Snapshot מתוצאת המשימה באמצעות קריאה SnapshotsClient.DataOrConflict.getData() לחלופין, המשחק שלך יכול גם לאחזר נתונים ספציפיים של תמונת מצב דרך ממשק המשתמש לבחירת משחקים שמורים, כפי שמתואר הצגת משחקים שמורים.

  3. מאחזרים את SnapshotContents מופע עם SnapshotsClient.SnapshotConflict

  4. שיחת טלפון 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 מספק מנגנון לפתרון התנגשויות קבוצות של משחקים שמורים בעלי מאפיינים זהים לחשבון פעיל בזמן הקריאה ומאפשרים פתרון שמתאימה למשחק שלכם.

כש-Play Games Services מזהה התנגשות נתונים, SnapshotsClient.DataOrConflict.isConflict() ה-method מחזירה את הערך true באירוע הזה, הפונקציה SnapshotsClient.SnapshotConflict class מספק שתי גרסאות של המשחק השמור:

  • גרסת שרת: הגרסה העדכנית ביותר שידועה ל-Play Games Services ל להיות מדויקות בהתאם למכשיר של הנגן.

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

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

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

  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 במכשיר המקומי.

    • אם יש התנגשות, הפונקציה SnapshotsClient.DataOrConflict.isConflict() מחזירה true. כאן המשחק צריך לחזור לשלב 2 ולחזור על השלבים כדי לשנות את תמונת המצב עד התנגשויות נפתרו.
    • אם אין מחלוקת, SnapshotsClient.DataOrConflict.isConflict() מחזירה את הערך false והאובייקט Snapshot פתוח כדי שהמשחק יוכל לשנות אותו.