Mobage Native SDK を cocos2d-x で使ってみた(Android 編)

こんにちは! テクニカルコンサルティンググループの monma です。

前回の 『Mobage Native SDK を cocos2d-x で使ってみた(iOS 編)』に引き続き、今回は Mobage Native SDK for Android を cocos2d-x 上で動かしてみましたので、前回同様 Mobage にログインできるようになるまでを書いていきます。

iOS と違い、Android の場合は JNI を利用して作成しなければならないので、実装するコード量と記事の内容が長くなってしまいますが、最後までお付き合い下さい。

1. 前提条件

iOS の時と同様に、CocosDenshion を参考にしています。記事の内容の前提条件は、以下の通りです。

  • Mac OSX 10.8.5
  • Mobage Native SDK 1.4.6.2
  • cocos2d-x 2.2.2 (C++)
  • Android Developer Tools Build: v22.3.0-887826
  • Android SDK 22.3
  • Android NDK r8b

また、Activity に関しても cocos2d-x のテンプレートが生成した Main Activity 以外の Activity が存在しない前提になっています。もし、複数の Activity を使用し Main Activity 以外から Mobage Native SDK for Android を呼び出す場合には、本記事に書かれている内容とは異なりますのでご注意下さい。

※この記事では、ただ cocos2d-x の Native コード側で Mobage Native SDK を動かすのではなくクロスプラットフォームで互換性のある実装をしていくための方法の解説となります。また、この記事の内容はそのうち cocos2d-x 用 Mobage SDK のサンプルコードとして公開させていただこうと考えています。

2. Mobage SDK の組み込み

iOS 同様、Mobage SDK の組み込みをします。cocos2d-x の Android プロジェクトに以下の URL の「Mobage Native SDK の導入」にしたがって組み込みを行ないます。

https://docs.mobage.com/display/JPSA/Native+Tutorial+Chapter+2+Android+Setup

AndroidManifest.xml の編集までを完了させます。

3. 作成するクラスのイメージ

本記事で作成していくクラス構成は、JNI を利用して以下の様なイメージで Mobage Native SDK の com.mobage.android.Mobage へアクセスするように実装していきます。

image2.png

それぞれのファイル構成は以下のようになります。

  • src

    • com.mobage.cocos2dx.lib (Package名)

      • MobageCocos2dxMobagePlatform.java

  • Classes

    • Cocos2dxMobageWrapper

      • Android

        • jni

          • CCMobagePlatformJni.cpp

          • CCMobagePlatformJni.h

      • include

        • CCMobagePlatform.h

cocos2d-x で作成されたテンプレートとは違うパッケージ com.mobage.cocos2dx.lib を用意し、そこに Java 側の実装をしていきます。また、Classes ディレクトリ以下に Cocos2dxMobageWrapper というディレクトリを作成し、jni ディレクトリに JNI の実装と cocos2d-x から include できる CCMobagePlatform.h を include ディレクトリに配置します。

本記事は上記の構成を前提としてパスを表記していますが、実際に実装する際には好きなもので構いません。

4. cocos2d-x から呼び出すための定義部

それでは、cocos2d-x 側から実装をしていきます。

iOS の時と同様に共通アクセス部として CCMobagePlatform.h を作成します。CCMobagePlatform.h は、iOS と共通で使用できるようにします。今回も前回同様にログイン部分だけの実装になるので、非常に簡素なものとなります。

CCMobagePlatform.h
#ifndef __cocos2dMobage__CCMobagePlatform__
#define __cocos2dMobage__CCMobagePlatform__

#include "cocos2d.h"
USING_NS_CC;

class CCMobagePlatformDelegate
{
public:
    virtual void onLoginComplete(const char* userId) = 0;
    virtual void onLoginRequired() = 0;
    virtual void onLoginError(int code, const char* description) = 0;
    virtual void onLoginCancel() = 0;
    virtual void onSplashComplete() = 0;
};

namespace CCMobage {
    /*!
     * @abstract Region Type
     */
    enum REGION {
        REGION_US, //unsupported for now
        REGION_JP,
        REGION_CN, //unsupported for now
        REGION_KR
    };

    enum SERVER_TYPE {
        SANDBOX,
        PRODUCTION
    };

    typedef enum {
        TARGET_USER_GRADE_DEFAULT,
        TARGET_USER_GRADE_GRADE2,
        TARGET_USER_GRADE_GRADE3
    } TargetUserGrade;
    
    class CCMobagePlatform
    {
    private:
        CCMobagePlatformDelegate* m_mobagePlatformDelegate;
        
    public:
        CCMobagePlatform();
        ~CCMobagePlatform();
        
        static CCMobagePlatform* sharedMobagePlatform();
        static void end();
        
        void initialize(REGION region,
                        SERVER_TYPE server,
                        const char* strConsumerKey,
                        const char* strConsumerSecret,
                        const char* strAppId);
        
        CCMobagePlatformDelegate* getDelegate(){ return m_mobagePlatformDelegate; }
        void setDelegate(CCMobagePlatformDelegate* pDelegate);
        
        const char* getSDKVersion();
        
        void hideSplashScreen();
        
        void checkLoginStatus();
        
        void showLoginDialog();
    };
    
}

#endif /* defined(__cocos2dMobage__CCMobagePlatform__) */

上記の定義部は、iOS と共通のものを利用していますので iOS と全く同じものとなります。詳細は前回記事をご確認下さい。

5. cocos2d-x から呼び出すための実装部

実装部に移ります。実装部は JNI のクラスを呼び出すための橋渡し役になります。

CCMobagePlatform.cpp
#include "../include/CCMobagePlatform.h"
#include "./jni/CCMobagePlatformJni.h"
#include <cstddef>

namespace CCMobage {

    static CCMobagePlatform* sharedMobagePlatformObject = NULL;

    CCMobagePlatform::CCMobagePlatform()
    {
        m_mobagePlatformDelegate = NULL;
    }

    CCMobagePlatform::~CCMobagePlatform()
    {
    }

    CCMobagePlatform* CCMobagePlatform::sharedMobagePlatform()
    {
        if (! sharedMobagePlatformObject)
        {
            sharedMobagePlatformObject = new CCMobagePlatform();
        }
        return sharedMobagePlatformObject;
    }

    void CCMobagePlatform::end()
    {
        if (sharedMobagePlatformObject)
        {
            delete sharedMobagePlatformObject;
            sharedMobagePlatformObject = NULL;
        }
        static_mobageplatform_end();
    }

    void CCMobagePlatform::initialize(CCMobage::REGION region,
                                      CCMobage::SERVER_TYPE server,
                                      const char* strConsumerKey,
                                      const char* strConsumerSecret,
                                      const char* strAppId)
    {
        mobageplatform_initialize_jni((int)region,
                                         (int)server,
                                         strConsumerKey,
                                         strConsumerSecret,
                                         strAppId);

        return;
    }

    void CCMobagePlatform::setDelegate(CCMobagePlatformDelegate* pDelegate)
    {
        mobageplatform_setDelegate_jni(pDelegate);
    }

    void CCMobagePlatform::hideSplashScreen()
    {
        mobageplatform_hideSplashScreen_jni();
    }

    void CCMobagePlatform::checkLoginStatus()
    {
        mobageplatform_checkLoginStatus_jni();
    }

    void CCMobagePlatform::showLoginDialog()
    {
        mobageplatform_showLoginDialog_jni();
    }
}

CCMobagePlatform 実装部で行うことは、

  • CCMobagePlatformJni.h 内のメソッドへの値の受け渡し
  • delegate オブジェクトの受け渡し
  • シングルトンのオブジェクトの管理

のみとなります。

delegate オブジェクトに関しては、本来 Mobage Native SDK for Android で使用する  PlatformListener の代わりに、CCMobagePlatformDelegate が呼ばれるように作成するためのものです。

6. JNI 定義部

さて、Android の場合は C++ と Java 間のやりとりは JNI (Java Native Interface) を用いて行ないます。この実装を行うことで、C++ 側から Java 側へのアクセスを可能にします。

CCMobagePlatformJni.h
#ifndef __CCMOBAGE_PLATFORM_JNI__
#define __CCMOBAGE_PLATFORM_JNI__

#include <jni.h>
#include "../../include/CCMobagePlatform.h"

extern "C"
{
    extern void mobageplatform_initialize_jni(int region,
            int server,
            const char* strConsumerKey,
            const char* strConsumerSecret,
            const char* strAppId);

    extern void mobageplatform_setDelegate_jni(CCMobagePlatformDelegate* pDelegate);
    extern void mobageplatform_hideSplashScreen_jni();
    extern void mobageplatform_checkLoginStatus_jni();
    extern void mobageplatform_showLoginDialog_jni();
}

#endif // __SIMPLE_AUDIO_ENGINE_JNI__

JNI でやりとりを行う場合、C リンケージで実装していかなければならないので、extern “C”  を指定します。

7. JNI 実装部

JNI 実装の実装部になります。以下のように実装していきます。

CCMobagePlatfom.cpp
#include "CCMobagePlatformJni.h"
#include "platform/android/jni/JniHelper.h"

#include <android/log.h>
#include <jni.h>

#define  LOG_TAG    "mobageplatform_jni"
#define  LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#define  CLASS_NAME "com/mobage/cocos2dx/lib/MobageCocos2dxMobagePlatform"

extern "C" {
    static CCMobagePlatformDelegate* platformDelegate = 0;
    
    JNIEXPORT void JNICALL Java_com_mobage_cocos2dx_lib_MobageCocos2dxMobagePlatform_mobageplatformLoginlistenerOnLoginComplete(JNIEnv * env, jobject thiz, jstring jstrUserId) 
    {
        const char* strUserId = env->GetStringUTFChars(jstrUserId, 0);
        if(platformDelegate)
        {
            platformDelegate->onLoginComplete(strUserId);
        }
        env->ReleaseStringUTFChars(jstrUserId, strUserId);
    }
    
    JNIEXPORT void JNICALL Java_com_mobage_cocos2dx_lib_MobageCocos2dxMobagePlatform_mobageplatformLoginlistenerOnLoginRequired(JNIEnv * env, jobject thiz) 
    {
        if (platformDelegate) 
        {
            platformDelegate->onLoginRequired();
        }
    }
    
    JNIEXPORT void JNICALL Java_com_mobage_cocos2dx_lib_MobageCocos2dxMobagePlatform_mobageplatformLoginlistenerOnLoginError(JNIEnv * env, jobject thiz, jint jcode, jstring jstrDescription) 
    {
        int code = jcode;
        const char* strDescription = env->GetStringUTFChars(jstrDescription, 0);
        if (platformDelegate) 
        {
            platformDelegate->onLoginError(code, strDescription);
        }
        env->ReleaseStringUTFChars(jstrDescription, strDescription);
    }
    
    JNIEXPORT void JNICALL Java_com_mobage_cocos2dx_lib_MobageCocos2dxMobagePlatform_mobageplatformLoginlistenerOnLoginCancel(JNIEnv * env, jobject thiz) 
    {
        if (platformDelegate) 
        {
            platformDelegate->onLoginCancel();
        }
    }
    
    JNIEXPORT void JNICALL Java_com_mobage_cocos2dx_lib_MobageCocos2dxMobagePlatform_mobageplatformLoginlistenerOnSplashComplete(JNIEnv * env, jobject thiz) 
    {
        if (platformDelegate) 
        {
            platformDelegate->onSplashComplete();
        }
    }
    
    void mobageplatform_initialize_jni(int region, int server, const char* strConsumerKey, const char* strConsumerSecret, const char* strAppId) 
    {
        JniMethodInfo methodInfo;
        if (!JniHelper::getStaticMethodInfo(methodInfo, CLASS_NAME,
                                            "mobageplatform_initialize",
                                            "(IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V")) 
        {
            return;
        }
        
        jstring jstrConsumerKey = methodInfo.env->NewStringUTF(strConsumerKey);
        jstring jstrConsumerSecret = methodInfo.env->NewStringUTF(
                                                                  strConsumerSecret);
        jstring jstrAppId = methodInfo.env->NewStringUTF(strAppId);
        
        methodInfo.env->CallStaticVoidMethod(methodInfo.classID,
                                             methodInfo.methodID,
                                             (int) region,
                                             (int) server,
                                             jstrConsumerKey,
                                             jstrConsumerSecret,
                                             jstrAppId);
        
        methodInfo.env->DeleteLocalRef(jstrConsumerKey);
        methodInfo.env->DeleteLocalRef(jstrConsumerSecret);
        methodInfo.env->DeleteLocalRef(jstrAppId);
        
        methodInfo.env->DeleteLocalRef(methodInfo.classID);
    }
    
    void mobageplatform_setDelegate_jni(CCMobagePlatformDelegate* delegate) 
    {
        platformDelegate = delegate;
    }
    
    void mobageplatform_hideSplashScreen_jni() 
    {
        JniMethodInfo methodInfo;
        
        if (!JniHelper::getStaticMethodInfo(methodInfo, CLASS_NAME, "mobageplatform_hideSplashScreen", "()V"))
        {
            return;
        }
        
        methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID);
        
        methodInfo.env->DeleteLocalRef(methodInfo.classID);
    }
    void mobageplatform_checkLoginStatus_jni() 
    {
        JniMethodInfo methodInfo;
        
        if (!JniHelper::getStaticMethodInfo(methodInfo, CLASS_NAME,  "mobageplatform_checkLoginStatus", "()V")) 
        {
            return;
        }
        
        methodInfo.env->CallStaticVoidMethod(methodInfo.classID,  methodInfo.methodID);
        
        methodInfo.env->DeleteLocalRef(methodInfo.classID);
    }
    
    void mobageplatform_showLoginDialog_jni() 
    {
        JniMethodInfo methodInfo;
        if (!JniHelper::getStaticMethodInfo(methodInfo, CLASS_NAME, "mobageplatform_showLoginDialog", "()V")) 
        {
            return;
        }
        
        methodInfo.env->CallStaticVoidMethod(methodInfo.classID,methodInfo.methodID);
        
        methodInfo.env->DeleteLocalRef(methodInfo.classID);
    }

それでは、それぞれ上から順に解説していきます。

7-1 Include 文

#include "../../include/CCMobagePlatform.h"
#include "platform/android/jni/JniHelper.h"

CCMobagePlatform.h 内に実装されている CCMobagePlatformDelegate クラスが必要になるため、include しておきます。また、JNI でのアクセスが簡単になる cocos2d-x の JniHelper が用意されているのでこれも include しておきます。

7-2 CLASS_NAME

#define  CLASS_NAME "com/mobage/cocos2dx/lib/MobageCocos2dxMobagePlatform"

CLASS_NAME は JNI で Java 側の該当クラスのメソッドを呼び出す際に必要になります。該当クラスまでの絶対パスをドット区切りではなくスラッシュ区切りで定義しておきます。

7-3 CCMobagePlatformDelegate インスタンス

static CCMobagePlatformDelegate* platformDelegate = 0;

static で CCMobagePlatformDelegate のインスタンスを定義しておきます。CCMobagePlatformDelegate は、Java 側で呼び出された Mobage の PlatformListener を C++ 側に受け渡すためのものです。

7-4 Java_com_mobage_cocos2dx_lib_MobageCocos2dxMobagePlatform_mobageplatformLoginlistenerOnLoginComplete メソッド

JNIEXPORT void JNICALL Java_com_mobage_cocos2dx_lib_MobageCocos2dxMobagePlatform_mobageplatformLoginlistenerOnLoginComplete(JNIEnv * env, jobject thiz, jstring jstrUserId) 
{
    const char* strUserId = env->GetStringUTFChars(jstrUserId, 0);
    if(platformDelegate)
    {
        platformDelegate->onLoginComplete(strUserId);
    }
    env->ReleaseStringUTFChars(jstrUserId, strUserId);
}

Java_com_mobage_cocos2dx_lib_MobageCocos2dxMobagePlatform_mobageplatformLoginListenerOnLoginComplete メソッドを実装します。メソッド名が長いですが、このメソッドは Java 側から JNI を経由して C++ 側のメソッドを呼び出すことを想定していますので、Java_+パッケージ名(.を_にする)+_+クラス名+_+メソッド名となります。

このメソッドは、Mobage Native SDK for Android の Java 側で設定する PlatformListener から呼び出されることを想定して作成します。このメソッドが呼び出されると、platformDelegate オブジェクト内の onLoginComplete メソッドが呼び出される仕組みです。

引数は、JNI のメソッドでは、JNIEnv と jobject の記載が必要になりますので、デフォルトで記載しておき(JNIEnv は JNI のメソッドへのアクセス、jobject は this を受け取れます)、さらに jstring jstrUserId を追加します。これは、onLoginComplete に通知される userId を受け取るためのものです。受け取った jstring を GetStringUTFChars メソッドで const char に変換し、delegate の onLoginRequired に受渡したあとに、ReleaseStringUTFChars で解放処理を行ないます。解放処理を行わないとメモリリークの原因となります。


その他の onLoginRequired, onLoginCancel, onLoginError, onSplashComplete に関しても同様の実装を行っていきます。

7-5 mobageplatform_initialize_jni メソッド

    void mobageplatform_initialize_jni(int region, int server, const char* strConsumerKey, const char* strConsumerSecret, const char* strAppId) 
    {
        JniMethodInfo methodInfo;
        if (!JniHelper::getStaticMethodInfo(methodInfo, CLASS_NAME,
                                            "mobageplatform_initialize",
                                            "(IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V")) 
        {
            return;
        }
        
        jstring jstrConsumerKey = methodInfo.env->NewStringUTF(strConsumerKey);
        jstring jstrConsumerSecret = methodInfo.env->NewStringUTF(
                                                                  strConsumerSecret);
        jstring jstrAppId = methodInfo.env->NewStringUTF(strAppId);
        
        methodInfo.env->CallStaticVoidMethod(methodInfo.classID,
                                             methodInfo.methodID,
                                             (int) region,
                                             (int) server,
                                             jstrConsumerKey,
                                             jstrConsumerSecret,
                                             jstrAppId);
        
        methodInfo.env->DeleteLocalRef(jstrConsumerKey);
        methodInfo.env->DeleteLocalRef(jstrConsumerSecret);
        methodInfo.env->DeleteLocalRef(jstrAppId);
        
        methodInfo.env->DeleteLocalRef(methodInfo.classID);
    }

mobageplatform_initialize_jni メソッドを実装します。mobageplatform_initialize_jni メソッドは Java 側のメソッドを呼び出すため、cocos2d-x の JniHelper を利用すると簡単に呼び出すことができます。

        if (!JniHelper::getStaticMethodInfo(methodInfo, CLASS_NAME,
                                            "mobageplatform_initialize",
                                            "(IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V")) 
        {
            return;
        }

まずは、対象のメソッドの情報を getStaticMethodInfo メソッドで読み込みます。メソッドの情報は JniMethodInfo 構造体 methodInfo に格納されます。対象のメソッドのクラス名、メソッド名、メソッドのシグニチャを引数に与え、そのメソッドが存在する場合は、true が返却され、methodInfo に情報が格納されます。

※ getStaticMethodInfo は static メソッドを呼び出す用途で使用します。instance メソッドを呼び出す際には getMethodInfo メソッドを使用します。

        jstring jstrConsumerKey = methodInfo.env->NewStringUTF(strConsumerKey);
        jstring jstrConsumerSecret = methodInfo.env->NewStringUTF(
                                                                  strConsumerSecret);
        jstring jstrAppId = methodInfo.env->NewStringUTF(strAppId);

JniMethodInfo 構造体の中には JNIEnv である env オブジェクトが含まれるので、JNI のメソッドにアクセスすることができます。JNI を通して Java のメソッドを呼び出すため、Java の String 型でメソッドを呼び出す必要があります。JNI では jstring が Java の String にあたるため、 const char* をそれぞれ jstring へキャストします。

        methodInfo.env->CallStaticVoidMethod(methodInfo.classID,
                                             methodInfo.methodID,
                                             (int) region,
                                             (int) server,
                                             jstrConsumerKey,
                                             jstrConsumerSecret,
                                             jstrAppId);

Java 側に存在する mobageplatform_initialize メソッドへのアクセスを CallStaticVoidMethod メソッドを使用して行ないます。呼び出す際には、対象のクラス名とメソッド名を特定するために methodInfo.classID と methodInfo.methodID も一緒に引き渡す必要があります。

        methodInfo.env->DeleteLocalRef(jstrConsumerKey);
        methodInfo.env->DeleteLocalRef(jstrConsumerSecret);
        methodInfo.env->DeleteLocalRef(jstrAppId);
        
        methodInfo.env->DeleteLocalRef(methodInfo.classID);

mobageplatform_initialize_jni メソッドの最後は解放処理です。jstring の変数は解放処理を行わなくてはなりません。また、methodInfo.classID に関しても同様に開放処理を行ないます。

7-6 mobageplatform_setDelegate メソッド

    void mobageplatform_setDelegate_jni(CCMobagePlatformDelegate* delegate) 
    {
        platformDelegate = delegate;
    }

cocos2d-x (C++) 側から、CCMobagePlatformDelegate オブジェクトを受け取ります。

7-7 その他のメソッド

その他のメソッド、mobageplatform_hideSplashScreen_jni, mobageplatform_checkLoginStatus_jni, mobageplatform_showLoginDialog_jni に関してもこれまでと同様のの方法で実装していきます。

8. Android.mk の編集

追加した CCMobagePlatform.h, CCMobagePlatform.cpp, CCMobagePlatformJni.h, CCMobagePlatform.cpp を有効にするためには、Project ルートの jni ディレクトリに存在する Android.mk ファイルを編集して、ビルド時に含めるようにしなくてはなりません。これは cocos2d-x プロジェクトに新たな C++ のファイルを追加する場合も同様です。

LOCAL_SRC_FILES := hellocpp/main.cpp \
                   ../../Classes/AppDelegate.cpp \
                   ../../Classes/HelloWorldScene.cpp \
                   ../../Classes/Cocos2dxMobageWrapper/Android/CCMobagePlatform.cpp \
                   ../../Classes/Cocos2dxMobageWrapper/Android/jni/CCMobagePlatformJni.cpp

上記のように、LOCAL_SRC_FILES に CCMobagePlatform.cpp と CCMobagePlatformJni.cpp を追加します。

※ また、これまで実装してきたファイルは libcocos2dcpp に含まれることとなり、Main の Activity に実装されている System.loadLibrary("cocos2dcpp"); が呼び出された時点で Java 側から使用できるようになります。

9. Java 側での実装

最初に説明したとおり、com.mobage.cocos2dx.lib パッケージの下に MobageCocos2dxMobagePlatform.java として実装していきます。基本的な実装は、com.mobage.android.Mobage と com.mobage.android.Mobage.PlatformListener の実装を JNI へ受け渡すのが目的となっています。

MobageCocos2dxMobagePlatform.java
package com.mobage.cocos2dx.lib;

import com.mobage.android.Mobage;
import com.mobage.android.Mobage.PlatformListener;
import com.mobage.android.Error;

import android.app.Activity;
import android.os.Looper;
import android.util.Log;

public class MobageCocos2dxMobagePlatform {

    private static native void mobageplatformLoginlistenerOnLoginRequired();
    private static native void mobageplatformLoginlistenerOnLoginComplete(String userId);
    private static native void mobageplatformLoginlistenerOnLoginError(int code, String description);
    private static native void mobageplatformLoginlistenerOnLoginCancel();
    private static native void mobageplatformLoginlistenerOnSplashComplete();


    private static PlatformListener _platformListener = null;

    private static Activity _activity = null;

    public static void setActivity(final Activity activity) {
        _activity = activity;
    }

    protected static void setPlatformListener() {
        if (_activity == null) {
            return;
        }
        if (_platformListener == null) {
            _platformListener = new PlatformListener() {
                @Override
                public void onLoginComplete(String userId) {
                    MobageCocos2dxMobagePlatform.mobageplatformLoginlistenerOnLoginComplete(userId);
                }

                @Override
                public void onLoginError(Error error) {
                    int code = error.getCode();
                    String description = error.getDescription();
                    MobageCocos2dxMobagePlatform.mobageplatformLoginlistenerOnLoginError(code, description);
                }

                @Override
                public void onLoginCancel() {
                    MobageCocos2dxMobagePlatform.mobageplatformLoginlistenerOnLoginCancel();
                }

                @Override
                public void onLoginRequired() {
                    MobageCocos2dxMobagePlatform.mobageplatformLoginlistenerOnLoginRequired();
                }

                @Override
                public void onSplashComplete() {
                    MobageCocos2dxMobagePlatform.mobageplatformLoginlistenerOnSplashComplete();
                }
            };
        }
        Mobage.addPlatformListener(_activity, _platformListener);
    }

    public static void mobageplatform_initialize(final int region, final int serverMode, final String consumerKey, final String consumerSecret, final String appId) {
        if (_activity == null) {
            return;
        }
        Mobage.Region mobageRegion;
        Mobage.ServerMode mobageServerMode;

        switch (region) {
        case 0:
            mobageRegion = Mobage.Region.US;
            break;
        case 1:
            mobageRegion = Mobage.Region.JP;
            break;
        case 2:
            mobageRegion = Mobage.Region.KR;
            break;
        case 3:
            mobageRegion = Mobage.Region.CN;
            break;
        default:
            mobageRegion = Mobage.Region.JP;
            break;
        }

        switch (serverMode) {
        case 0:
            mobageServerMode = Mobage.ServerMode.SANDBOX;
            break;
        case 1:
            mobageServerMode = Mobage.ServerMode.PRODUCTION;
            break;
        default:
            mobageServerMode = Mobage.ServerMode.SANDBOX;
            break;
        }

        try {
            Handler handle = new Handler(Looper.getMainLooper());
            final CountDownLatch latch = new CountDownLatch(1);
            handle.post(new Runnable(){
                public void run(){
                    Mobage.initialize(mobageRegion, mobageServerMode, consumerKey,
                            consumerSecret, appId, _activity);
                    latch.countDown();
                }
            }); 
            latch.await();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        mobageplatform_onCreate();
        mobageplatform_onStart();
        mobageplatform_onResume();

        setPlatformListener();
        return;
    }

    public static void mobageplatform_onCreate() {
        if (_activity == null) {
            return;
        }
        Mobage.onCreate(_activity);
        setPlatformListener();
    }

    public static void mobageplatform_onStart() {
        if (_activity == null) {
            return;
        }
        Mobage.onStart(_activity);
    }

    public static void mobageplatform_onRestart() {
        if (_activity == null) {
            return;
        }
        Mobage.onRestart(_activity);
        setPlatformListener();
    }

    public static void mobageplatform_onResume() {
        if (_activity == null) {
            return;
        }
        Mobage.onResume(_activity);
    }

    public static void mobageplatform_onPause() {
        if (_activity == null) {
            return;
        }
        Mobage.onPause(_activity);
    }

    public static void mobageplatform_onStop() {
        if (_activity == null) {
            return;
        }
        Mobage.onStop(_activity);
    }

    public static void mobageplatform_onDestroy() {
        if (_activity == null) {
            return;
        }
        Mobage.onDestroy(_activity);
    }
    public static void mobageplatform_checkLoginStatus() {
        Mobage.checkLoginStatus();
    }

    public static void mobageplatform_showLoginDialog() {
        Mobage.showLoginDialog();
        return;
    }

    public static void mobageplatform_hideSplashScreen() {
        Mobage.hideSplashScreen();
    }
}

[2014/04/22 更新] 上記コードにログイン周りで不具合があったため、以下を追加しました。

  • mobageplatform_onCreate と mobageplatform_onRestart に setPlatformListener を追加しました。
  • mobageplatform_initialize で mobageplatform_onCreate, mobageplatform_onStart, mobageplatform_onResume を呼び出すように追加しました。

MobageCocos2dxMobagePlatform クラスについて、上から順に説明していきます。

9-1 JNI のインターフェース宣言

    private static native void mobageplatformLoginlistenerOnLoginRequired();
    private static native void mobageplatformLoginlistenerOnLoginComplete(String userId);
    private static native void mobageplatformLoginlistenerOnLoginError(int code, String description);
    private static native void mobageplatformLoginlistenerOnLoginCancel();
    private static native void mobageplatformLoginlistenerOnSplashComplete();

CCMobagePlatformJni に実装したメソッドを呼び出すために、private static native の形でメソッドを宣言します。こうすることで、JNI に実装した Java_com_mobage_cocos2dx_lib_MobageCocos2dxMobagePlatform_mobageplatformLoginlistenerOnLoginComplete メソッドが Java 側から呼び出せるようになります。

9-2 クラスメンバ変数

    private static PlatformListener _platformListener = null;
    private static Activity _activity = null;

com.mobage.android.Mobage.PlatformListener と android.app.Activity のクラスメンバ変数を宣言します。Mobage Native SDK for Android では PlatformListener の設定が必須ですが、PlatformListener を固定するためにクラスメンバ変数として宣言しています。また Activity に関しては、com.mobage.android.Mobage に含まれる多くのメソッドで Acitivity が必要になるためにクラスメンバ変数として宣言しました。

9-3 setActivity メソッド

    public static void setActivity(final Activity activity) {
        _activity = activity;
    }

Activity を設定するために実装します。setActivity は Main の Activity が作成された瞬間に呼び出されるように後で実装します。

9-4 setPlatformListener メソッド

setPlatformListener メソッドは、CCMobagePlatformDelegate オブジェクトを addPlatformListener に設定した PlatformListener から呼び出すためのメソッドです。例えば onLoginComplete が呼び出された場合には、先ほど宣言したMobageCocos2dxMobagePlatform.mobageplatformLoginlistenerOnLoginComplete メソッドが呼び出されるように実装していきます。

※  PlatformListener に関しては、以下のドキュメントの「ログインリスナーの実装」をご確認下さい。

https://docs.mobage.com/display/JPSA/Native+Tutorial+Chapter+2+Android+Develop

9-5 mobageplatform_initialize メソッドの実装

mobageplatform_initialize メソッドを実装していきます。

        switch (region) {
        case 0:
            mobageRegion = Mobage.Region.US;
            break;
        case 1:
            mobageRegion = Mobage.Region.JP;
            break;
        case 2:
            mobageRegion = Mobage.Region.KR;
            break;
        case 3:
            mobageRegion = Mobage.Region.CN;
            break;
        default:
            mobageRegion = Mobage.Region.JP;
            break;
        }

まずは、int で渡ってきた Region 情報を Mobage.Region クラスに変換します。

        switch (serverMode) {
        case 0:
            mobageServerMode = Mobage.ServerMode.SANDBOX;
            break;
        case 1:
            mobageServerMode = Mobage.ServerMode.PRODUCTION;
            break;
        default:
            mobageServerMode = Mobage.ServerMode.SANDBOX;
            break;
        }

serverMode に関しても同様に Mobage.ServerMode クラスに変換します。

         try {
            Handler handle = new Handler(Looper.getMainLooper());
            final CountDownLatch latch = new CountDownLatch(1);
            handle.post(new Runnable(){
                public void run(){
                    Mobage.initialize(mobageRegion, mobageServerMode, consumerKey,
                            consumerSecret, appId, _activity);
                    latch.countDown();
                }
            }); 
            latch.await();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

Mobage Native SDK for Android の Mobage.initialize メソッドはメインスレッド以外で呼ぶことができません。cocos2d-x 側の例えば AppDelegate.cpp 等は、メインスレッドではない為、そのまま cocos2d-x 側から CCMobagePlatform#initialize を呼び出してしまうとエラーとなってしまいます。

そのため、Looper.getMainLooper でメインスレッドの Looper を取得して、処理を post します。CountDownLatch を使用している理由は、Mobage.initialize の処理を待つためです。

        setPlatformListener();

Mobage.initialize が完了してから、先ほど実装した setPlatformListener メソッドを呼び出して、 PlatformListener を設定します。

9-6 ライフサイクルメソッドの実装

    public static void mobageplatform_onCreate() {
        if (_activity == null) {
            return;
        }
        Mobage.onCreate(_activity);
    }

    public static void mobageplatform_onStart() {
        if (_activity == null) {
            return;
        }
        Mobage.onStart(_activity);
    }

    public static void mobageplatform_onRestart() {
        if (_activity == null) {
            return;
        }
        Mobage.onRestart(_activity);
    }

    public static void mobageplatform_onResume() {
        if (_activity == null) {
            return;
        }
        Mobage.onResume(_activity);
    }

    public static void mobageplatform_onPause() {
        if (_activity == null) {
            return;
        }
        Mobage.onPause(_activity);
    }

    public static void mobageplatform_onStop() {
        if (_activity == null) {
            return;
        }
        Mobage.onStop(_activity);
    }

    public static void mobageplatform_onDestroy() {
        if (_activity == null) {
            return;
        }
        Mobage.onDestroy(_activity);
    }

上記のように、onCreate, onStart, onRestert, onResume, onPause, onStop, onDestory をそれぞれ実装していきます。これらのメソッドは、Java 側から直接呼び出されることになります。Mobage.onCreate 等に渡す Activity は _activity を指定します。

9-7 その他のメソッドの実装

mobageplatform_checkLoginStatus, mobageplatform_showLoginDialog, mobageplatform_hideSplashScreen のそれぞれのメソッドもこれまで同様にブリッジとして実装しておきます。

10. Main Activity への実装

Mobage Native SDK for Android では、Main Activity での実装が必須になる部分があります。具体的には、以下の様な実装が必要になります。

    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);    
        
        MobageCocos2dxMobagePlatform.setActivity(this);
        MobageCocos2dxMobagePlatform.mobageplatform_onCreate();
    }
    @Override
    protected void onStart()
    {
        super.onStart();
        MobageCocos2dxMobagePlatform.mobageplatform_onStart();
    }
    @Override
    protected void onResume()
    {
        super.onResume();
        MobageCocos2dxMobagePlatform.mobageplatform_onResume();
    }
    @Override
    protected void onRestart()
    {
        super.onRestart();
        MobageCocos2dxMobagePlatform.mobageplatform_onRestart();
    }
    @Override    
    protected void onStop()
    {
        super.onStop();
        MobageCocos2dxMobagePlatform.mobageplatform_onStop();
    }
    @Override
    protected void onPause()
    {
        super.onPause();
        MobageCocos2dxMobagePlatform.mobageplatform_onPause();
    }
    @Override
    protected void onDestroy()
    {
        super.onDestroy();
        MobageCocos2dxMobagePlatform.mobageplatform_onDestroy();
    }

[2014/04/22 更新] 上記コードにログイン周りで不具合があったため、以下を追加しました。

  • onStart 等のライフサイクル系のメソッドが Override になっていなかったので、修正しました。

cocos2d-x の Main Activity でオーバーライドされている onCreate 内に、Activity をセットするための MobageCocos2dxMobagePlatform.setActivity(this); と、Mobage Native SDK を使用する際に必要なライフサイクルメソッドの呼び出しを行うために、 MobageCocos2dxMobagePlatform.mobageplatform_onCreate(); を呼び出します。

onStart, onRestert, onResume, onPause, onStop, onDestory に関しては、オーバーライドし、それぞれのライフサイクルメソッドの呼び出しを行ないます。

11. やっと呼び出し

長くなってしまいましたが、やっと呼び出しです!

呼び出し部分の実装に関しては、『Mobage Native SDK を cocos2d-x で使ってみた(iOS 編)』の「8. いよいよ呼び出し」と全く同じとなりますのでそちらを参照して、AppDelegate.h, AppDelegate.cpp と呼び出し部分をそれぞれ実装して下さい。

呼び出しに成功すると、iOS 同様、以下の様な画面が表示されます。

image3.png

また、「モバゲー ID ではじめる」ボタンをタップし、ログインできるか確認します。ログイン画面がアプリ上で表示されますので、Mobage のテストアカウントの ID / パスワードを入力してログインします。

image4.png

ログ上に userId:XXXXX のような表示が出ていればログイン完了です!以下のようにゲーム画面に遷移します。

image5.png

最後に

いかがでしたか?前回と今回の記事で Mobage Native SDK を cocos2d-x で使うための一助になりましたら幸いです。

また、記事中で何度か触れていますが、作成したコードをもとにしたサンプルを時期は未定ですが、github で公開する予定です。

本記事に関するご不明な点やお問い合わせもお気軽にお寄せください。