本文說明如何將現有遊戲從遊戲第 1 版 SDK 遷移至遊戲第 2 版 SDK。
事前準備
您可以透過偏好的 IDE (例如 Android Studio) 遷移遊戲。遷移至遊戲第 2 版前,請完成下列步驟:
- 下載並安裝 Android Studio
- 遊戲必須使用遊戲服務第 1 版 SDK。
- 您可以將遊戲升級為使用遊戲服務第 1 版 SDK,以
com.google.android.gms:play-services-games:24.0.0。請勿升級至com.google.android.gms:play-services-games:25.0.0,因為遊戲服務第 1 版 API 已移除。
更新依附元件
在模組的
build.gradle檔案中,找出模組層級依附元件中的這一行。implementation "com.google.android.gms:play-services-games:+"替換成以下程式碼:
implementation "com.google.android.gms:play-services-games-v2:version"將 version 替換為最新版遊戲 SDK。
更新依附元件後,請務必完成本文中的所有步驟。
定義專案 ID
如要在應用程式中加入 Play 遊戲服務 SDK 專案 ID,請完成下列步驟:
在
AndroidManifest.xml檔案中,將下列<meta-data>元素和屬性新增至<application>元素:<manifest> <application> <meta-data android:name="com.google.android.gms.games.APP_ID" android:value="@string/game_services_project_id"/> </application> </manifest>使用遊戲的遊戲服務專案 ID 為值,定義字串資源參考資料
@string/game_services_project_id。遊戲服務專案 ID 會在 Google Play 管理中心「設定」頁面的遊戲名稱下方顯示。在
res/values/strings.xml檔案中,新增字串資源參考資料,並將專案 ID 設為該值。例如:<!-- res/values/strings.xml --> <resources> <!-- Replace 0000000000 with your game’s project id. Example value shown above. --> <string translatable="false" name="game_services_project_id"> 0000000000 </string> </resources>
遷移路徑
遊戲的正確遷移路徑取決於如何實作 Play Games 服務第 1 版,以及如何處理玩家身分。為確保順利轉換並避免遺失玩家資料,請找出最符合現有設定的情境,然後按照對應步驟操作。
做法 1:針對 IGA 繫結至 Play 遊戲服務玩家 ID 的遊戲
這個情境適用於遊戲,這些遊戲使用 Play 遊戲服務 Player ID做為玩家遊戲內帳戶 (IGA) 的唯一 ID,且先前未要求或儲存 OpenID。主要挑戰是將現有 IGA 連結至主要 ID (OpenID),同時保留與玩家進度的連結。
遷移流程包含下列步驟:
- 遊戲推出時,Play Games 服務第 2 版 SDK 會自動且無聲地驗證平台。
顯示登入畫面,其中包含「使用 Google 帳戶登入」按鈕,取代「Google Play」按鈕。例如,請參閱 CredManBridge.java。
CredManBridge.java
package com.wickedcube.trivialkart; import android.accounts.Account; import android.content.Context; import android.util.Log; import android.os.CancellationSignal; import androidx.credentials.CredentialManager; import androidx.credentials.GetCredentialRequest; import androidx.credentials.GetCredentialResponse; import androidx.credentials.exceptions.GetCredentialException; import androidx.credentials.exceptions.NoCredentialException; import com.google.android.libraries.identity.googleid.GetGoogleIdOption; import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential; import com.google.android.gms.auth.api.identity.AuthorizationClient; import com.google.android.gms.auth.api.identity.AuthorizationRequest; import com.google.android.gms.auth.api.identity.AuthorizationResult; import com.google.android.gms.common.api.ApiException; import com.google.android.gms.auth.api.identity.Identity; import com.google.android.gms.common.api.Scope; import com.unity3d.player.UnityPlayer; import java.util.Collections; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.Executors;public class CredManBridge {
// --- MODE 1: SILENT SIGN-IN (Called on Awake) --- // Tries to auto-select an authorized account. If it fails, it does NOT show UI. public static void signInSilent(Context context, String webClientId) { CredentialManager credentialManager = CredentialManager.create(context); CancellationSignal cancellationSignal = new CancellationSignal(); Executor executor = Executors.newSingleThreadExecutor();
Log.d("CredMan", "Attempting Silent Sign-In..."); GetGoogleIdOption silentOption = new GetGoogleIdOption.Builder() .setFilterByAuthorizedAccounts(true) // Strict: Only authorized accounts .setServerClientId(webClientId) .setAutoSelectEnabled(true) // Auto-select if possible .build(); GetCredentialRequest silentRequest = new GetCredentialRequest.Builder() .addCredentialOption(silentOption) .build(); credentialManager.getCredentialAsync( context, silentRequest, cancellationSignal, executor, new androidx.credentials.CredentialManagerCallback<GetCredentialResponse, GetCredentialException>() { @Override public void onResult(GetCredentialResponse result) { Log.d("CredMan", "Silent Sign-In Successful!"); handleSignInResult(context, result, webClientId); } @Override public void onError(GetCredentialException e) { // Send a specific error code so Unity knows to just stay on the Start Screen Log.d("CredMan", "Silent sign-in failed. Keeping UI hidden."); UnityPlayer.UnitySendMessage("AuthManager", "OnSignInError", "SilentFailed"); } } );}
// --- MODE 2: INTERACTIVE SIGN-IN (Called on Button Click) --- // Forces the Account Selection / "Add Account" sheet to appear. public static void signInInteractive(Context context, String webClientId) { CredentialManager credentialManager = CredentialManager.create(context); CancellationSignal cancellationSignal = new CancellationSignal(); Executor executor = Executors.newSingleThreadExecutor();
Log.d("CredMan", "Starting Interactive Sign-In..."); GetGoogleIdOption interactiveOption = new GetGoogleIdOption.Builder() .setFilterByAuthorizedAccounts(false) // Show ALL accounts (and "Add Account") .setServerClientId(webClientId) .setAutoSelectEnabled(false) // Force the UI to show .build(); GetCredentialRequest interactiveRequest = new GetCredentialRequest.Builder() .addCredentialOption(interactiveOption) .build(); credentialManager.getCredentialAsync( context, interactiveRequest, cancellationSignal, executor, new androidx.credentials.CredentialManagerCallback<GetCredentialResponse, GetCredentialException>() { @Override public void onResult(GetCredentialResponse result) { Log.d("CredMan", "Interactive Sign-In Successful!"); handleSignInResult(context, result, webClientId); } @Override public void onError(GetCredentialException e) { Log.e("CredMan", "Interactive Sign-In Canceled or Failed", e); UnityPlayer.UnitySendMessage("AuthManager", "OnSignInError", "Canceled"); } } );}
private static void handleSignInResult(Context context, GetCredentialResponse result, String webClientId) { try { GoogleIdTokenCredential credential = GoogleIdTokenCredential.createFrom(result.getCredential().getData()); String email = credential.getId();
Account account = new Account(email, "com.google"); // Requesting GAMES_LITE scope to check for pre-existing V1 grants List<Scope> requestedScopes = Collections.singletonList(new Scope("https://www.googleapis.com/auth/games_lite")); AuthorizationRequest authRequest = new AuthorizationRequest.Builder() .setRequestedScopes(requestedScopes) .setAccount(account) .requestOfflineAccess(webClientId) .build(); AuthorizationClient authClient = Identity.getAuthorizationClient(context); authClient.authorize(authRequest) .addOnSuccessListener(authorizationResult -> { if (authorizationResult.getServerAuthCode() != null) { // CASE 1: RETURNING USER (Success) // The user has already granted GAMES_LITE in the past. // We got the code directly without showing UI. Log.i("CredMan", "PGS v1: Existing grant found. Returning user detected. Auth Code retrieved."); UnityPlayer.UnitySendMessage("AuthManager", "OnSignInSuccess", authorizationResult.getServerAuthCode()); } else if (authorizationResult.hasResolution()) { // CASE 2: NEW USER (PendingIntent) // The user has NOT granted GAMES_LITE before. The API returned a PendingIntent // (authorizationResult.getPendingIntent()) to show the consent screen. // As per your flow, we DISCARD this intent and do not show UI. Log.i("CredMan", "PGS v1: No existing grant (PendingIntent returned). This is a NEW user or they revoked access."); Log.i("CredMan", "PGS v1: Discarding PendingIntent. Proceeding as New User."); // Notify Unity that this is a "New User" so it can trigger V2 logic instead of failing UnityPlayer.UnitySendMessage("AuthManager", "OnSignInError", "NewUser_NoGrant"); } else { // Edge Case: No code and no resolution? Log.e("CredMan", "PGS v1: Authorization success but no Auth Code or Resolution returned."); UnityPlayer.UnitySendMessage("AuthManager", "OnSignInError", "No Auth Code returned"); } }) .addOnFailureListener(e -> { // CASE 3: GENERIC FAILURE Log.e("CredMan", "PGS v1: Authorization failed completely.", e); UnityPlayer.UnitySendMessage("AuthManager", "OnSignInError", "Authorization Failed: " + e.getMessage()); }); } catch (Exception e) { UnityPlayer.UnitySendMessage("AuthManager", "OnSignInError", "Parsing Error: " + e.getMessage()); }} }
當玩家輕觸「使用 Google 帳戶登入」按鈕並選取 Google 帳戶時,請擷取兩個不同的 ID:
OpenID,這是繫結 IGA 的主要 ID。- 使用
GAMES_LITE範圍擷取的 Play 遊戲服務Player ID,可在後端系統中查詢玩家的 IGA 並執行繫結。詳情請參閱「擷取Player ID」。
在後續啟動遊戲時,透過「使用 Google 帳戶登入」流程存取 IGA,遊戲不必使用
Player ID做為主要 ID。
擷取 Player ID
您可以使用遊戲用戶端實作項目執行步驟 3。
- 呼叫 Android Credential Manager API,讓使用者透過 Google 帳戶登入。
- 使用者完成「使用 Google 帳戶登入」流程並選取 Google 帳戶後,您會收到包含 ID 權杖和電子郵件地址的結果物件。
- 從電子郵件地址建構 Account 物件。
- 使用
GAMES_LITE範圍和帳戶呼叫 Authorization API。 - 如果帳戶在
GAMES_LITE範圍內已有授權,Authorization API 會直接在回應物件中傳回權杖:- 使用回應權杖呼叫 Play Games 服務伺服器,並擷取 Play Games 服務
Player ID。 - 確認 Play 遊戲服務
Player ID是否已連結遊戲內帳戶。- 這表示使用者是從 Play 遊戲服務第 1 版回訪。
- 將新的 Gaia ID 連結至先前的 Play Games 服務第 1 版帳戶。
- 使用回應權杖呼叫 Play Games 服務伺服器,並擷取 Play Games 服務
- 如果帳戶在
GAMES_LITE範圍內沒有預先存在的授權,Authorization API 會傳回PendingIntent:- 這表示使用者沒有 Play Games 服務第 1 版的現有帳戶。
- 安全捨棄
PendingIntent,且不顯示任何 UI。
方法 2:遊戲已將 IGA 繫結至 OpenID
這個群組的開發人員遷移路徑最簡單。如果遊戲內帳戶已主要繫結至 OpenID,您只需要按照步驟,從第 1 版執行標準技術 SDK 遷移至第 2 版。
從已淘汰的 Google 登入服務遷移
將 GoogleSignInClient 類別替換為 GamesSignInClient 類別。
Java
找出 GoogleSignInClient 類別的檔案。
import com.google.android.gms.auth.api.signin.GoogleSignIn;
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
// ... existing code
@Override
public void onCreate(@Nullable Bundle bundle) {
super.onCreate(bundle);
// ... existing code
GoogleSignInOptions signInOption =
new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN).build();
// Client used to sign in to Google services
GoogleSignInClient googleSignInClient =
GoogleSignIn.getClient(this, signInOptions);
}
並更新為以下程式碼:
import com.google.android.gms.games.PlayGamesSdk;
import com.google.android.gms.games.PlayGames;
import com.google.android.gms.games.GamesSignInClient;
// ... existing code
@Override
public void onCreate(){
super.onCreate();
PlayGamesSdk.initialize(this);
// Client used to sign in to Google services
GamesSignInClient gamesSignInClient =
PlayGames.getGamesSignInClient(getActivity());
}
Kotlin
找出 GoogleSignInClient 類別的檔案。
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInClient
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
// ... existing code
val signInOptions = GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN
// ... existing code
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val googleSignInClient: GoogleSignInClient =
GoogleSignIn.getClient(this, signInOptions)
}
並更新為以下程式碼:
import com.google.android.gms.games.PlayGames
import com.google.android.gms.games.PlayGamesSdk
import com.google.android.gms.games.GamesSignInClient
// ... existing code
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
PlayGamesSdk.initialize(this)
// client used to sign in to Google services
val gamesSignInClient: GamesSignInClient =
PlayGames.getGamesSignInClient(this)
}
更新 GoogleSignIn 程式碼
遊戲服務第 2 版 SDK 不支援 GoogleSignIn API。請將 GoogleSignIn API 程式碼替換為 GamesSignInClient API,如下方範例所示。
如要要求伺服器端存取權杖,請使用 GamesSignInClient.requestServerSideAccess() 方法。詳情請參閱「更新伺服器端存取權類別」。
Java
找出 GoogleSignIn 類別的檔案。
// Request code used when invoking an external activity.
private static final int RC_SIGN_IN = 9001;
private boolean isSignedIn() {
GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(this);
GoogleSignInOptions signInOptions =
GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN;
return GoogleSignIn.hasPermissions(account, signInOptions.getScopeArray());
}
private void signInSilently() {
GoogleSignInOptions signInOptions =
GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN;
GoogleSignInClient signInClient = GoogleSignIn.getClient(this, signInOptions);
signInClient
.silentSignIn()
.addOnCompleteListener(
this,
task -> {
if (task.isSuccessful()) {
// The signed-in account is stored in the task's result.
GoogleSignInAccount signedInAccount = task.getResult();
showSignInPopup();
} else {
// Perform interactive sign in.
startSignInIntent();
}
});
}
private void startSignInIntent() {
GoogleSignInClient signInClient = GoogleSignIn.getClient(this,
GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN);
Intent intent = signInClient.getSignInIntent();
startActivityForResult(intent, RC_SIGN_IN);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RC_SIGN_IN) {
GoogleSignInResult result =
Auth.GoogleSignInApi.getSignInResultFromIntent(data);
if (result.isSuccess()) {
// The signed-in account is stored in the result.
GoogleSignInAccount signedInAccount = result.getSignInAccount();
showSignInPopup();
} else {
String message = result.getStatus().getStatusMessage();
if (message == null || message.isEmpty()) {
message = getString(R.string.signin_other_error);
}
new AlertDialog.Builder(this).setMessage(message)
.setNeutralButton(android.R.string.ok, null).show();
}
}
}
private void showSignInPopup() {
Games.getGamesClient(requireContext(), signedInAccount)
.setViewForPopups(contentView)
.addOnCompleteListener(
task -> {
if (task.isSuccessful()) {
logger.atInfo().log("SignIn successful");
} else {
logger.atInfo().log("SignIn failed");
}
});
}
並更新為以下程式碼:
private void signInSilently() {
gamesSignInClient.isAuthenticated().addOnCompleteListener(isAuthenticatedTask -> {
boolean isAuthenticated =
(isAuthenticatedTask.isSuccessful() &&
isAuthenticatedTask.getResult().isAuthenticated());
if (isAuthenticated) {
// Continue with Play Games Services
} else {
// If authentication fails, either disable Play Games Services
// integration or
// display a login button to prompt players to sign in.
// Use`gamesSignInClient.signIn()` when the login button is clicked.
}
});
}
@Override
protected void onResume() {
super.onResume();
// When the activity is inactive, the signed-in user's state can change;
// therefore, silently sign in when the app resumes.
signInSilently();
}Kotlin
找出 GoogleSignIn 類別的檔案。
// Request codes we use when invoking an external activity.
private val RC_SIGN_IN = 9001
// ... existing code
private fun isSignedIn(): Boolean {
val account = GoogleSignIn.getLastSignedInAccount(this)
val signInOptions = GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN
return GoogleSignIn.hasPermissions(account, *signInOptions.scopeArray)
}
private fun signInSilently() {
val signInOptions = GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN
val signInClient = GoogleSignIn.getClient(this, signInOptions)
signInClient.silentSignIn().addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
// The signed-in account is stored in the task's result.
val signedInAccount = task.result
// Pass the account to showSignInPopup.
showSignInPopup(signedInAccount)
} else {
// Perform interactive sign in.
startSignInIntent()
}
}
}
private fun startSignInIntent() {
val signInClient = GoogleSignIn.getClient(this, GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN)
val intent = signInClient.signInIntent
startActivityForResult(intent, RC_SIGN_IN)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == RC_SIGN_IN) {
val result = Auth.GoogleSignInApi.getSignInResultFromIntent(data)
if (result.isSuccess) {
// The signed-in account is stored in the result.
val signedInAccount = result.signInAccount
showSignInPopup(signedInAccount) // Pass the account to showSignInPopup.
} else {
var message = result.status.statusMessage
if (message == null || message.isEmpty()) {
message = getString(R.string.signin_other_error)
}
AlertDialog.Builder(this)
.setMessage(message)
.setNeutralButton(android.R.string.ok, null)
.show()
}
}
}
private fun showSignInPopup(signedInAccount: GoogleSignInAccount) {
// Add signedInAccount parameter.
Games.getGamesClient(this, signedInAccount)
.setViewForPopups(contentView) // Assuming contentView is defined.
.addOnCompleteListener { task ->
if (task.isSuccessful) {
logger.atInfo().log("SignIn successful")
} else {
logger.atInfo().log("SignIn failed")
}
}
}並更新為以下程式碼:
private fun signInSilently() {
gamesSignInClient.isAuthenticated.addOnCompleteListener { isAuthenticatedTask ->
val isAuthenticated = isAuthenticatedTask.isSuccessful &&
isAuthenticatedTask.result.isAuthenticated
if (isAuthenticated) {
// Continue with Play Games Services
} else {
// To handle a user who is not signed in, either disable Play Games Services integration
// or display a login button. Selecting this button calls `gamesSignInClient.signIn()`.
}
}
}
override fun onResume() {
super.onResume()
// Since the state of the signed in user can change when the activity is
// not active it is recommended to try and sign in silently from when the
// app resumes.
signInSilently()
}新增 GamesSignInClient 程式碼
如果玩家成功通過驗證,請從遊戲中移除 Play 遊戲服務登入按鈕。如果使用者選擇不要在遊戲啟動時驗證,請繼續顯示有 Play Games 服務圖示的按鈕,並使用 GamesSignInClient.signIn() 啟動登入程序。
Java
private void startSignInIntent() {
gamesSignInClient
.signIn()
.addOnCompleteListener( task -> {
if (task.isSuccessful() && task.getResult().isAuthenticated()) {
// sign in successful
} else {
// sign in failed
}
});
}Kotlin
private fun startSignInIntent() {
gamesSignInClient
.signIn()
.addOnCompleteListener { task ->
if (task.isSuccessful && task.result.isAuthenticated) {
// sign in successful
} else {
// sign in failed
}
}
}移除登出代碼
移除 GoogleSignInClient.signOut 的程式碼。
移除下列範例所示的程式碼:
Java
// ... existing code
private void signOut() {
GoogleSignInClient signInClient = GoogleSignIn.getClient(this,
GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN);
signInClient.signOut().addOnCompleteListener(this,
new OnCompleteListener() {
@Override
public void onComplete(@NonNull Task task) {
// At this point, the user is signed out.
}
});
} Kotlin
// ... existing code
private fun signOut() {
val signInClient = GoogleSignIn.getClient(this, GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN)
signInClient.signOut().addOnCompleteListener(this) {
// At this point, the user is signed out.
}
}確認驗證是否成功
加入下列程式碼,檢查您是否已自動驗證,並新增自訂邏輯 (如有)。
Java
private void checkIfAutomaticallySignedIn() {
gamesSignInClient.isAuthenticated().addOnCompleteListener(isAuthenticatedTask -> {
boolean isAuthenticated =
(isAuthenticatedTask.isSuccessful() &&
isAuthenticatedTask.getResult().isAuthenticated());
if (isAuthenticated) {
// Continue with Play Games Services
// If your game requires specific actions upon successful sign-in,
// you can add your custom logic here.
// For example, fetching player data or updating UI elements.
} else {
// Show a login button to ask players to sign-in. Clicking it should
// call GamesSignInClient.signIn().
}
});
}
Kotlin
private void checkIfAutomaticallySignedIn() {
gamesSignInClient.isAuthenticated()
.addOnCompleteListener { task ->
val isAuthenticated = task.isSuccessful && task.result?.isAuthenticated ?: false
if (isAuthenticated) {
// Continue with Play Games Services
} else {
// Disable your integration or show a login button
}
}
}
更新用戶端類別名稱和方法
遷移至遊戲服務第 2 版後,取得用戶端類別名稱的方法會有所不同。請改用對應的 PlayGames.getxxxClient() 方法,而非 Games.getxxxClient() 方法。
舉例來說,針對 LeaderboardsClient,請使用 PlayGames.getLeaderboardsClient(),而非 Games.getLeaderboardsClient() 方法。
移除與 GamesClient 和 GamesMetadataClient 類別相關的任何程式碼,因為遊戲 v2 中沒有任何替代類別。
Java
找出 LeaderboardsClient 的程式碼。
import com.google.android.gms.games.LeaderboardsClient;
import com.google.android.gms.games.Games;
@Override
public void onCreate(@Nullable Bundle bundle) {
super.onCreate(bundle);
// Get the leaderboards client using Play Games services.
LeaderboardsClient leaderboardsClient = Games.getLeaderboardsClient(this,
GoogleSignIn.getLastSignedInAccount(this));
}
並更新為以下程式碼:
import com.google.android.gms.games.LeaderboardsClient;
import com.google.android.gms.games.PlayGames;
@Override
public void onCreate(@Nullable Bundle bundle) {
super.onCreate(bundle);
// Get the leaderboards client using Play Games services.
LeaderboardsClient leaderboardsClient = PlayGames.getLeaderboardsClient(getActivity());
}
Kotlin
找出 LeaderboardsClient 的程式碼。
import com.google.android.gms.games.LeaderboardsClient
import com.google.android.gms.games.Games
// Initialize the variables.
private lateinit var leaderboardsClient: LeaderboardsClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
leaderboardsClient = Games.getLeaderboardsClient(this,
GoogleSignIn.getLastSignedInAccount(this))
}並更新為以下程式碼:
import com.google.android.gms.games.LeaderboardsClient
import com.google.android.gms.games.PlayGames
// Initialize the variables.
private lateinit var leaderboardsClient: LeaderboardsClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
leaderboardsClient = PlayGames.getLeaderboardsClient(this)
}同樣地,請針對下列用戶端使用對應的方法:AchievementsClient、EventsClient、GamesSignInClient、PlayerStatsClient、RecallClient、SnapshotsClient 或 PlayersClient。
更新伺服器端存取權類別
如要要求伺服器端存取權杖,請使用 GamesSignInClient.requestServerSideAccess() 方法,而非 GoogleSignInAccount.getServerAuthCode() 方法。
詳情請參閱「傳送伺服器授權碼」。
以下範例說明如何要求伺服器端存取權杖。
Java
找到 GoogleSignInOptions 類別的程式碼。
private static final int RC_SIGN_IN = 9001;
private GoogleSignInClient googleSignInClient;
private void startSignInForAuthCode() {
/** Client ID for your backend server. */
String webClientId = getString(R.string.webclient_id);
GoogleSignInOptions signInOption = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN)
.requestServerAuthCode(webClientId)
.build();
GoogleSignInClient signInClient = GoogleSignIn.getClient(this, signInOption);
Intent intent = signInClient.getSignInIntent();
startActivityForResult(intent, RC_SIGN_IN);
}
/** Auth code to send to backend server */
private String mServerAuthCode;
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RC_SIGN_IN) {
GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
if (result.isSuccess()) {
mServerAuthCode = result.getSignInAccount().getServerAuthCode();
} else {
String message = result.getStatus().getStatusMessage();
if (message == null || message.isEmpty()) {
message = getString(R.string.signin_other_error);
}
new AlertDialog.Builder(this).setMessage(message)
.setNeutralButton(android.R.string.ok, null).show();
}
}
}
並更新為以下程式碼:
private void startRequestServerSideAccess() {
GamesSignInClient gamesSignInClient = PlayGames.getGamesSignInClient(this);
gamesSignInClient
.requestServerSideAccess(OAUTH_2_WEB_CLIENT_ID,
/* forceRefreshToken= */ false, /* additional AuthScope */ scopes)
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
AuthResponse authresp = task.getResult();
// Send the authorization code as a string and a
// list of the granted AuthScopes that were granted by the
// user. Exchange for an access token.
// Verify the player with Play Games Services REST APIs.
} else {
// Authentication code retrieval failed.
}
});
}
Kotlin
找到 GoogleSignInOptions 類別的程式碼。
// ... existing code
private val RC_SIGN_IN = 9001
private lateinit var googleSignInClient: GoogleSignInClient
// Auth code to send to backend server.
private var mServerAuthCode: String? = null
private fun startSignInForAuthCode() {
// Client ID for your backend server.
val webClientId = getString(R.string.webclient_id)
val signInOption = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN)
.requestServerAuthCode(webClientId)
.build()
googleSignInClient = GoogleSignIn.getClient(this, signInOption)
val intent = googleSignInClient.signInIntent
startActivityForResult(intent, RC_SIGN_IN)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == RC_SIGN_IN) {
val result = Auth.GoogleSignInApi.getSignInResultFromIntent(data)
if (result.isSuccess) {
mServerAuthCode = result.signInAccount.serverAuthCode
} else {
var message = result.status.statusMessage
if (message == null || message.isEmpty()) {
message = getString(R.string.signin_other_error)
}
AlertDialog.Builder(this).setMessage(message)
.setNeutralButton(android.R.string.ok, null).show()
}
}
}
並更新為以下程式碼:
private void startRequestServerSideAccess() {
GamesSignInClient gamesSignInClient = PlayGames.getGamesSignInClient(this);
gamesSignInClient
.requestServerSideAccess(OAUTH_2_WEB_CLIENT_ID, /* forceRefreshToken= */ false,
/* additional AuthScope */ scopes)
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
AuthResponse authresp = task.getResult();
// Send the authorization code as a string and a
// list of the granted AuthScopes that were granted by the
// user. Exchange for an access token.
// Verify the player with Play Games Services REST APIs.
} else {
// Authentication code retrieval failed.
}
});
}
從 GoogleApiClient 遷移
較舊的現有遊戲整合功能可能因 Play 遊戲服務 SDK 的 GoogleApiClient API 變化版本而異。此設定已於 2017 年年底淘汰,並由「無網路連線」的用戶端取代。如要遷移,可以使用對等的「無網路連線」取代 GoogleApiClient 類別。下表列出遊戲第 1 版到第 2 版的常見類別對應:
| games v2 (目前) | games v1 (舊版) |
|---|---|
| com.google.android.gms.games.AchievementsClient | com.google.android.gms.games.achievement.Achievements |
| com.google.android.gms.games.LeaderboardsClient | com.google.android.gms.games.leaderboard.Leaderboard |
| com.google.android.gms.games.SnapshotsClient | com.google.android.gms.games.snapshot.Snapshots |
| com.google.android.gms.games.PlayerStatsClient | com.google.android.gms.games.stats.PlayerStats |
| com.google.android.gms.games.PlayersClient | com.google.android.gms.games.Players |
| com.google.android.gms.games.GamesClientStatusCodes | com.google.android.gms.games.GamesStatusCodes |
建構並執行遊戲
如要在 Android Studio 中建構及執行,請參閱「建構並執行應用程式」。
測試遊戲
請測試遊戲,確保遊戲功能符合設計。您執行的測試取決於遊戲功能。
以下列出常見的測試。
成功登入。
自動登入功能正常運作。使用者啟動遊戲時,應登入 Play 遊戲服務。
系統會顯示歡迎彈出式視窗。
歡迎彈出式視窗範例 (按一下可放大)。 系統會顯示成功記錄訊息。在終端機中執行下列指令:
adb logcat | grep com.google.android.
以下範例顯示成功記錄訊息:
[
$PlaylogGamesSignInAction$SignInPerformerSource@e1cdecc number=1 name=GAMES_SERVICE_BROKER>], returning true for shouldShowWelcomePopup. [CONTEXT service_id=1 ]
確保 UI 元件一致性。
在 Play 遊戲服務使用者介面 (UI) 中,彈出式視窗、排行榜和成就能在各種螢幕大小和方向下正確且一致地顯示。
Play Games 服務使用者介面中沒有登出選項。
確認您能順利擷取玩家 ID,且伺服器端功能運作正常 (如適用)。
如果遊戲使用伺服器端驗證,請徹底測試
requestServerSideAccess流程。確認伺服器收到驗證碼,並可交換存取權杖。測試網路錯誤的成功和失敗情境,以及無效的client ID情境。
如果遊戲使用下列任何功能,請測試這些功能,確保遷移後運作方式與遷移前相同:
- 排行榜:提交分數並查看排行榜。確認玩家名稱和分數的排名與顯示方式是否正確。
- 成就:解鎖成就,並確認成就已正確記錄及顯示在 Play Games 使用者介面中。
- 遊戲進度存檔:如果遊戲使用遊戲進度存檔,請確保儲存和載入遊戲進度時不會發生任何問題。因此請務必在多部裝置上測試,並在應用程式更新後再次測試。
遷移後工作
遷移至 Games v2 後,請完成下列步驟。
發布遊戲
建構 APK,並在 Play 管理中心發布遊戲。
- 在 Android Studio 選單中,依序選取「Build」>「Build Bundle(s) / APK(s)」>「Build APK(s)」。
- 發布遊戲。 詳情請參閱「 透過 Play 管理中心發布私人應用程式」。