本文說明如何將現有遊戲從遊戲第 1 版 SDK 遷移至遊戲第 2 版 SDK。
事前準備
您可以透過偏好的 IDE (例如 Android Studio) 遷移遊戲。遷移至 Games v2 前,請完成下列步驟:
- 下載並安裝 Android Studio
- 遊戲必須使用遊戲服務第 1 版 SDK
更新依附元件
- 在模組的 - 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>
從已淘汰的 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 遊戲服務圖示的按鈕,並使用 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
        }
    }
}
更新用戶端類別名稱和方法
遷移至 Games v2 時,取得用戶端類別名稱的方法會有所不同。請改用對應的 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 遊戲服務使用者介面中未顯示登出選項。 
- 確認您能順利擷取玩家 ID,且伺服器端功能運作正常 (如適用)。 
- 如果遊戲使用伺服器端驗證,請徹底測試 - requestServerSideAccess流程。確認伺服器收到驗證碼,並可交換存取權杖。測試網路錯誤的成功和失敗情境,以及無效的- client ID情境。
 
如果遊戲使用下列任何功能,請測試這些功能,確保運作方式與遷移前相同:
- 排行榜:提交分數並查看排行榜。確認玩家名稱和分數的排名和顯示方式是否正確。
- 成就:解鎖成就,並確認成就已正確記錄及顯示在 Play 遊戲使用者介面中。
- 遊戲進度存檔:如果遊戲使用遊戲進度存檔,請確保遊戲進度儲存和載入功能運作無誤。因此請務必在多部裝置上測試,並在應用程式更新後進行測試。
遷移後工作
遷移至 Games v2 後,請完成下列步驟。
發布遊戲
建構 APK,並在 Play 管理中心發布遊戲。
- 在 Android Studio 選單中,依序選取「Build」>「Build Bundle(s) / APK(s)」>「Build APK(s)」。
- 發布遊戲。 詳情請參閱「 透過 Play 管理中心發布私人應用程式」。
