การรองรับเกมที่บันทึกไว้ในเกม Android

หลังจากเลิกใช้งาน API ของ Google Sign-In แล้ว เราจะนำ SDK v1 ของเกมออกในปี 2026 หลังจากเดือนกุมภาพันธ์ 2025 คุณจะเผยแพร่ เกมที่เพิ่งผสานรวมกับ SDK v1 ของ Games ใน Google Play ไม่ได้ เราขอแนะนำให้ใช้ SDK v2 ของ Games แทน
แม้ว่าเกมที่มีอยู่ซึ่งผสานรวมกับเกมเวอร์ชัน 1 ก่อนหน้าจะยังคงทำงานได้อีก 2-3 ปี แต่เราขอแนะนำให้คุณย้ายข้อมูลไปยัง v2 ตั้งแต่เดือนมิถุนายน 2025 เป็นต้นไป
คู่มือนี้มีไว้สำหรับการใช้ SDK บริการเกมของ Play เวอร์ชัน 1 ดูข้อมูล เกี่ยวกับ SDK เวอร์ชันล่าสุดได้ที่ เอกสารประกอบ v2

คู่มือนี้จะแสดงวิธีใช้งานเกมที่บันทึกไว้โดยใช้ Snapshots API ที่บริการเกมของ Google Play มีให้ คุณจะพบ API ได้ในแพ็กเกจ com.google.android.gms.games.snapshot และ com.google.android.gms.games

ก่อนเริ่มต้น

หากยังไม่ได้ดำเนินการ คุณอาจต้องดูแนวคิดเกมที่บันทึกไว้

ดาวน์โหลดไคลเอ็นต์สแนปชอต

หากต้องการเริ่มใช้ Snapshots API เกมของคุณต้องได้รับออบเจ็กต์ SnapshotsClient ก่อน คุณทำได้โดยการเรียกใช้เมธอด Games.getSnapshotsClient() และส่งกิจกรรมและ GoogleSignInAccount สำหรับผู้เล่นปัจจุบัน ดูวิธี ดึงข้อมูลบัญชีผู้เล่นได้ที่ การลงชื่อเข้าใช้ในเกม Android

ระบุขอบเขตของไดรฟ์

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 ได้ทุกที่ที่เกมมีตัวเลือกให้ผู้เล่น บันทึกหรือกู้คืนความคืบหน้า เกมอาจแสดงตัวเลือกดังกล่าวในจุดบันทึก/กู้คืนที่กำหนด หรืออนุญาตให้ผู้เล่นบันทึกหรือกู้คืนความคืบหน้าได้ทุกเมื่อ

เมื่อผู้เล่นเลือกตัวเลือกบันทึก/กู้คืนในเกมของคุณ เกมจะแสดงหน้าจอที่แจ้งให้ผู้เล่นป้อนข้อมูลสำหรับเกมใหม่ที่บันทึกไว้ หรือเลือกเกมที่บันทึกไว้แล้วเพื่อกู้คืน (ไม่บังคับ)

API สแนปชอตมีอินเทอร์เฟซผู้ใช้ (UI) สำหรับการเลือกเกมที่บันทึกไว้เริ่มต้นที่คุณใช้ได้ทันทีเพื่อลดความซับซ้อนในการพัฒนา UI การเลือกเกมที่บันทึกไว้ช่วยให้ผู้เล่น สร้างเกมที่บันทึกไว้ใหม่ ดูรายละเอียดเกี่ยวกับเกมที่บันทึกไว้ที่มีอยู่ และโหลดเกมที่บันทึกไว้ก่อนหน้าได้

วิธีเปิด UI เกมที่บันทึกไว้เริ่มต้น

  1. โทรหา SnapshotsClient.getSelectSnapshotIntent() เพื่อรับ Intent สำหรับเปิดใช้ UI การเลือกเกมที่บันทึกไว้เริ่มต้น
  2. โทรหา startActivityForResult() แล้วส่ง Intent หากการเรียกใช้สำเร็จ เกมจะแสดง UI การเลือกเกมที่บันทึกไว้พร้อมกับตัวเลือกที่คุณระบุ

ตัวอย่างวิธีเปิด UI การเลือกเกมที่บันทึกไว้เริ่มต้นมีดังนี้

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

หากผู้เล่นเลือกสร้างเกมที่บันทึกไว้ใหม่หรือโหลดเกมที่บันทึกไว้ที่มีอยู่ UI จะส่งคำขอไปยังบริการเกมของ Google Play หากคำขอสำเร็จ บริการเกมของ Google 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
      // ...
    }
  }
}

เขียนเกมที่บันทึกไว้

วิธีจัดเก็บเนื้อหาลงในเกมที่บันทึกไว้

  1. เปิดสแนปชอตแบบไม่พร้อมกันผ่าน SnapshotsClient.open() จากนั้นเรียกออบเจ็กต์ Snapshot จากผลลัพธ์ของงานโดยเรียก SnapshotsClient.DataOrConflict.getData()
  2. เรียกข้อมูลอินสแตนซ์ SnapshotContents ผ่าน SnapshotsClient.SnapshotConflict
  3. เรียกใช้ SnapshotContents.writeBytes() เพื่อจัดเก็บข้อมูลของผู้เล่นในรูปแบบไบต์
  4. เมื่อเขียนการเปลี่ยนแปลงทั้งหมดแล้ว ให้เรียกใช้ 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

โหลดเกมที่บันทึกไว้

วิธีดึงข้อมูลเกมที่บันทึกไว้สำหรับผู้เล่นที่ลงชื่อเข้าใช้ในปัจจุบัน

  1. เปิดสแนปชอตแบบไม่พร้อมกันผ่าน SnapshotsClient.open() จากนั้นเรียกออบเจ็กต์ Snapshot จากผลลัพธ์ของงานโดยเรียก SnapshotsClient.DataOrConflict.getData() หรือเกมของคุณยังสามารถดึงข้อมูลสแนปชอตที่เฉพาะเจาะจงผ่าน UI การเลือกเกมที่บันทึกไว้ได้ด้วย ตามที่อธิบายไว้ในส่วนแสดงเกมที่บันทึกไว้
  2. ดึงข้อมูลอินสแตนซ์ SnapshotContents ผ่าน SnapshotsClient.SnapshotConflict
  3. โทรหา 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 จะมีเกมที่บันทึกไว้ 2 เวอร์ชัน

  • เวอร์ชันเซิร์ฟเวอร์: เวอร์ชันล่าสุดที่บริการเกม Google Play ทราบว่าถูกต้อง สำหรับอุปกรณ์ของผู้เล่น และ
  • เวอร์ชันในเครื่อง: เวอร์ชันที่แก้ไขแล้วซึ่งตรวจพบในอุปกรณ์เครื่องใดเครื่องหนึ่งของผู้เล่นที่มีเนื้อหาหรือข้อมูลเมตาที่ขัดแย้งกัน ซึ่งอาจไม่ใช่เวอร์ชันเดียวกับที่คุณพยายามบันทึก

เกมของคุณต้องตัดสินใจวิธีแก้ไขข้อขัดแย้งโดยเลือกเวอร์ชันใดเวอร์ชันหนึ่งที่ ระบุไว้ หรือผสานข้อมูลของเกมที่บันทึกไว้ 2 เวอร์ชัน

วิธีตรวจหาและแก้ไขความขัดแย้งของเกมที่บันทึกไว้

  1. โทรหา SnapshotsClient.open() ผลลัพธ์ของงานมีSnapshotsClient.DataOrConflict คลาส
  2. เรียกใช้เมธอด SnapshotsClient.DataOrConflict.isConflict() หากผลลัพธ์เป็นจริง แสดงว่าคุณมี ความขัดแย้งที่ต้องแก้ไข
  3. โทรหา SnapshotsClient.DataOrConflict.getConflict() เพื่อเรียกอินสแตนซ์ SnapshotsClient.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 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 ที่มีอยู่ เพื่อบันทึกลงในเซิร์ฟเวอร์เป็นเวอร์ชันสุดท้ายที่แก้ไขแล้ว ให้ทำตามขั้นตอนต่อไปนี้

  1. โทรหา SnapshotsClient.open()
  2. เรียกใช้ SnapshotsClient.SnapshotConflict.getResolutionSnapshotsContent() เพื่อรับออบเจ็กต์ SnapshotContents ใหม่
  3. ผสานข้อมูลจาก SnapshotsClient.SnapshotConflict.getConflictingSnapshot() และ SnapshotsClient.SnapshotConflict.getSnapshot() ลงในออบเจ็กต์ SnapshotContents จาก ขั้นตอนก่อนหน้า
  4. หรือจะสร้างอินสแตนซ์ SnapshotMetadataChange ก็ได้หากมีการเปลี่ยนแปลงฟิลด์ข้อมูลเมตา
  5. โทรหา SnapshotsClient.resolveConflict() ในการเรียกเมธอด ให้ส่ง SnapshotsClient.SnapshotConflict.getConflictId() เป็นอาร์กิวเมนต์แรก และออบเจ็กต์ SnapshotMetadataChange และ SnapshotContents ที่คุณแก้ไขก่อนหน้านี้เป็นอาร์กิวเมนต์ที่ 2 และ 3 ตามลำดับ
  6. หากการเรียก SnapshotsClient.resolveConflict() สำเร็จ API จะจัดเก็บออบเจ็กต์ Snapshot ไว้ในเซิร์ฟเวอร์และพยายามเปิดออบเจ็กต์ Snapshot ในอุปกรณ์ของคุณ
    • หากมีข้อขัดแย้ง SnapshotsClient.DataOrConflict.isConflict() จะแสดงผล true ในกรณีนี้ เกมควรกลับไปที่ขั้นตอนที่ 2 และทำขั้นตอนซ้ำเพื่อแก้ไขสแนปชอตจนกว่า ความขัดแย้งจะได้รับการแก้ไข
    • หากไม่มีข้อขัดแย้ง SnapshotsClient.DataOrConflict.isConflict() จะแสดงผล false และ ออบเจ็กต์ Snapshot จะเปิดให้เกมของคุณแก้ไขได้