迁移到 Play 游戏服务 v2 (Unity)

本文档介绍了如何将现有游戏从游戏 v1 SDK 迁移到游戏 v2 SDK。 适用于 Unity 的 Play 游戏插件(版本 10 及更低版本)使用游戏 v1 SDK。

准备工作

  • 确保您已设置 Play 管理中心并安装 Unity 编辑器。

下载适用于 Unity 的 Google Play 游戏插件

如需使用 Play 游戏服务的最新功能,请下载并安装最新版本的插件。您可以从 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. 确保当前的 build 平台已设置为 Android

    1. 在主菜单中,依次点击 File > Build Settings

    2. 选择 Android ,然后点击 Switch Platform

    3. Window > Google Play Games 下应该会显示新的菜单项。如果未显示,请点击 Assets > Refresh 来刷新资源,然后再次尝试设置 build 平台。

  4. 在 Unity 编辑器中,依次点击 File > Build Settings > Player Settings > Other Settings

  5. 目标 API 级别 框中,选择一个版本。

  6. Scripting backend 框中,输入 IL2CPP

  7. Target architectures 框中,选择一个值。

  8. 记下软件包名称 package_name。您稍后可以使用此信息 。

    Unity 项目中的 Player Settings
    Unity 项目中的玩家设置。
  9. 从 Play 管理中心复制 Android 资源

  10. 将 Android 资源添加到 Unity 项目

迁移途径

游戏的正确迁移途径取决于游戏实现 Play 游戏服务 v1 和处理玩家身份的方式。为确保顺利过渡并防止玩家数据丢失,请确定最符合您现有设置的场景,然后按照相应的步骤操作。

选项 1:适用于 IGA 绑定到 Play 游戏服务玩家 ID 的游戏

此场景适用于以下游戏:已将 Play 游戏服务 Player ID 用作玩家的游戏内账号 (IGA) 的唯一标识符,并且之前未请求或存储 OpenID。主要挑战在于将现有 IGA 链接到主要标识符 (OpenID),同时不会丢失与玩家进度的连接。

迁移流程包括以下步骤:

  1. 游戏启动时,Play 游戏服务 v2 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 集成

      • GetGoogleIdOptionsetFilterByAuthorizedAccounts(true) 结合使用,以进行静默登录,仅允许之前已授权应用的用户登录。
      • setFilterByAuthorizedAccounts(false) 用于互动式登录,以允许用户选择账号或添加新账号。
    3. 范围请求

      • 获取基本 Google 凭据后,它会创建一个 AuthorizationRequest,请求特定的旧版范围: https://www.googleapis.com/auth/games_lite
      • 此范围至关重要,因为它会授予服务器查找用户旧版 PlayerID 的权限。
    4. 结果处理

      • 如果用户授予权限(或之前已授予权限),桥接器会将 ServerAuthCode 返回给 Unity。
      • 如果用户未授予权限(新用户场景),API 会返回 PendingIntent。在此示例中,intent 会被舍弃,用户会被视为新用户,以简化流程。
  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. 当玩家点按 SiWG 按钮并选择 Google 账号时,游戏必须检索两个不同的标识符:

    • OpenID,用于绑定 IGA 的主要标识符。
    • Play 游戏服务 Player ID,通过使用 GAMES_LITE 范围检索,以在后端系统中查找玩家的 IGA 并执行绑定。
  5. 在后续游戏启动中,玩家可以通过 SiWG 流程访问其 IGA,而无需游戏使用 Player ID 作为主要标识符。

您可以使用游戏客户端实现执行第 4 步。

  1. 开发者调用 Android Credential Manager API,以使用 Google 账号让用户登录。
  2. 用户完成 SiwG 并选择 Google 账号后,开发者会收到一个结果对象,其中包含 ID 令牌、电子邮件地址。
  3. 开发者会根据电子邮件地址构建一个账号对象。
  4. 开发者使用 GAMES_LITE 范围和账号调用 Authorization API。
  5. 如果账号对 GAMES_LITE 范围具有预先存在的授权,Authorization API 会直接在响应对象中返回令牌。
    1. 使用响应令牌调用 Play 游戏服务服务器并检索 Play 游戏服务 Player ID
    2. 开发者验证 Play 游戏服务 Player ID 是否已与游戏内账号关联。
      1. 开发者知道这是 Play 游戏服务 v1 的回访用户。
    3. 开发者可以将新的 gaia ID 与之前的 Play 游戏服务 v1 账号相关联。
  6. 或者,如果账号对 GAMES_LITE 范围没有预先存在的授权,Authorization API 会返回 PendingIntent。
    1. 开发者知道用户没有 Play 游戏服务 v1 中的现有账号。
    2. 开发者可以安全地舍弃 PendingIntent,而无需显示任何界面。

选项 2:适用于已将 IGA 绑定到 OpenID 的游戏

此组中的开发者拥有最直接的迁移途径。如果游戏的游戏内账号已主要绑定到 OpenID,您只需按照步骤中所述,执行从 v1 到 v2 的标准技术 SDK 迁移。

更新自动登录代码

PlayGamesClientConfiguration 初始化类替换为 PlayGamesPlatform.Instance.Authenticate() 类。 无需初始化和激活 PlayGamesPlatform 。调用 PlayGamesPlatform.Instance.Authenticate() 会获取自动登录的结果。 如需详细了解集成 Play 游戏服务 v2 时推荐的身份验证流程,请参阅理想身份验证流程的用户体验指南

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 游戏服务不再需要游戏内退出按钮。

移除以下示例中显示的代码:

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. 确保界面组件的一致性

    1. 在 Play 游戏服务用户界面 (UI) 中,弹出式窗口、排行榜和成就可在各种屏幕尺寸和屏幕方向上正确且一致地显示。

    2. 退出选项在 Play 游戏服务界面中不可见。

    3. 确保您可以成功检索 Player ID,并且如果适用,服务器端功能可以按预期运行。

    4. 如果游戏使用服务器端身份验证,请彻底测试 requestServerSideAccess 流程。确保服务器收到身份验证代码并可以将其换成访问令牌。 测试网络错误、无效 client ID 场景的成功和失败情况。

如果您的游戏使用了以下任何功能,请对其进行测试,以确保它们与迁移前的工作方式相同:

  • 排行榜:提交得分并查看排行榜。检查玩家姓名和得分的排名和显示是否正确。
  • 成就:解锁成就并验证它们是否已正确记录 并显示在 Play 游戏界面中。
  • 游戏存档:如果游戏使用游戏存档,请确保保存和加载游戏进度可以顺利进行。这一点对于跨多个设备以及在应用更新后进行测试尤为重要。

迁移后任务

迁移到游戏 v2 SDK 后,请完成以下步骤。

  1. 使用 Play 应用签名

  2. 创建 AAB 文件

  3. 创建内部测试版本

  4. 验证应用签名凭据