2016年01月11日

Cocos2d-x、AndroidでGoogle Play Games ServicesのLeaderboardを使用する

概要

Cocos2d-xでGoogle Play Games ServicesのLeaderboardを使う方法についてのメモです。あんまりCocos2d-x関係ないですが。

以下のような流れで書いていきます。

  1. Google Play Developer Consoleからゲームサービスを登録する。
  2. Google Play services SDK、Google Repositoryをインストールする。
  3. build.gradleにGoogle Play Games Servicesを追加する。
  4. AndroidManifest.xmlにApp IDを指定する記述を追加する。
  5. Cocos2dxActivityの継承元クラスをFragmentActivityに変更する。
  6. Google APIに接続する処理を作成する。
  7. Leaderboardにスコアを送信する処理を作成する。
  8. Leaderboardを表示する処理を作成する。
  9. C++からJavaの関数を呼び出す処理を作成する。
  10. Signed APKを作成してインストールする。

Google Play Developer Consoleからアプリを登録する

Developerの登録とアプリの登録は済んでいることを前提に書いていきます。

まずGoogle Play Developer Consoleからゲームサービスを登録します。あまり悩むところも無いと思うので箇条書きで書いていきます。

  • 左側の"ゲームサービス"を選んで、"Google Play ゲーム サービスをセットアップ"のボタンをクリックします。
  • ゲームの名前と種類を入力するダイアログが表示されるので、入力して"次へ"をクリックします。
  • "リンク済みアプリ"を選んで、"Android"のボタンをクリックします。
  • アプリの名前とパッケージ名を入力して、"保存して次へ"をクリックします。
  • 今すぐアプリを承認"をクリックします。
  • 署名証明書フィンガープリントというものが表示されますが、気にせず"確認"をクリックします。
  • 次に"リーダーボード"を選択して、"リーダーボードを追加"をクリックします。
  • 名前を入力して、"保存"をクリックします。
  • リーダーボードのIDが表示されている下辺りにある"リソースを取得"をクリックします。
  • "Android"のタブを選択して、表示されるxmlをコピーして、コメントに書いてあるようにgames-ids.xmlというファイル名で保存して、プロジェクトのリソースに追加します。
  • Developerに登録しているアカウント以外でテストする場合には"テスト"を選択して、テスト用アカウントに登録します。

Google Play services SDK、Google Repositoryをインストールする

Android StudioのSDK Managerを使用して、Google Play services SDKとGoogle Repositoryをインストールします。

build.gradleにGoogle Play Games Servicesを追加する

proj.android-studio/app/build.gradleを編集して、Google Play Games Servicesをリンクするようにします。

dependencies {
    ...

    compile 'com.google.android.gms:play-services-games:8.4.0'

    ...
}

AndroidManifest.xmlにApp IDを指定する記述を追加する

AndroidManifest.xmlにApp IDを指定する記述を追加します。ここで指定するApp IDはゲームサービス登録して時に取得しておいたgames-ids.xmlから取ってきています。

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher">

    <meta-data android:name="com.google.android.gms.games.APP_ID"
android:value="@string/app_id" />
    <meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />

</application>

Cocos2dxActivityの継承元クラスをFragmentActivityに変更する

Leaderboardとは直接関係ないのですが、Googleのサンプルプログラムでエラーが発生した時のダイアログを出すのにFragmentを使用しているので、これを使えるようにCocos2dxActivityの継承元クラスをFragmentActivityに変更します。

Cocos2d-xのソースをいじるのは抵抗があるのですが、サンプルをそのまま使いたいので変更してしまいます。

import android.support.v4.app.FragmentActivity;

public abstract class Cocos2dxActivity extends FragmentActivity implements Cocos2dxHelperListener {

FragmentActivityを使えるようにするためにlibcocos2dx/build.gradleも変更します。

android {
   compileSdkVersion 23
   ...
}

dependencies {
    ...
    compile "com.android.support:support-v4:23.1.1"
}

Google APIに接続する処理を作成する

前準備が整ったので、Google APIにアクセスする処理を作成していきます。AppActivity.javaに処理を追加していきます。

まずはimportの追加から。

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.games.Games;

AppActivityをGoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListenerをimplementsするように変更します。

public class AppActivity extends Cocos2dxActivity implements
GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {

onCreateにGoogleApiClientを作成する処理を追加します。

/** Google APIにアクセスするためのクライアント */
private GoogleApiClient mGoogleApiClient;

@Override
protected void onCreate(Bundle savedInstanceState) {

    ...

    mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(Games.API).addScope(Games.SCOPE_GAMES)
                .build();

    ...

onConnected、onConnectionSuspendedを実装します。

    @Override
    public void onConnected(Bundle connectionHint) {

    }

    @Override
    public void onConnectionSuspended(int cause) {

        // Google APIへ接続する
        mGoogleApiClient.connect();
  }

onConnectionFailedとエラー処理を実装します。まずはエラーダイアログを表示する部分を作成します。

import android.app.Dialog;
import android.content.IntentSender.SendIntentException;
import android.content.DialogInterface;
import android.support.v4.app.DialogFragment;

public class AppActivity extends Cocos2dxActivity implements

GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {

    /** Unique tag for the error dialog fragment */
    private static final String DIALOG_ERROR = "dialog_error";

    /** Request code to use when launching the resolution activity */
    private static final int REQUEST_RESOLVE_ERROR = 1001;

    ...

    /**
     * Creates a dialog for an error message
     * @param errorCode エラーコード
     */
    private void showErrorDialog(int errorCode) {

        // Create a fragment for the error dialog
        ErrorDialogFragment dialogFragment = new ErrorDialogFragment();

        // Pass the error that should be displayed
        Bundle args = new Bundle();
        args.putInt(DIALOG_ERROR, errorCode);
        dialogFragment.setArguments(args);
        dialogFragment.show(getSupportFragmentManager(), "errordialog");
    }

    /**
     *  Called from ErrorDialogFragment when the dialog is dismissed.
     */
    public void onDialogDismissed() {
        mResolvingError = false;
    }

    /**
     *  A fragment to display an error dialog
     */
    public static class ErrorDialogFragment extends DialogFragment {

        /**
         * コンストラクタ。無処理。
         */
        public ErrorDialogFragment() {
        }

        /**
         * ダイアログ作成処理。
         * Google APIのエラーダイアログを作成する。
         * @param savedInstanceState 状態変数
         * @return エラーダイアログ
         */
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {

            // Get the error code and retrieve the appropriate dialog
            int errorCode = this.getArguments().getInt(DIALOG_ERROR);

            // エラーダイアログを作成する
            return GoogleApiAvailability.getInstance().getErrorDialog(
                    this.getActivity(), errorCode, REQUEST_RESOLVE_ERROR);
        }

        /**
         * 終了時処理。
         * アクティビティのダイアログ終了処理を呼び出す。
         * @param dialog ダイアログ
         */
        @Override
        public void onDismiss(DialogInterface dialog) {

            // アクティビティのダイアログ終了処理を呼び出す
            ((AppActivity)getActivity()).onDialogDismissed();
        }
    }
}

onConnectionFailedの処理は以下の通りです。

    /** Google API接続エラーを解決中かどうか */
    private boolean mResolvingError = false;

    /**
     * Google APIへの接続に失敗した時の処理。
     * 解決方法がある場合は解決を試みる。
     * @param result
     */
    @Override
    public void onConnectionFailed(ConnectionResult result) {

        if (mResolvingError) {

            // すでにエラー解決中の場合は何もしない
        }
        else if (result.hasResolution()) {

            // 解決方法がある場合は解決を試みる
            try {

                // エラー解決中のフラグを立てる
                mResolvingError = true;

                // 解決を試みる
                result.startResolutionForResult(this, REQUEST_RESOLVE_ERROR);

            } catch (SendIntentException e) {

                // エラー解決中に例外が発生した場合は、再度接続を試みる
                mGoogleApiClient.connect();
            }
        }
        else {

            // 解決方法がない場合はエラーダイアログを表示する
            showErrorDialog(result.getErrorCode());
            mResolvingError = true;
        }
    }

onStartでGoogle APIへの接続、onStopでGoogle APIの切断の処理を追加します。

    /**
     * Activity開始時の処理。
     * Google APIへ接続する。
     */
    @Override
    protected void onStart() {

        // 基底クラスの処理を呼び出す
        super.onStart();

        // エラー解決中でない場合はGoogle APIへの接続を行う。
        // Googleへのサインインを行って戻る場合などもonStartが走るが、
        // この時にも接続を行おうとすると二重に接続することになるので、
        // これを防止するため。
        if (!mResolvingError) {
            mGoogleApiClient.connect();
        }
    }

    /**
     * Activity停止時の処理。
     * Google APIを切断する。
     */
    @Override
    protected void onStop() {

        // Google APIを切断する
        mGoogleApiClient.disconnect();

        // 基底クラスの処理を呼び出す
        super.onStop();
    }

Leaderboardにスコアを送信する処理を作成する

Leaderboardにスコアを送信する処理を作成します。JNIから呼び出すため、static関数とします。

R.string.leaderboard_scoreのところは、Google Developer Consoleで登録したLeaderboardの名前で変わってきますので、games-ids.xmlのLeaderboard IDを設定している所を参照してください。

    /**
     * Leaderboardにハイスコアを送信する。
     * @param score スコア
     */
    public static void postHighScore(int score) {

        if (me.mGoogleApiClient != null && me.mGoogleApiClient.isConnected()) {

            // Leaderboardにハイスコアを送信する
            Games.Leaderboards.submitScore(me.mGoogleApiClient, me.getString(R.string.leaderboard_score), score);
        }
    }

Leaderboardを表示する処理を作成する

Leaderboardを表示する処理を作成します。JNIから呼び出すため、static関数とします。

R.string.leaderboard_scoreのところは、Google Developer Consoleで登録したLeaderboardの名前で変わってきますので、games-ids.xmlのLeaderboard IDを設定している所を参照してください。

    /** Leaderboard表示のリクエストコード */
    private static final int REQUEST_LEADERBOARD = 1002;

    /**
     * Leaderboardの画面を表示する。
     */
    public static void openRanking() {

        if (me.mGoogleApiClient != null && me.mGoogleApiClient.isConnected()) {

            // Leaderboardのアクティビティを開始する
            me.startActivityForResult(
                    Games.Leaderboards.getLeaderboardIntent(me.mGoogleApiClient, me.getString(R.string.leaderboard_score)),
                    REQUEST_LEADERBOARD);
        }
    }

C++からJavaの関数を呼び出す処理を作成する

C++からJavaの関数を呼び出す処理を作成します。

#include <assert.h>
#include <jni.h>
#include "platform/android/jni/JniHelper.h"
#define JNICLASSNAME "org/cocos2dx/cpp/AppActivity"

using cocos2d::JniMethodInfo;
using cocos2d::JniHelper;

// ランキング画面表示
void OnlineScore::openRanking()
{
    // ネイティブコードのランキング画面表示関数を取得する
    JniMethodInfo methodInfo;
    if (!JniHelper::getStaticMethodInfo(methodInfo, JNICLASSNAME, "openRanking", "()V"))
    {
        assert(false);
        return;
    }

    // 関数を呼び出す
    methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID);

    // リソースを解放する
    methodInfo.env->DeleteLocalRef(methodInfo.classID);
}

// ハイスコア送信
void OnlineScore::postHighScore(int score)
{
    // ネイティブコードのハイスコア送信関数を取得する
    JniMethodInfo methodInfo;
    if (!JniHelper::getStaticMethodInfo(methodInfo, JNICLASSNAME, "postHighScore", "(I)V"))
    {
        assert(false);
        return;
    }

    // 関数を呼び出す
    methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID, score);

    // リソースを解放する
    methodInfo.env->DeleteLocalRef(methodInfo.classID);
}

Signed APKを作成してインストールする

Signed APKでないとGoogle Playに接続することができないので、テストをするときにはSigned APKを作成してインストールするなど必要があります。

Signed APKの作成方法は次のサイトを参考にしました。

Android Studioで APK ファイルを作成する - Android Studioでアプリ開発!

作成したAPKファイルをAndroidにインストールするにはターミナルからコマンドを入力します。Signed APKをインストールする前に署名なしのものをインストールしている場合は先にアンインストールをする必要があります。

$ adb uninstall パッケージ名
$ adb -d install APKファイルのパス

あとはAndroidの画面からアプリを実行すればテストできます。

なお、Android Studio経由でなく、Androidの画面から実行したアプリのログを見る場合は次のようにターミナルからlogcatを実行します。オプションを何も付けなければ全部のログが表示されます。

$ adb logcat 見たいログのタグ名:V *:S
ラベル:android cocos2d
posted by かねだ at 11:53| Comment(0) | 開発方法メモ | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

×

この広告は1年以上新しい記事の投稿がないブログに表示されております。