이 문서에서는 기존 게임을 게임 v1 SDK에서 게임 v2 SDK로 이전하는 방법을 설명합니다.
시작하기 전에
Android 스튜디오와 같은 원하는 IDE를 사용하여 게임을 이전할 수 있습니다. 게임 v2로 이전하기 전에 다음 단계를 완료하세요.
- Android 스튜디오 다운로드 및 설치
- 게임에서 게임즈 v1 SDK를 사용해야 합니다.
종속 항목 업데이트
모듈의
build.gradle
파일에서 모듈 수준 종속 항목에 있는 다음 줄을 찾습니다.implementation "com.google.android.gms:play-services-games-v1:+"
다음 코드로 바꿉니다.
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>
지원 중단된 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 게임즈 서비스 로그인 버튼을 삭제합니다. 사용자가 게임이 시작될 때 로그인하지 않도록 선택하는 경우 Play 게임즈 서비스 아이콘이 있는 버튼을 계속 표시하고 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 {
// 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
}
}
}
클라이언트 클래스 이름 및 메서드 업데이트
게임 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로의 일반적인 클래스 매핑이 나열되어 있습니다.
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 스튜디오에서 빌드하고 실행하려면 앱 빌드 및 실행을 참고하세요.
게임 테스트
게임을 테스트하여 설계된 대로 작동하는지 확인합니다. 실행하는 테스트는 게임의 기능에 따라 다릅니다.
다음은 실행할 일반적인 테스트 목록입니다.
로그인 성공
자동 로그인이 작동합니다. 사용자는 게임을 실행할 때 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 Console에 게임을 게시합니다.
- Android 스튜디오 메뉴에서 Build > Build Bundles(s) / APK(s) > Build APK(s)를 선택합니다.
- 게임을 게시합니다. 자세한 내용은 Play Console에서 비공개 앱 게시하기를 참고하세요.