本文說明如何將現有遊戲從 games v1 SDK 遷移至 games v2 SDK。
事前準備
您可以使用任何偏好的 IDE (例如 Android Studio) 來遷移遊戲。遷移至遊戲 v2 前,請先完成下列步驟:
- 下載並安裝 Android Studio
- 遊戲必須使用遊戲服務第 1 版 SDK
更新依附元件
在模組的
build.gradle
檔案中,在模組層級依附元件中找出這行。implementation "com.google.android.gms:play-services-games-v1:+"
替換成以下程式碼:
implementation "com.google.android.gms:play-services-games-v2:version"
將 version 替換為最新版的遊戲 SDK。
更新依附元件後,請務必完成本文件中的所有步驟。
從已淘汰的 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
val signInOptions = GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN
// 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();
// 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 和範圍。將 OAuth 2.0 範圍的 GoogleSignIn
API 程式碼替換為 GamesSignInClient
API,如以下範例所示:
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 {
// Disable your integration with Play Games Services or 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()
方法。
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)
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
String serverAuthToken = task.getResult();
// Send authentication code to the backend game server.
// 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)
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
String serverAuthToken = task.getResult();
// Send authentication code to the backend game server.
// 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
類別替換為對等的「無連線」類別。以下是常見類別的對應項目:
games v2
// Replace com.google.android.gms.games.achievement.Achievements
com.google.android.gms.games.AchievementsClient
// Replace com.google.android.gms.games.leaderboard.Leaderboard
com.google.android.gms.games.LeaderboardsClient
// Replace com.google.android.gms.games.snapshot.Snapshots
com.google.android.gms.games.SnapshotsClient
// Replace com.google.android.gms.games.stats.PlayerStats
com.google.android.gms.games.PlayerStatsClient
// Replace com.google.android.gms.games.Players
com.google.android.gms.games.PlayersClient
// Replace com.google.android.gms.games.GamesStatusCodes
com.google.android.gms.games.GamesClientStatusCodes
games v1
com.google.android.gms.games.achievement.Achievements
com.google.android.gms.games.leaderboard.Leaderboard
com.google.android.gms.games.snapshot.Snapshots
com.google.android.gms.games.stats.PlayerStats
com.google.android.gms.games.Players
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 遊戲服務 UI 中沒有登出選項。
確認您能成功擷取玩家 ID,並且伺服器端功能 (如適用) 能正常運作。
如果遊戲使用伺服器端驗證,請徹底測試
requestServerSideAccess
流程。請確認伺服器收到驗證碼,並能用來交換存取權杖。測試成功和失敗情境,檢查網路錯誤、無效的client ID
情境。
如果您的遊戲使用下列任何功能,請進行測試,確保這些功能的運作方式與遷移前相同:
- 排行榜:提交分數及查看排行榜。檢查排名是否正確,並確認玩家名稱和分數是否正確顯示。
- 成就:解鎖成就,並確認成就是否正確記錄並顯示在 Play 遊戲 UI 中。
- 已儲存的遊戲:如果遊戲使用已儲存的遊戲,請確保儲存及載入遊戲進度能夠正常運作。這對於在多部裝置上進行測試,以及在應用程式更新後測試,特別重要。
遷移後工作
遷移至遊戲 v2 後,請完成下列步驟。
發布遊戲
建構 APK 並在 Play 管理中心發布遊戲。
- 在 Android Studio 選單中,依序選取「Build」>「Build Bundles(s) / APK(s)」>「Build APK(s)」。
- 發布遊戲。 詳情請參閱「 透過 Play 管理中心發布私人應用程式」。