이 문서에서는 기존 게임을 게임 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의 최신 버전으로 바꿉니다.
종속 항목을 업데이트한 후 이 문서의 모든 단계를 완료해야 합니다.
지원 중단된 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
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
코드 업데이트
게임 v2 SDK에서는 GoogleSignIn
API 및 범위가 지원되지 않습니다. 다음 예와 같이 OAuth 2.0 범위의 GoogleSignIn
API 코드를 GamesSignInClient
API로 바꿉니다.
자바
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
}
}
}
클라이언트 클래스 이름 및 메서드 업데이트
games v2로 이전할 때 클라이언트 클래스 이름을 가져오는 데 사용되는 메서드는 다릅니다.
Games.getxxxClient()
메서드 대신 상응하는 PlayGames.getxxxClient()
메서드를 사용하세요.
예를 들어 LeaderboardsClient
의 경우 Games.getLeaderboardsClient()
메서드 대신 PlayGames.getLeaderboardsClient()
를 사용합니다.
자바
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)
.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
클래스를 그에 대응하는 '연결 없는' 클래스로 대체하면 됩니다.
다음 표에는 게임 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 게임즈 서비스 UI에 로그아웃 옵션이 표시되지 않습니다.
플레이어 ID를 가져올 수 있고 해당하는 경우 서버 측 기능이 예상대로 작동하는지 확인합니다.
게임에서 서버 측 인증을 사용하는 경우
requestServerSideAccess
흐름을 철저히 테스트합니다. 서버가 인증 코드를 수신하고 이를 액세스 토큰으로 교환할 수 있는지 확인합니다. 네트워크 오류, 잘못된client ID
시나리오에 대한 성공 및 실패 시나리오를 모두 테스트합니다.
게임에서 다음 기능 중 하나를 사용하고 있다면 테스트하여 마이그레이션 전과 동일하게 작동하는지 확인하세요.
- 리더보드: 점수를 제출하고 리더보드를 확인합니다. 순위 및 선수 이름과 점수가 올바르게 표시되는지 확인합니다.
- 업적: 업적을 잠금 해제하고 업적이 올바르게 기록되고 Play 게임즈 UI에 표시되는지 확인합니다.
- 저장된 게임: 게임에서 저장된 게임을 사용하는 경우 게임 진행 상황을 저장하고 로드하는 작업이 원활하게 작동하는지 확인합니다. 이는 여러 기기에서 테스트하고 앱 업데이트 후에 테스트하는 데 특히 중요합니다.
마이그레이션 후 태스크
게임 v2로 이전한 후 다음 단계를 완료합니다.
게임 게시
APK를 빌드하고 Play Console에 게임을 게시합니다.
- Android 스튜디오 메뉴에서 Build > Build Bundles(s) / APK(s) > Build APK(s)를 선택합니다.
- 게임을 게시합니다. 자세한 내용은 Play Console에서 비공개 앱 게시하기를 참고하세요.