Play Games サービス v2 に移行する(Java または Kotlin)

このドキュメントでは、既存のゲームを games v1 SDK から games v2 SDK に移行する方法について説明します。

始める前に

ゲームの移行には、Android Studio など、お好みの IDE を使用できます。games 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 クラスに置き換えます。

Java

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 コードを更新する

GoogleSignIn API とスコープは、games v2 SDK ではサポートされていません。次の例に示すように、OAuth 2.0 スコープの GoogleSignIn API コードを GamesSignInClient API に置き換えます。

Java

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() でログイン プロセスを開始します。

Java

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 のコードは削除します。

次の例に示すコードを削除します。

Java

// ... 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.
    }
}

自動ログインが正常に完了したことを確認する

自動的にログインされたかどうかを確認する次のコードを追加し、カスタム ロジックがある場合は追加します。

Java

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() を使用します。

Java

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)
}

同様に、AchievementsClientEventsClientGamesSignInClientPlayerStatsClientRecallClientSnapshotsClientPlayersClient の各クライアントには、対応するメソッドを使用します。

サーバーサイドのアクセス クラスを更新する

サーバーサイド アクセス トークンをリクエストするには、GoogleSignInAccount.getServerAuthCode() メソッドではなく GamesSignInClient.requestServerSideAccess() メソッドを使用します。

次の例は、サーバーサイド アクセス トークンをリクエストする方法を示しています。

Java

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 クラスを「接続なし」の同等のものに置き換えます。次の表に、games v1 から games 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 Studio でビルドして実行するには、アプリをビルドして実行するをご覧ください。

ゲームをテストする

ゲームをテストして、設計どおりに機能することを確認します。実行するテストは、ゲームの機能によって異なります。

実行する一般的なテストのリストは次のとおりです。

  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 Games サービスのユーザー インターフェース(UI)に正しく一貫して表示されます。

    2. Play Games 開発者サービスの UI にログアウト オプションが表示されない。

    3. プレーヤー ID を正常に取得できること、該当する場合はサーバーサイド機能が想定どおりに動作することを確認します。

    4. ゲームでサーバーサイド認証を使用している場合は、requestServerSideAccess フローを徹底的にテストします。サーバーが認証コードを受信し、アクセス トークンと交換できることを確認します。ネットワーク エラー、無効な client ID シナリオについて、成功シナリオと失敗シナリオの両方をテストします。

ゲームで次のいずれかの機能を使用していた場合は、移行前と同じように動作することを確認してください。

  • リーダーボード: スコアを送信してリーダーボードを表示します。ランキングが正しく、プレーヤーの名前とスコアが表示されていることを確認します。
  • 実績: 実績をロック解除し、実績が正しく記録され、Google Play Games の UI に表示されることを確認します。
  • 保存済みゲーム: ゲームで保存済みゲームを使用している場合は、ゲームの進行状況の保存と読み込みが問題なく機能することを確認します。これは、複数のデバイス間でテストする場合や、アプリの更新後にテストする場合に特に重要です。

移行後のタスク

games v2 に移行したら、次の手順を行います。

ゲームを公開する

APK をビルドし、Google Play Console でゲームを公開します。

  1. Android Studio メニューで、[Build] > [Build Bundle(s) / APK(s)] > [Build APK(s)] を選択します。
  2. ゲームを公開します。詳しくは、 限定公開アプリを Play Console から公開するをご覧ください。