Play 게임즈 서비스 v2 (Java 또는 Kotlin)로 이전

이 문서에서는 기존 게임을 게임 v1 SDK에서 게임 v2 SDK로 이전하는 방법을 설명합니다.

시작하기 전에

Android 스튜디오와 같은 선호하는 IDE를 사용하여 게임을 이전할 수 있습니다. 게임 v2로 이전하기 전에 다음 단계를 완료하세요.

종속 항목 업데이트

  1. 모듈의 build.gradle 파일에서 모듈 수준 종속 항목에서 이 줄을 찾습니다.

    implementation "com.google.android.gms:play-services-games-v1:+"

    다음 코드로 바꿉니다.

    implementation "com.google.android.gms:play-services-games-v2:version"

    version게임 SDK의 최신 버전으로 바꿉니다.

  2. 종속 항목을 업데이트한 후 이 문서의 모든 단계를 완료해야 합니다.

지원 중단된 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 스튜디오에서 빌드하고 실행하려면 앱 빌드 및 실행을 참고하세요.

게임 테스트

게임을 테스트하여 설계된 대로 작동하는지 확인합니다. 실행하는 테스트는 게임의 기능에 따라 다릅니다.

다음은 실행할 일반적인 테스트 목록입니다.

  1. 로그인 완료.

    1. 자동 로그인이 작동합니다. 사용자는 게임을 실행할 때 Play 게임즈 서비스에 로그인해야 합니다.

    2. 환영 팝업이 표시됩니다.

      샘플 시작 팝업
      샘플 시작 팝업 (확대하려면 클릭)

    3. 성공 로그 메시지가 표시됩니다. 터미널에서 다음 명령어를 실행합니다.

      adb logcat | grep com.google.android.

      성공 로그 메시지는 다음 예와 같습니다.

      [$PlaylogGamesSignInAction$SignInPerformerSource@e1cdecc
      number=1 name=GAMES_SERVICE_BROKER>], returning true for shouldShowWelcomePopup.
      [CONTEXT service_id=1 ]
  2. UI 구성요소 일관성 유지

    1. 팝업, 리더보드, 업적이 Play 게임즈 서비스 사용자 인터페이스 (UI)의 다양한 화면 크기와 방향에서 올바르고 일관되게 표시됩니다.

    2. Play 게임즈 서비스 UI에 로그아웃 옵션이 표시되지 않습니다.

    3. 플레이어 ID를 가져올 수 있고 해당하는 경우 서버 측 기능이 예상대로 작동하는지 확인합니다.

    4. 게임에서 서버 측 인증을 사용하는 경우 requestServerSideAccess 흐름을 철저히 테스트합니다. 서버가 인증 코드를 수신하고 이를 액세스 토큰으로 교환할 수 있는지 확인합니다. 네트워크 오류, 잘못된 client ID 시나리오에 대한 성공 및 실패 시나리오를 모두 테스트합니다.

게임에서 다음 기능 중 하나를 사용하고 있다면 테스트하여 마이그레이션 전과 동일하게 작동하는지 확인하세요.

  • 리더보드: 점수를 제출하고 리더보드를 확인합니다. 순위 및 선수 이름과 점수가 올바르게 표시되는지 확인합니다.
  • 업적: 업적을 잠금 해제하고 업적이 올바르게 기록되고 Play 게임즈 UI에 표시되는지 확인합니다.
  • 저장된 게임: 게임에서 저장된 게임을 사용하는 경우 게임 진행 상황을 저장하고 로드하는 작업이 원활하게 작동하는지 확인합니다. 이는 여러 기기에서 테스트하고 앱 업데이트 후에 테스트하는 데 특히 중요합니다.

마이그레이션 후 태스크

게임 v2로 이전한 후 다음 단계를 완료합니다.

게임 게시

APK를 빌드하고 Play Console에 게임을 게시합니다.

  1. Android 스튜디오 메뉴에서 Build > Build Bundles(s) / APK(s) > Build APK(s)를 선택합니다.
  2. 게임을 게시합니다. 자세한 내용은 Play Console에서 비공개 앱 게시하기를 참고하세요.