이 문서에서는 기존 게임을 게임 v1 SDK에서 게임 v2 SDK로 이전하는 방법을 설명합니다.
시작하기 전에
Android 스튜디오와 같은 원하는 IDE를 사용하여 게임을 이전할 수 있습니다. 게임 v2로 이전하기 전에 다음 단계를 완료하세요.
- Android 스튜디오 다운로드 및 설치
- 게임에서 게임즈 v1 SDK를 사용해야 합니다.
- 게임을 업그레이드하여 게임 v1 SDK를 사용하여
com.google.android.gms:play-services-games:24.0.0할 수 있습니다. 게임 v1 API가 삭제되었으므로com.google.android.gms:play-services-games:25.0.0로 업그레이드하면 안 됩니다.
종속 항목 업데이트
모듈의
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파일에서<application>요소에 다음<meta-data>요소 및 속성을 추가합니다.<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 Console에서 구성 페이지의 게임 이름 아래에 있습니다.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>
마이그레이션 경로
게임의 올바른 이전 경로는 Play 게임즈 서비스 v1을 구현하고 플레이어 ID를 처리하는 방식에 따라 다릅니다. 원활한 전환을 보장하고 플레이어 데이터 손실을 방지하려면 기존 설정과 가장 일치하는 시나리오를 파악하고 해당 단계를 따르세요.
옵션 1: IGA가 Play 게임즈 서비스 플레이어 ID에 바인드된 게임
이 시나리오는 Play 게임즈 서비스 Player ID를 플레이어의 게임 내 계정 (IGA)의 유일한 식별자로 사용하고 이전에 OpenID를 요청하거나 저장하지 않은 게임에 적용됩니다. 핵심 과제는 플레이어의 진행 상황과의 연결을 유지하면서 기존 IGA를 기본 식별자 (OpenID)에 연결하는 것입니다.
마이그레이션 흐름에는 다음 단계가 포함됩니다.
- 게임이 출시되면 Play Games 서비스 v2 SDK가 자동으로 백그라운드에서 플랫폼을 인증합니다.
Google Play 버튼을 대체하는 Google 계정으로 로그인 버튼이 있는 로그인 화면을 표시합니다. 예를 들어 CredManBridge.java를 참고하세요.
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); } @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()); }} }
플레이어가 Google 계정으로 로그인 버튼을 탭하고 Google 계정을 선택하면 서로 다른 두 식별자를 가져옵니다.
OpenID(IGA를 바인딩하는 기본 식별자)GAMES_LITE범위를 사용하여 검색된 Play Games 서비스Player ID로, 백엔드 시스템에서 플레이어의 IGA를 조회하고 바인딩을 실행합니다. 자세한 내용은Player ID가져오기를 참고하세요.
게임에서
Player ID를 기본 식별자로 사용하지 않아도 후속 게임 실행에서 Google 계정으로 로그인 흐름을 통해 IGA에 액세스할 수 있습니다.
Player ID 검색
게임 클라이언트 측 구현을 사용하여 3단계를 실행할 수 있습니다.
- Android Credential Manager API를 호출하여 Google 계정으로 사용자를 로그인시킵니다.
- 사용자가 Google 계정으로 로그인 흐름을 완료하고 Google 계정을 선택하면 ID 토큰과 이메일 주소가 포함된 결과 객체를 수신합니다.
- 이메일 주소에서 Account 객체를 생성합니다.
GAMES_LITE범위와 계정으로 승인 API를 호출합니다.- 계정에
GAMES_LITE범위에 대한 기존 권한이 있는 경우 승인 API는 응답 객체에 토큰을 직접 반환합니다.- 응답 토큰을 사용하여 Play Games 서비스 서버를 호출하고 Play Games 서비스
Player ID를 가져옵니다. - Play 게임즈 서비스
Player ID가 인게임 계정과 연결되어 있는지 확인합니다.- Play 게임즈 서비스 v1에서 돌아온 사용자를 나타냅니다.
- 새 GAIA ID를 이전 Play Games 서비스 v1 계정과 연결합니다.
- 응답 토큰을 사용하여 Play Games 서비스 서버를 호출하고 Play Games 서비스
- 계정에
GAMES_LITE범위에 대한 기존 권한이 없으면 승인 API가PendingIntent를 반환합니다.- 이는 사용자에게 Play 게임 서비스 v1의 기존 계정이 없음을 나타냅니다.
- UI를 표시하지 않고
PendingIntent을 안전하게 삭제합니다.
옵션 2: 이미 IGA를 OpenID에 바인딩한 게임
이 그룹의 개발자는 가장 간단한 이전 경로를 가지고 있습니다. 게임의 인게임 계정이 이미 OpenID에 기본적으로 바인드되어 있는 경우 단계에 설명된 대로 v1에서 v2로 표준 기술 SDK 이전만 실행하면 됩니다.
지원 중단된 Google 로그인에서 이전
GoogleSignInClient 클래스를 GamesSignInClient 클래스로 바꿉니다.
자바
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 코드 업데이트
GoogleSignIn API는 게임 v2 SDK에서 지원되지 않습니다. 다음 예와 같이 GoogleSignIn API 코드를 GamesSignInClient API로 바꿉니다.
서버 측 액세스 토큰을 요청하려면 GamesSignInClient.requestServerSideAccess() 메서드를 사용합니다.
자세한 내용은 서버 측 액세스 클래스 업데이트를 참고하세요.
자바
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 Games 서비스 로그인 버튼을 삭제합니다. 사용자가 게임이 시작될 때 인증하지 않도록 선택하는 경우 Play Games 서비스 아이콘이 있는 버튼을 계속 표시하고 GamesSignInClient.signIn()으로 로그인 프로세스를 시작합니다.
자바
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의 코드를 삭제합니다.
다음 예에 표시된 코드를 삭제합니다.
자바
// ... 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.
}
}인증 성공 여부 확인
다음 코드를 포함하여 자동으로 인증되었는지 확인하고, 사용할 수 있는 경우 맞춤 로직을 추가합니다.
자바
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
}
}
}
클라이언트 클래스 이름 및 메서드 업데이트
게임즈 v2로 이전하면 클라이언트 클래스 이름을 가져오는 데 사용되는 메서드가 달라집니다. Games.getxxxClient() 메서드 대신 해당하는 PlayGames.getxxxClient() 메서드를 사용하세요.
예를 들어 LeaderboardsClient의 경우 Games.getLeaderboardsClient() 메서드 대신 PlayGames.getLeaderboardsClient()를 사용합니다.
게임 v2에는 대체 클래스가 없으므로 GamesClient 및 GamesMetadataClient 클래스와 관련된 코드를 삭제합니다.
자바
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 클라이언트의 경우 해당 메서드를 사용합니다.
서버 측 액세스 클래스 업데이트
서버 측 액세스 토큰을 요청하려면 GoogleSignInAccount.getServerAuthCode() 메서드 대신 GamesSignInClient.requestServerSideAccess() 메서드를 사용합니다.
자세한 내용은 서버 승인 코드 전송을 참고하세요.
다음 예에서는 서버 측 액세스 토큰을 요청하는 방법을 보여줍니다.
자바
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 클래스를 그에 대응하는 '연결 없는' 클래스로 대체하면 됩니다.
다음 표에는 게임 v1에서 게임 v2로의 일반적인 클래스 매핑이 나열되어 있습니다.
| 게임 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 스튜디오에서 빌드하고 실행하려면 앱 빌드 및 실행을 참고하세요.
게임 테스트
테스트를 통해 게임이 설계된 대로 작동하는지 확인합니다. 실행하는 테스트는 게임의 기능에 따라 다릅니다.
다음은 실행할 일반적인 테스트 목록입니다.
로그인 성공
자동 로그인이 작동합니다. 사용자는 게임을 실행할 때 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 서비스 UI에 로그아웃 옵션이 표시되지 않습니다.
플레이어 ID를 성공적으로 가져올 수 있는지, 서버 측 기능이 예상대로 작동하는지 확인합니다(해당하는 경우).
게임에서 서버 측 인증을 사용하는 경우
requestServerSideAccess흐름을 철저히 테스트합니다. 서버가 승인 코드를 수신하고 액세스 토큰으로 교환할 수 있는지 확인합니다. 네트워크 오류, 잘못된client ID시나리오의 성공 및 실패 시나리오를 모두 테스트합니다.
게임에서 다음 기능을 사용한 경우 마이그레이션 전과 동일하게 작동하는지 테스트하세요.
- 리더보드: 점수를 제출하고 리더보드를 확인합니다. 플레이어 이름과 점수의 순위 및 표시가 올바른지 확인합니다.
- 업적: 업적을 잠금 해제하고 Play Games UI에 올바르게 기록되고 표시되는지 확인합니다.
- 저장된 게임: 게임에서 저장된 게임을 사용하는 경우 게임 진행 상황을 저장하고 로드하는 작업이 원활하게 작동하는지 확인합니다. 특히 여러 기기에서 앱 업데이트 후 테스트하는 것이 중요합니다.
마이그레이션 후 작업
게임 v2로 이전한 후 다음 단계를 완료하세요.
게임 게시
APK를 빌드하고 Play Console에 게임을 게시합니다.
- Android 스튜디오 메뉴에서 Build > Build Bundles(s) / APK(s) > Build APK(s)를 선택합니다.
- 게임을 게시합니다. 자세한 내용은 Play Console에서 비공개 앱 게시하기를 참고하세요.