遷移至 Play 遊戲服務第 2 版 (Unity)

本文說明如何將現有遊戲從遊戲第 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 專案

如要將外掛程式匯入 Unity 專案,請按照下列步驟操作:

  1. 開啟遊戲專案。
  2. 在 Unity 編輯器中,依序點選「Assets」>「Import Package」>「Custom Package」,將下載的 unitypackage 檔案匯入專案的資產。
  3. 確認目前的建構平台已設為「Android」

    1. 依序點選主選單的「File」>「Build Settings」

    2. 選取「Android」,然後點選「Switch Platform」

    3. 依序前往「Window」>「Google Play Games」,應該會顯示新的選單項目。如果沒有顯示,請依序點選「Assets」>「Refresh」重新整理資產,再嘗試重新設定建構平台。

  4. 在 Unity 編輯器中,依序點選「File」>「Build Settings」>「Player Settings」>「Other Settings」

  5. 在「目標 API 級別」方塊中選取版本。

  6. 在「Scripting backend」(指令碼後端) 方塊中輸入 IL2CPP

  7. 在「目標架構」方塊中選取值。

  8. 請記下套件名稱 package_name,稍後會用到這項資訊。

    Unity 專案中的玩家設定
    Unity 專案中的「Player settings」。
  9. 從 Play 管理中心複製 Android 資源

  10. 將 Android 資源加入 Unity 專案中

遷移路徑

遊戲的正確遷移路徑取決於如何實作 Play Games 服務第 1 版,以及如何處理玩家身分。為確保順利轉換並避免遺失玩家資料,請找出最符合現有設定的情境,然後按照對應步驟操作。

做法 1:針對 IGA 繫結至 Play 遊戲服務玩家 ID 的遊戲

這個情境適用於遊戲,這些遊戲使用 Play 遊戲服務 Player ID做為玩家遊戲內帳戶 (IGA) 的唯一 ID,且先前未要求或儲存 OpenID。主要挑戰是將現有 IGA 連結至主要 ID (OpenID),同時保留與玩家進度的連結。

遷移流程包含下列步驟:

  1. 遊戲推出時,Play Games 服務第 2 版 SDK 會自動且無聲地驗證平台。
  2. 遊戲會顯示登入畫面。這個畫面必須顯示「使用 Google 登入」 (SiWG) 按鈕,取代「Google Play」按鈕。整合步驟如下:

    1. 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()); } } }

    2. 整合 Credential Manager:

      • 搭配 setFilterByAuthorizedAccounts(true) 使用 GetGoogleIdOption 進行無聲登入,只登入先前授權過應用程式的使用者。
      • 使用 setFilterByAuthorizedAccounts(false) 進行互動式登入,讓使用者選取帳戶或新增帳戶。
    3. 範圍要求:

      • 取得基本 Google 憑證後,系統會建立AuthorizationRequest要求特定舊版範圍:https://www.googleapis.com/auth/games_lite
      • 這個範圍非常重要,因為它會授予伺服器權限,可查詢使用者的舊版 PlayerID。
    4. 結果處理:

      • 如果使用者授予權限 (或先前已授予),橋接器會將 ServerAuthCode 傳回 Unity。
      • 如果使用者尚未授予權限 (新使用者情境),API 會傳回 PendingIntent。在這個範例中,意圖會遭到捨棄,且使用者會被視為新使用者,以簡化流程。
  3. 如要支援 Credential Manager 和 Google Identity 服務,請務必將下列依附元件新增至 mainTemplate.gradle Gradle 設定。

    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,用於擷取 OpenID Connect 權杖。
    • Play 服務驗證:必須維持相容性,並要求 GAMES_LITE 範圍,才能擷取舊版 Player ID
  4. 當玩家輕觸「使用 Google 登入」按鈕並選取 Google 帳戶時,遊戲必須擷取兩個不同的 ID:

    • OpenID,這是繫結 IGA 的主要 ID。
    • 使用 GAMES_LITE 範圍擷取的 Play Games 服務 Player ID,可在後端系統中查詢玩家的 IGA 並執行繫結。
  5. 在後續的遊戲啟動中,玩家可以透過 SiWG 流程存取 IGA,遊戲不必使用 Player ID 做為主要 ID。

您可以使用遊戲用戶端實作項目執行步驟 4。

  1. 開發人員呼叫 Android Credential Manager API,讓使用者透過 Google 帳戶登入。
  2. 使用者完成「使用 Google 帳戶登入」程序並選取 Google 帳戶後,開發人員會收到結果物件,其中包含 ID 權杖和電子郵件地址。
  3. 開發人員會從電子郵件地址建構帳戶物件。
  4. 開發人員會使用 GAMES_LITE 範圍和帳戶呼叫 Authorization API。
  5. 如果帳戶已授予 GAMES_LITE 範圍的權限,Authorization API 會直接在回應物件中傳回權杖。
    1. 使用回應權杖呼叫 Play Games 服務伺服器,並擷取 Play Games 服務 Player ID
    2. 開發人員會驗證 Play 遊戲服務 Player ID 是否已連結遊戲內帳戶。
      1. 開發人員知道這是透過 Play 遊戲服務第 1 版回訪的使用者。
    3. 開發人員可以將新的 Gaia ID 連結至先前的 Play Games 服務第 1 版帳戶。
  6. 或者,如果帳戶沒有 GAMES_LITE 範圍的預先存在授權,Authorization API 會傳回 PendingIntent。
    1. 開發人員知道使用者沒有 Play Games 服務第 1 版的現有帳戶。
    2. 開發人員可以放心捨棄 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();

測試遊戲

請測試遊戲,確保遊戲功能符合設計。您執行的測試取決於遊戲功能。

以下列出常見的測試。

  1. 成功登入

    1. 自動登入功能正常運作。使用者啟動遊戲時,應登入 Play 遊戲服務。

    2. 系統會顯示歡迎彈出式視窗。

      歡迎訊息彈出式視窗範例。
      歡迎彈出式視窗範例 (按一下可放大)。

    3. 系統會顯示成功記錄訊息。在終端機中執行下列指令:

      adb logcat | grep com.google.android.

      以下範例顯示成功記錄訊息:

      [$PlaylogGamesSignInAction$SignInPerformerSource@e1cdecc
      number=1 name=GAMES_SERVICE_BROKER>], returning true for shouldShowWelcomePopup.
      [CONTEXT service_id=1 ]
  2. 確保 UI 元件一致性

    1. 在 Play 遊戲服務使用者介面 (UI) 中,彈出式視窗、排行榜和成就能在各種螢幕大小和方向下正確且一致地顯示。

    2. Play Games 服務使用者介面中沒有登出選項。

    3. 確認您能順利擷取玩家 ID,且伺服器端功能運作正常 (如適用)。

    4. 如果遊戲使用伺服器端驗證,請徹底測試 requestServerSideAccess 流程。確認伺服器收到驗證碼,並可交換存取權杖。測試網路錯誤的成功和失敗情境,以及無效的 client ID 情境。

如果遊戲使用下列任何功能,請測試這些功能,確保遷移後運作方式與遷移前相同:

  • 排行榜:提交分數並查看排行榜。確認玩家名稱和分數的排名與顯示方式是否正確。
  • 成就:解鎖成就,並確認成就已正確記錄及顯示在 Play Games 使用者介面中。
  • 遊戲進度存檔:如果遊戲使用遊戲進度存檔,請確保儲存和載入遊戲進度時不會發生任何問題。因此請務必在多部裝置上測試,並在應用程式更新後再次測試。

遷移後工作

遷移至 Games v2 SDK 後,請完成下列步驟。

  1. 使用 Play 應用程式簽署功能

  2. 建立 AAB 檔案

  3. 建立內部測試版本

  4. 驗證應用程式簽署憑證