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

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

始める前に

ゲームの移行には、Android Studio など、お好みの IDE を使用できます。ゲーム v2 に移行する前に、次の手順を完了してください。

  • Android Studio をダウンロードしてインストールする
  • ゲームで Google Play Games サービス v1 の SDK を使用する必要があります。
  • ゲームをアップグレードして、com.google.android.gms:play-services-games:24.0.0 に Google Play Games サービス v1 の SDK を使用できます。ゲーム v1 API が削除されているため、com.google.android.gms:play-services-games:25.0.0 にアップグレードしないでください。

依存関係を更新する

  1. モジュールの build.gradle ファイルで、モジュール レベルの依存関係にある次の行を見つけます。

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

    次のコードに置き換えます。

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

    version は、ゲーム SDK の最新バージョンに置き換えます。

  2. 依存関係を更新したら、このドキュメントのすべての手順を完了してください。

プロジェクト ID を定義する

Play ゲームサービス SDK のプロジェクト ID をアプリに追加する手順は次のとおりです。

  1. AndroidManifest.xml ファイルで、次の <meta-data> 要素と属性を <application> 要素に追加します。

    <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 の [設定] ページのゲーム名で確認できます。

  2. 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>
    

移行パス

ゲームの適切な移行パスは、Play ゲームサービス v1 の実装方法とプレーヤー ID の処理方法によって異なります。スムーズな移行とプレーヤー データの損失を防ぐため、既存の設定に最も適したシナリオを特定し、対応する手順に沿って操作してください。

オプション 1: IGA が Play ゲームサービス プレーヤー ID にバインドされているゲームの場合

このシナリオは、Play ゲームサービスの Player ID をプレーヤーのゲーム内アカウント(IGA)の唯一の識別子として使用し、以前に OpenID をリクエストまたは保存していないゲームに適用されます。主な課題は、プレーヤーの進行状況との接続を失うことなく、既存の IGA をプライマリ識別子(OpenID)にリンクすることです。

移行フローには次の手順が含まれます。

  1. ゲームが起動すると、Play Games サービス v2 SDK はプラットフォームを自動的にサイレント認証します。
  2. Google Play ボタンを置き換える Google でログイン ボタンを備えたログイン画面を表示します。たとえば、CredManBridge.java を参照してください。

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

    }

    // --- モード 2: インタラクティブなログイン(ボタンクリック時に呼び出し)--- // アカウント選択シートまたは [アカウントを追加] シートを強制的に表示します。 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);
            }
    
            @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());
    }
    

    }

  3. プレーヤーが [Google でログイン] ボタンをタップして Google アカウントを選択したときに、2 つの異なる識別子を取得します。

    • IGA のバインディングのプライマリ識別子である OpenID
    • GAMES_LITE スコープを使用して取得した Play Games サービス Player ID。バックエンド システムでプレーヤーの IGA を検索し、バインディングを実行するために使用します。詳細については、Player ID を取得するをご覧ください。
  4. ゲームで Player ID をプライマリ ID として使用する必要なく、その後のゲームの起動時に Google でログイン フローで IGA にアクセスします。

Player ID を取得する

ステップ 3 は、ゲーム クライアントサイドの実装を使用して実行できます。

  1. Android Credential Manager API を呼び出して、Google アカウントでユーザーをログインさせます。
  2. ユーザーが Google でログイン フローを完了して Google アカウントを選択すると、ID トークンとメールアドレスを含む結果オブジェクトが返されます。
  3. メールアドレスから Account オブジェクトを構築します。
  4. GAMES_LITE スコープと Account を使用して Authorization API を呼び出します。
  5. アカウントに GAMES_LITE スコープに対する既存の権限付与がある場合、Authorization API はレスポンス オブジェクトでトークンを直接返します。
    1. レスポンス トークンを使用して Play Games サービス サーバーを呼び出し、Play Games サービス Player ID を取得します。
    2. Play Games サービス Player ID がゲーム内アカウントにリンクされているかどうかを確認します。
      1. これは、Play ゲームサービス v1 からの再訪ユーザーを示します。
    3. 新しい Gaia ID を以前の Play Games サービス v1 アカウントにリンクします。
  6. アカウントに GAMES_LITE スコープに対する既存の権限付与がない場合、Authorization API は PendingIntent を返します。
    1. これは、ユーザーが Play Games サービス v1 の既存のアカウントを持っていないことを示します。
    2. UI を表示せずに PendingIntent を安全に破棄します。

オプション 2: すでに IGA を OpenID にバインドしているゲームの場合

このグループのデベロッパーは、最も簡単な移行パスを利用できます。ゲーム内アカウントがすでに OpenID に主にバインドされている場合は、手順に沿って v1 から v2 への標準の技術 SDK 移行を行うだけで済みます。

サポートが終了した 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

    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 は、Google Play Games サービス v2 の SDK ではサポートされていません。次の例に示すように、GoogleSignIn API コードを GamesSignInClient API に置き換えてください。

サーバーサイドのアクセス トークンをリクエストするには、GamesSignInClient.requestServerSideAccess() メソッドを使用します。詳しくは、サーバーサイド アクセス クラスを更新するをご覧ください。

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 {
        // 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 クラスに関連するコードを削除します。

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, /* 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 Studio でビルドして実行するには、アプリをビルドして実行するをご覧ください。

ゲームをテストする

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

実行する一般的なテストのリストを次に示します。

  1. ログインに成功しました

    1. 自動ログインが機能している。ユーザーはゲームを起動すると Play Games サービスにログインする。

    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 Games サービスの UI にログアウト オプションが表示されない。

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

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

ゲームで次の機能を使用していた場合は、移行前と同じように動作することを確認するためにテストします。

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

移行後のタスク

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

ゲームを公開する

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

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