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

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

ข้อควรทราบก่อนที่จะเริ่มต้น

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

กําลังโหลดไคลเอ็นต์สแนปชอต

เกมจะต้องได้รับออบเจ็กต์ SnapshotsClient ก่อนเพื่อเริ่มใช้สแนปชอต API ซึ่งทําได้โดยการเรียกเมธอด Games.getSnapshotsClient() และส่งผ่านกิจกรรม

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

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

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

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

หากผู้เล่นเลือกที่จะสร้างเกมที่บันทึกไว้ใหม่หรือโหลดเกมที่บันทึกไว้ที่มีอยู่ 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 =
      PlayGames.getSnapshotsClient(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 =
      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.
          // ...
        }
      });
}

การจัดการความขัดแย้งของเกมที่บันทึกไว้

เมื่อใช้ API สแนปชอตในเกม อุปกรณ์หลายเครื่องอาจอ่านและเขียนในเกมที่บันทึกไว้เดียวกันได้ ในกรณีที่อุปกรณ์ขาดการเชื่อมต่อเครือข่ายชั่วคราวและเชื่อมต่อใหม่ในภายหลัง อาจทําให้ข้อมูลขัดแย้งกัน โดยเกมที่บันทึกไว้ที่จัดเก็บไว้ในอุปกรณ์ในเครื่องของผู้เล่นไม่ซิงค์กับเวอร์ชันระยะไกลที่จัดเก็บไว้ในเซิร์ฟเวอร์ของ Google

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

เมื่อบริการเกมของ Google Play ตรวจพบความขัดแย้งของข้อมูล วิธี SnapshotsClient.DataOrConflict.isConflict() จะแสดงค่า true ในเหตุการณ์นี้ คลาส SnapshotsClient.SnapshotConflict จะแสดงเกมที่บันทึกไว้ 2 เวอร์ชัน ได้แก่

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

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

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

  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() ในเมธอดเมธอด ให้ส่ง SnapshotsClient.SnapshotConflict.getConflictId() เป็นอาร์กิวเมนต์แรก รวมทั้งออบเจ็กต์ SnapshotMetadataChange และ SnapshotContents ที่คุณแก้ไขไปก่อนหน้านี้เป็นอาร์กิวเมนต์ที่ 2 และ 3 ตามลําดับ
  6. หากการเรียก SnapshotsClient.resolveConflict() สําเร็จ API จะจัดเก็บออบเจ็กต์ Snapshot ไปยังเซิร์ฟเวอร์และพยายามเปิดออบเจ็กต์สแนปชอตในอุปกรณ์ในเครื่อง
    • หากมีข้อขัดแย้ง SnapshotsClient.DataOrConflict.isConflict() จะแสดงผล true ในกรณีนี้ เกมของคุณควรกลับไปยังขั้นตอนที่ 2 และทําตามขั้นตอนซ้ําเพื่อแก้ไขสแนปชอตจนกว่าปัญหาจะได้รับการแก้ไข
    • หากไม่มีข้อขัดแย้ง SnapshotsClient.DataOrConflict.isConflict() จะแสดงผล false และออบเจ็กต์ Snapshot จะเปิดให้คุณแก้ไขเกม