이 문서에서는 기존 게임을 게임 v1 SDK 에서 게임 v2 SDK로 이전하는 방법을 설명합니다. Unity용 Play 게임즈 플러그인 버전 10 이하에서는 게임 v1 SDK를 사용합니다.
시작하기 전에
- Play Console을 이미 설정하고 Unity 편집기를 설치했는지 확인합니다.
Unity용 Google Play 게임즈 플러그인 다운로드
Play 게임즈 서비스의 최신 기능을 활용하려면 최신 플러그인 버전을 다운로드하여 설치하세요. gitHub 저장소에서 다운로드합니다.
이전 플러그인 삭제
Unity 편집기에서 다음 폴더 또는 파일을 삭제합니다.
Assets/GooglePlayGames Assets/GeneratedLocalRepo/GooglePlayGames Assets/Plugins/Android/GooglePlayGamesManifest.androidlib Assets/Plugins/Android
Unity 프로젝트로 새 플러그인 가져오기
Unity 프로젝트로 플러그인을 가져오려면 다음 단계를 따르세요.
- 게임 프로젝트를 엽니다.
- Unity 편집기에서 Assets > Import Package > Custom Package
를 클릭하여 다운로드한
unitypackage파일을 프로젝트의 애셋으로 가져옵니다. 현재 빌드 플랫폼이 Android 로 설정되어 있는지 확인합니다.
기본 메뉴에서 File > Build Settings 를 클릭합니다.
Android 를 선택하고 Switch Platform 을 클릭합니다.
Window > Google Play Games 아래에 새 메뉴 항목이 있습니다. 표시되지 않으면 Assets > Refresh 를 클릭하여 애셋을 새로고침한 다음 빌드 플랫폼을 다시 설정해 봅니다.
Unity 편집기에서 File > Build Settings > Player Settings > Other Settings 를 클릭합니다.
대상 API 수준 상자에서 버전을 선택합니다.
Scripting backend 상자에
IL2CPP를 입력합니다.Target architectures 상자에서 값을 선택합니다.
패키지 이름 package_name을 기록해 둡니다.이 정보는 나중에 사용할 수 있습니다.
Unity 프로젝트의 플레이어 설정
이전 경로
게임의 올바른 마이그레이션 경로는 Play Games 서비스 v1을 구현하고 플레이어 ID를 처리하는 방식에 따라 다릅니다. 원활한 전환을 보장하고 플레이어 데이터 손실을 방지하려면 기존 설정과 가장 일치하는 시나리오를 파악하고 해당 단계를 따르세요.
옵션 1: IGA가 Play 게임즈 서비스 플레이어 ID에 바인딩된 게임
이 시나리오는 Play 게임즈 서비스 Player ID를 플레이어의 게임 내 계정 (IGA)에 대한 유일한 식별자로 사용하고 이전에 OpenID를 요청하거나 저장하지 않은 게임에 적용됩니다. 가장 큰 과제는 플레이어 진행 상황과의 연결을 끊지 않고 기존 IGA를 기본 식별자 (OpenID)에 연결하는 것입니다.
이전 흐름에는 다음 단계가 포함됩니다.
- 게임이 출시되면 Play Games 서비스 v2 SDK가 자동으로 백그라운드에서 플랫폼을 인증합니다.
게임에 로그인 화면이 표시됩니다. 이 화면에는 Google Play 버튼을 대체하는 Google로 로그인 (SiWG) 버튼이 있어야 합니다. 통합하려면 다음 단계를 따르세요.
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()); } } }
인증 관리자 통합:
- 이전에 앱을 승인한 사용자만 로그인하도록 자동 로그인에
setFilterByAuthorizedAccounts(true)와 함께GetGoogleIdOption을 사용합니다. - 사용자가 계정을 선택하거나 새 계정을 추가할 수 있도록 대화형 로그인에
setFilterByAuthorizedAccounts(false)를 사용합니다.
- 이전에 앱을 승인한 사용자만 로그인하도록 자동 로그인에
범위 요청:
- 기본 Google 사용자 인증 정보를 가져온 후 특정 기존 범위인
AuthorizationRequesthttps://www.googleapis.com/auth/games_lite를 요청하는 `AuthorizationRequest`를 만듭니다. - 이 범위는 서버에 사용자의 기존 PlayerID를 조회할 수 있는 권한을 부여하므로 매우 중요합니다.
- 기본 Google 사용자 인증 정보를 가져온 후 특정 기존 범위인
결과 처리:
- 사용자가 권한을 부여하거나 이전에 권한을 부여한 경우 브리지가
ServerAuthCode를 Unity에 반환합니다. - 사용자가 권한을 부여하지 않은 경우 (신규 사용자 시나리오) API가
PendingIntent를 반환합니다. 이 샘플에서는 흐름을 간소화하기 위해 인텐트가 삭제되고 사용자가 신규 사용자로 처리됩니다.
- 사용자가 권한을 부여하거나 이전에 권한을 부여한 경우 브리지가
인증 관리자 및 Google ID 서비스를 지원하려면 다음 종속 항목이
mainTemplate.gradleGradle 구성에 추가되어 있는지 확인합니다.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' }
- 인증 관리자: 계정 선택을 위한 기본 ID 오케스트레이션 및 UI를 처리합니다.
- GoogleID 라이브러리:
OpenIDConnect 토큰을 가져오기 위한GetGoogleIdOption을 구체적으로 제공합니다. - Play 서비스 인증: 호환성을 유지하고 기존
Player ID검색을 위한GAMES_LITE범위를 요청하는 데 필요합니다.
플레이어가 SiWG 버튼을 탭하고 Google 계정을 선택하면 게임에서 두 가지 고유한 식별자를 가져와야 합니다.
- IGA를 바인딩하기 위한 기본 식별자인
OpenID입니다. GAMES_LITE범위를 사용하여 가져온 Play Games 서비스Player ID입니다. 백엔드 시스템에서 플레이어의 IGA를 조회하고 바인딩을 실행하는 데 사용됩니다.
- IGA를 바인딩하기 위한 기본 식별자인
이후 게임 실행에서 플레이어는 게임에서
Player ID를 기본 식별자로 사용하지 않고도 SiWG 흐름을 통해 IGA에 액세스할 수 있습니다.
게임 클라이언트 측 구현을 사용하여 4단계를 실행할 수 있습니다.
- 개발자가 Android Credential Manager API를 호출하여 Google 계정으로 사용자를 로그인합니다.
- 사용자가 SiwG를 완료하고 Google 계정을 선택하면 개발자가 ID 토큰과 이메일 주소가 포함된 결과 객체를 수신합니다.
- 개발자가 이메일 주소에서 계정 객체를 구성합니다.
- 개발자가
GAMES_LITE범위와 계정으로 인증 API를 호출합니다. - 계정에
GAMES_LITE범위에 대한 기존 권한이 있는 경우 인증 API가 응답 객체에서 토큰을 직접 반환합니다.- 응답 토큰을 사용하여 Play Games 서비스 서버를 호출하고 Play Games 서비스
Player ID를 가져옵니다. - 개발자가 Play 게임즈 서비스
Player ID가 게임 내 계정에 연결되었는지 확인합니다.- 개발자는 이것이 Play 게임즈 서비스 v1에서 돌아온 사용자임을 알고 있습니다.
- 개발자는 새 Gaia ID를 이전 Play 게임즈 서비스 v1 계정에 연결할 수 있습니다.
- 응답 토큰을 사용하여 Play Games 서비스 서버를 호출하고 Play Games 서비스
- 또는 계정에
GAMES_LITE범위에 대한 기존 권한이 없는 경우 인증 API가 PendingIntent를 반환합니다.- 개발자는 사용자에게 Play Games 서비스 v1의 기존 계정이 없음을 알고 있습니다.
- 개발자는 UI를 표시하지 않고 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 Games 서비스에는 더 이상 게임 내 로그아웃 버튼이 필요하지 않습니다.
다음 예시에 표시된 코드를 삭제합니다.
C#
// sign out
PlayGamesPlatform.Instance.SignOut();
게임 테스트
게임을 테스트하여 게임이 설계된 대로 작동하는지 확인합니다. 실행하는 테스트는 게임의 기능에 따라 다릅니다.
다음은 실행할 일반적인 테스트 목록입니다.
로그인 성공.
자동 로그인이 작동합니다. 게임을 실행할 때 사용자가 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에 표시되지 않습니다.
Player ID를 가져올 수 있는지, 서버 측 기능이 예상대로 작동하는지 확인합니다(해당하는 경우).
게임에서 서버 측 인증을 사용하는 경우
requestServerSideAccess흐름을 철저히 테스트합니다. 서버가 승인 코드를 수신하고 액세스 토큰으로 교환할 수 있는지 확인합니다. 네트워크 오류, 잘못된client ID시나리오에 대한 성공 및 실패 시나리오를 모두 테스트합니다.
게임에서 다음 기능 중 하나를 사용한 경우 이전 전과 동일하게 작동하는지 테스트합니다.
- 리더보드: 점수를 제출하고 리더보드를 봅니다. 플레이어 이름과 점수의 올바른 순위 및 표시를 확인합니다.
- 업적: 업적을 잠금 해제하고 올바르게 기록되고 Play Games UI에 표시되는지 확인합니다.
- 저장된 게임: 게임에서 저장된 게임을 사용하는 경우 게임 진행 상황을 저장하고 로드하는 것이 원활하게 작동하는지 확인합니다. 특히 여러 기기에서 그리고 앱 업데이트 후에 테스트하는 것이 중요합니다.
이전 후 작업
게임 v2 SDK로 이전한 후 다음 단계를 완료합니다.