本文說明如何將現有遊戲從遊戲第 1 版 SDK 遷移至遊戲第 2 版 SDK。適用於 Unity 的 Play Games 外掛程式 10 以下版本,使用的是遊戲服務第 1 版 SDK。
事前準備
- 請確認您已設定 Play 管理中心並安裝 Unity 編輯器。
下載 Unity 適用的 Google Play 遊戲外掛程式
如要使用 Play Games 服務的最新功能,請下載並安裝最新版外掛程式。請前往 GitHub 存放區下載。
移除舊外掛程式
在 Unity 編輯器中,移除下列資料夾或檔案。
Assets/GooglePlayGames Assets/GeneratedLocalRepo/GooglePlayGames Assets/Plugins/Android/GooglePlayGamesManifest.androidlib Assets/Plugins/Android
將新外掛程式匯入 Unity 專案
如要將外掛程式匯入 Unity 專案,請按照下列步驟操作:
- 開啟遊戲專案。
- 在 Unity 編輯器中,依序點選「Assets」>「Import Package」>「Custom Package」,將下載的
unitypackage檔案匯入專案的資產。 確認目前的建構平台已設為「Android」。
依序點選主選單的「File」>「Build Settings」。
選取「Android」,然後點選「Switch Platform」。
依序前往「Window」>「Google Play Games」,應該會顯示新的選單項目。如果沒有顯示,請依序點選「Assets」>「Refresh」重新整理資產,再嘗試重新設定建構平台。
在 Unity 編輯器中,依序點選「File」>「Build Settings」>「Player Settings」>「Other Settings」。
在「目標 API 級別」方塊中選取版本。
在「Scripting backend」(指令碼後端) 方塊中輸入
IL2CPP。在「目標架構」方塊中選取值。
請記下套件名稱 package_name,稍後會用到這項資訊。
Unity 專案中的「Player settings」。
遷移路徑
遊戲的正確遷移路徑取決於如何實作 Play Games 服務第 1 版,以及如何處理玩家身分。為確保順利轉換並避免遺失玩家資料,請找出最符合現有設定的情境,然後按照對應步驟操作。
做法 1:針對 IGA 繫結至 Play 遊戲服務玩家 ID 的遊戲
這個情境適用於遊戲,這些遊戲使用 Play 遊戲服務 Player ID做為玩家遊戲內帳戶 (IGA) 的唯一 ID,且先前未要求或儲存 OpenID。主要挑戰是將現有 IGA 連結至主要 ID (OpenID),同時保留與玩家進度的連結。
遷移流程包含下列步驟:
- 遊戲推出時,Play Games 服務第 2 版 SDK 會自動且無聲地驗證平台。
遊戲會顯示登入畫面。這個畫面必須顯示「使用 Google 登入」 (SiWG) 按鈕,取代「Google Play」按鈕。整合步驟如下:
將 CredManBridge.java 下載至資料夾。這個 Java 類別可做為 Unity 與
androidx.credentials程式庫之間的橋樑。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); }</getcredentialresponse,>
@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()); } } }
整合 Credential Manager:
- 搭配
setFilterByAuthorizedAccounts(true)使用GetGoogleIdOption進行無聲登入,只登入先前授權過應用程式的使用者。 - 使用
setFilterByAuthorizedAccounts(false)進行互動式登入,讓使用者選取帳戶或新增帳戶。
- 搭配
範圍要求:
- 取得基本 Google 憑證後,系統會建立
AuthorizationRequest要求特定舊版範圍:https://www.googleapis.com/auth/games_lite。 - 這個範圍非常重要,因為它會授予伺服器權限,可查詢使用者的舊版 PlayerID。
- 取得基本 Google 憑證後,系統會建立
結果處理:
- 如果使用者授予權限 (或先前已授予),橋接器會將
ServerAuthCode傳回 Unity。 - 如果使用者尚未授予權限 (新使用者情境),API 會傳回
PendingIntent。在這個範例中,意圖會遭到捨棄,且使用者會被視為新使用者,以簡化流程。
- 如果使用者授予權限 (或先前已授予),橋接器會將
如要支援 Credential Manager 和 Google Identity 服務,請務必將下列依附元件新增至
mainTemplate.gradleGradle 設定。dependencies { // Standard Unity dependencies implementation fileTree(dir: 'libs', include: ['*.jar']) // Credential Manager and Identity Libraries implementation 'androidx.credentials:credentials:1.3.0' implementation 'androidx.credentials:credentials-play-services-auth:1.3.0' implementation 'com.google.android.libraries.identity.googleid:googleid:1.1.1' // Play Services Auth for legacy scope handling implementation 'com.google.android.gms:play-services-auth:21.2.0' }
- Credential Manager:處理核心身分自動化調度管理和帳戶選取的使用者介面。
- GoogleID 程式庫:專門提供
GetGoogleIdOption,用於擷取OpenIDConnect 權杖。 - Play 服務驗證:必須維持相容性,並要求
GAMES_LITE範圍,才能擷取舊版Player ID。
當玩家輕觸「使用 Google 登入」按鈕並選取 Google 帳戶時,遊戲必須擷取兩個不同的 ID:
OpenID,這是繫結 IGA 的主要 ID。- 使用
GAMES_LITE範圍擷取的 Play Games 服務Player ID,可在後端系統中查詢玩家的 IGA 並執行繫結。
在後續的遊戲啟動中,玩家可以透過 SiWG 流程存取 IGA,遊戲不必使用
Player ID做為主要 ID。
您可以使用遊戲用戶端實作項目執行步驟 4。
- 開發人員呼叫 Android Credential Manager API,讓使用者透過 Google 帳戶登入。
- 使用者完成「使用 Google 帳戶登入」程序並選取 Google 帳戶後,開發人員會收到結果物件,其中包含 ID 權杖和電子郵件地址。
- 開發人員會從電子郵件地址建構帳戶物件。
- 開發人員會使用
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 版。
更新自動登入程式碼
將 PlayGamesClientConfiguration 初始化類別替換為 PlayGamesPlatform.Instance.Authenticate() 類別。不需要初始化和啟用 PlayGamesPlatform。呼叫 PlayGamesPlatform.Instance.Authenticate() 會擷取自動登入的結果。如要進一步瞭解整合 Play 遊戲服務第 2 版時建議使用的驗證流程,請參閱理想驗證流程的使用者體驗指南。
C#
在 Unity 編輯器中,找出具有 PlayGamesClientConfiguration 類別的檔案。
using GooglePlayGames;
using GooglePlayGames.BasicApi;
using UnityEngine.SocialPlatforms;
public void Start() {
PlayGamesClientConfiguration config =
new PlayGamesClientConfiguration.Builder()
// Enables saving game progress
.EnableSavedGames()
// Requests the email address of the player be available
// will bring up a prompt for consent
.RequestEmail()
// Requests a server auth code be generated so it can be passed to an
// associated backend server application and exchanged for an OAuth token
.RequestServerAuthCode(false)
// Requests an ID token be generated. This OAuth token can be used to
// identify the player to other services such as Firebase.
.RequestIdToken()
.Build();
PlayGamesPlatform.InitializeInstance(config);
// recommended for debugging:
PlayGamesPlatform.DebugLogEnabled = true;
// Activate the Google Play Games platform
PlayGamesPlatform.Activate();
}
並更新為以下程式碼:
using GooglePlayGames;
public void Start() {
PlayGamesPlatform.Instance.Authenticate(ProcessAuthentication);
}
internal void ProcessAuthentication(SignInStatus status) {
if (status == SignInStatus.Success) {
// Continue with Play Games Services
} else {
// Disable your integration with Play Games Services or show a login
// button to ask users to sign-in. Clicking it should call
// PlayGamesPlatform.Instance.ManuallyAuthenticate(ProcessAuthentication).
}
}
選擇社群平台
如要選擇社群平台,請參閱「選擇社群平台」。
擷取伺服器驗證碼
如要取得伺服器端存取碼,請參閱「擷取伺服器驗證碼」。
移除登出代碼
移除登出程式碼。Play Games 服務不再需要遊戲中的登出按鈕。
移除下列範例所示的程式碼:
C#
// sign out
PlayGamesPlatform.Instance.SignOut();
測試遊戲
請測試遊戲,確保遊戲功能符合設計。您執行的測試取決於遊戲功能。
以下列出常見的測試。
成功登入。
自動登入功能正常運作。使用者啟動遊戲時,應登入 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 SDK 後,請完成下列步驟。