Tài liệu này sẽ hướng dẫn bạn cách triển khai trò chơi đã lưu bằng API ảnh chụp nhanh do Dịch vụ trò chơi của Google Play cung cấp. Bạn có thể tìm thấy API trong các gói
com.google.android.gms.games.snapshot
và
com.google.android.gms.games
.
Trước khi bắt đầu
Nếu chưa từng có kinh nghiệm, bạn có thể xem lại các khái niệm về Trò chơi đã lưu.
- Hãy nhớ bật tính năng hỗ trợ trò chơi đã lưu cho trò chơi của bạn trong Google Play Console.
- Tải xuống và xem lại mã mẫu trò chơi đã lưu trên Trang mẫu Android.
- Làm quen với những đề xuất được mô tả trong Danh mục kiểm tra chất lượng.
Tải ứng dụng chụp nhanh
Để có thể sử dụng API ảnh chụp nhanh, trước tiên, trò chơi của bạn phải có được một đối tượng
SnapshotsClient
. Bạn có thể thực hiện việc này bằng cách gọi phương thức Games.getSnapshotsClient()
và truyền vào hoạt động cũng như GoogleSignInAccount
cho người chơi hiện tại. Để tìm hiểu cách truy xuất thông tin tài khoản người chơi, hãy xem phần Đăng nhập vào trò chơi trên Android.
Chỉ định phạm vi ổ đĩa
API ảnh chụp nhanh dựa vào API Google Drive để lưu trữ trò chơi đã lưu. Để truy cập API Drive, ứng dụng của bạn phải chỉ định phạm vi Drive.SCOPE_APPFOLDER
khi tạo ứng dụng đăng nhập bằng Google.
Dưới đây là ví dụ về cách thực hiện việc này trong phương thức
onResume()
cho hoạt động đăng nhập của bạn:
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 } } }); }
Hiển thị trò chơi đã lưu
Bạn có thể tích hợp API ảnh chụp nhanh bất cứ khi nào trò chơi của bạn cung cấp cho người chơi tùy chọn lưu hoặc khôi phục tiến trình của họ. Trò chơi của bạn có thể hiển thị tùy chọn đó tại các điểm lưu/khôi phục được chỉ định, hoặc cho phép người chơi lưu hoặc khôi phục tiến trình bất cứ lúc nào.
Sau khi người chơi chọn tùy chọn lưu/khôi phục cho trò chơi của mình, trò chơi sẽ hiển thị một màn hình nhắc người chơi nhập thông tin cho trò chơi đã lưu mới (nếu muốn), hoặc chọn một trò chơi đã lưu hiện có để khôi phục.
Để đơn giản hóa quá trình phát triển, API ảnh chụp nhanh cung cấp giao diện người dùng (UI) lựa chọn trò chơi đã lưu mặc định mà bạn có thể sử dụng ngay. Giao diện người dùng chọn trò chơi đã lưu cho phép người chơi tạo trò chơi đã lưu mới, xem chi tiết về trò chơi đã lưu hiện có và tải những trò chơi đã lưu trước đó.
Cách mở giao diện người dùng mặc định cho Trò chơi đã lưu:
- Hãy gọi
SnapshotsClient.getSelectSnapshotIntent()
để nhậnIntent
nhằm chạy giao diện người dùng chọn trò chơi đã lưu mặc định. - Gọi
startActivityForResult()
rồi truyềnIntent
đó vào. Nếu cuộc gọi thành công, trò chơi sẽ hiển thị giao diện người dùng cho trò chơi đã lưu, cùng với các tùy chọn mà bạn chỉ định.
Dưới đây là một ví dụ về cách khởi chạy giao diện người dùng lựa chọn trò chơi đã lưu mặc định:
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); } }); }
Nếu người chơi chọn tạo một trò chơi đã lưu mới hoặc tải trò chơi đã lưu hiện có, giao diện người dùng sẽ gửi yêu cầu đến Dịch vụ trò chơi của Google Play. Nếu yêu cầu thành công, Dịch vụ trò chơi của Google Play sẽ trả về thông tin để tạo hoặc khôi phục trò chơi đã lưu thông qua lệnh gọi lại onActivityResult()
. Trò chơi của bạn có thể ghi đè lệnh gọi lại này để kiểm tra xem liệu có lỗi nào xảy ra trong yêu cầu hay không.
Đoạn mã sau đây cho thấy quy trình triển khai mẫu của
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 // ... } } }
Viết trò chơi đã lưu
Cách lưu trữ nội dung vào trò chơi đã lưu:
- Mở ảnh chụp nhanh không đồng bộ qua
SnapshotsClient.open()
. Sau đó, truy xuất đối tượngSnapshot
từ kết quả của tác vụ bằng cách gọiSnapshotsClient.DataOrConflict.getData()
. - Truy xuất thực thể
SnapshotContents
thông quaSnapshotsClient.SnapshotConflict
. - Gọi
SnapshotContents.writeBytes()
để lưu trữ dữ liệu của người chơi ở định dạng byte. - Khi những thay đổi này đã được viết ra, hãy gọi
SnapshotsClient.commitAndClose()
để gửi những thay đổi đó tới máy chủ của Google. Trong lệnh gọi phương thức, trò chơi của bạn có thể cung cấp thêm thông tin để cho Dịch vụ trò chơi của Google Play biết cách hiển thị trò chơi đã lưu này cho người chơi. Thông tin này được thể hiện ở đối tượngSnapshotMetaDataChange
mà trò chơi của bạn tạo bằngSnapshotMetadataChange.Builder
.
Đoạn mã sau đây cho biết cách trò chơi của bạn có thể áp dụng thay đổi cho trò chơi đã lưu:
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); }
Nếu thiết bị của người chơi chưa kết nối mạng khi ứng dụng của bạn gọi SnapshotsClient.commitAndClose()
, Dịch vụ trò chơi của Google Play sẽ lưu trữ dữ liệu trò chơi đã lưu trên thiết bị. Sau khi kết nối lại với thiết bị, Dịch vụ trò chơi của Google Play sẽ đồng bộ hoá những thay đổi đối với trò chơi đã lưu trên bộ nhớ đệm của thiết bị vào máy chủ của Google.
Tải trò chơi đã lưu
Cách truy xuất trò chơi đã lưu cho người chơi hiện đang đăng nhập:
- Mở ảnh chụp nhanh không đồng bộ qua
SnapshotsClient.open()
. Sau đó, truy xuất đối tượngSnapshot
từ kết quả của tác vụ bằng cách gọiSnapshotsClient.DataOrConflict.getData()
. Ngoài ra, trò chơi của bạn cũng có thể truy xuất một ảnh chụp nhanh cụ thể thông qua giao diện người dùng chọn trò chơi đã lưu, như được mô tả trong nội dung phần Hiển thị trò chơi đã lưu. - Truy xuất thực thể
SnapshotContents
thông quaSnapshotsClient.SnapshotConflict
. - Gọi
SnapshotContents.readFully()
để đọc nội dung của ảnh chụp nhanh.
Đoạn mã sau đây minh họa cách tải trò chơi đã lưu cụ thể:
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. // ... } }); }
Xử lý xung đột với trò chơi đã lưu
Khi sử dụng API ảnh chụp nhanh trong ứng dụng trò chơi, nhiều thiết bị có thể đọc và ghi trên cùng một trò chơi đã lưu. Trong trường hợp thiết bị tạm thời mất kết nối mạng và sau đó kết nối lại, việc này có thể gây ra xung đột dữ liệu khi trò chơi đã lưu trên thiết bị cục bộ của người chơi không đồng bộ với phiên bản lưu trữ từ xa trong máy chủ của Google.
API ảnh chụp nhanh cung cấp cơ chế giải quyết xung đột đại diện cho cả hai nhóm trò chơi đã lưu xung đột nhau tại thời điểm đọc, đồng thời cho phép bạn triển khai chiến lược xử lý phù hợp với trò chơi của mình.
Khi phát hiện xung đột dữ liệu trong Dịch vụ trò chơi của Google Play, phương thức SnapshotsClient.DataOrConflict.isConflict()
sẽ trả về giá trị true
. Trong trường hợp này, lớp SnapshotsClient.SnapshotConflict
cung cấp hai phiên bản của trò chơi đã lưu:
- Phiên bản máy chủ: là phiên bản mới nhất mà Dịch vụ trò chơi của Google Play cập nhật chính xác thiết bị của người chơi; và
- Phiên bản cục bộ: Phiên bản sửa đổi được phát hiện trên một trong các thiết bị của người chơi chứa siêu dữ liệu hoặc nội dung xung đột. Phiên bản này có thể không giống với phiên bản mà bạn đã cố lưu lại.
Để giải quyết xung đột, trò chơi của bạn phải quyết định chọn một trong những phiên bản được cung cấp hoặc hợp nhất dữ liệu của hai phiên bản trò chơi đã lưu.
Cách phát hiện và giải quyết các xung đột trò chơi đã lưu:
- Gọi
SnapshotsClient.open()
. Kết quả tác vụ phải chứa một lớpSnapshotsClient.DataOrConflict
. - Gọi phương thức
SnapshotsClient.DataOrConflict.isConflict()
. Nếu kết quả là đúng, bạn hiện có một xung đột cần giải quyết. - Gọi
SnapshotsClient.DataOrConflict.getConflict()
để truy xuất bản saoSnaphotsClient.snapshotConflict
. - Gọi
SnapshotsClient.SnapshotConflict.getConflictId()
để truy xuất mã xung đột, là giá trị nhận dạng duy nhất cho xung đột đã phát hiện. Trò chơi của bạn cần có giá trị này để gửi yêu cầu giải quyết xung đột sau đó. - Gọi
SnapshotsClient.SnapshotConflict.getConflictingSnapshot()
để tải phiên bản cục bộ. - Gọi
SnapshotsClient.SnapshotConflict.getSnapshot()
để tải phiên bản máy chủ. - Để giải quyết xung đột cho trò chơi đã lưu, hãy chọn một phiên bản mà bạn muốn lưu vào máy chủ làm phiên bản cuối cùng, đồng thời chuyển sang phương thức
SnapshotsClient.resolveConflict()
.
Đoạn mã sau đây giải thích và đưa ra ví dụ về cách trò chơi của bạn xử lý các xung đột qua việc chọn trò chơi đã lưu được sửa đổi gần đây nhất làm phiên bản cuối cùng để lưu:
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"); } } }); }
Sửa đổi trò chơi đã lưu để giải quyết xung đột
Nếu bạn muốn hợp nhất dữ liệu từ nhiều trò chơi đã lưu, hoặc sửa đổi Snapshot
hiện có để lưu vào máy chủ dưới dạng phiên bản cuối cùng đã giải quyết, vui lòng làm theo các bước sau:
- Gọi
SnapshotsClient.open()
. - Gọi
SnapshotsClient.SnapshotConflict.getResolutionSnapshotsContent()
để nhận đối tượngSnapshotContents
mới. - Hợp nhất dữ liệu từ
SnapshotsClient.SnapshotConflict.getConflictingSnapshot()
vàSnapshotsClient.SnapshotConflict.getSnapshot()
vào đối tượngSnapshotContents
ở bước trước. - Bạn có thể tạo một bản sao
SnapshotMetadataChange
nếu có bất kỳ thay đổi nào đối với các trường siêu dữ liệu. - Gọi
SnapshotsClient.resolveConflict()
. Trong lệnh gọi phương thức, hãy truyềnSnapshotsClient.SnapshotConflict.getConflictId()
làm đối số đầu tiên, sau đó là đối tượngSnapshotMetadataChange
vàSnapshotContents
mà bạn đã sửa đổi trước đó dưới dạng đối số thứ hai và thứ ba tương ứng. - Nếu lệnh gọi
SnapshotsClient.resolveConflict()
thành công, API sẽ lưu trữ đối tượngSnapshot
vào máy chủ và cố gắng mở đối tượng Chụp nhanh trên thiết bị cục bộ.- Nếu có xung đột,
SnapshotsClient.DataOrConflict.isConflict()
sẽ trả vềtrue
. Trong trường hợp này, trò chơi của bạn sẽ quay lại bước 2 và lặp lại các bước để sửa đổi ảnh chụp nhanh cho đến khi các xung đột được giải quyết. - Nếu không có xung đột nào,
SnapshotsClient.DataOrConflict.isConflict()
sẽ trả vềfalse
và đối tượngSnapshot
sẽ mở để trò chơi của bạn sửa được.
- Nếu có xung đột,