gracetory’s blog

東池袋にある合同会社グレストリのエンジニアブログです

Cocos2d-xで「NIFTY Cloud mobile backend」導入してみた(Android編)

f:id:gmatsu:20170830121751j:plain

夏真っ盛り、というかもう終盤ですね。

毎年8月よりも9月の方が暑い気がします。

こんにちは、プログラマのgmatsuです。

ドラクエ11は全クリし、今はリネレボやってます。

以前リネージュ1をプレイしていましたので、懐かしさも相まってとても楽しいです。

9月はメトロイド、10月は女神転生。

ああ、忙しい…。

前回に引き続き、今回もNIFTY Cloud mobile backendの導入についてのお話となります。

以前紹介したiOS編はこちらになります。

techblog.gracetory.co.jp

iOS版の実装が終わっている事が今回のお話での前提となります。

Android版の紹介ではありますが、前回で既に実装されている物もありますのでお気をつけ下さい。

cocos2d-xバージョン

v3.15.1

NCMBバージョン

Android

v2.3.0

導入手順にあたっての前提

前回のiOS版実装が完了済み

導入手順

Android

1.NCMB用Androidライブラリの作成と設置

iOSとAndroidのネイティブ処理に必要なファイルがあっちこっちに分かれてしまう事がcocosを使っていて多々ありました。

Android用のSDKをproj.android-studio以下に設置したりするのが嫌だったので、Androidライブラリとして1つにまとめてしまう事にしました。

これであればClassesの中にiOS版とまとめて設置できるので精神衛生的にも非常によろしかったです。

そもそもAndroidのライブラリを作ったことがなかったので調べて所、AndroidStudioで作成とのことでした。

ただ、こんな作り方で良かったのかは自信がありませんので変だなと思った方はコメントを頂ければ幸いです。

自分が行った手順は次の通りです。

  1. ライブラリ作成用のプロジェクトを作成
  2. ライブラリを作成
  3. ライブラリを作成用プロジェクトから抜き出してcocos2d-xのプロジェクトに設置
  4. 各種設定ファイルにライブラリを使用する為の記述を追加

とりあえず手順通りやっていきたいと思います。

1.ライブラリ作成用のプロジェクトを作成

ここからの作業はAndroidStudioで行います。

プロジェクトを作るだけですので、特に難しい事は無いと思います。

私は「AndroidLibrary」という名前にしました。

2.ライブラリを作成

次は1.で作成したプロジェクトにライブラリを作成します。

作成のウィンドウは以下の手順で出せます。

File > New > New Module…

すると次のようなウィンドウが出ます。

f:id:gmatsu:20170830130637p:plain

作成する物の種類を選択します。

今回は「Android Library」を選択して下さい。

f:id:gmatsu:20170830130826p:plain

次はライブラリの名前等を決めて行きます。

今回は「NcmbManager」としています。

Package nameの変更は右の方にある「Edit」から行えます。

後は「Finish」ボタン押下でライブラリが作成されます。

3.ライブラリを作成用プロジェクトから抜き出してcocos2d-xのプロジェクトに設置

作成が終わったら次はcocosのプロジェクトに設置を行います。

コードは設置後に書いて行きますのでまずはちゃちゃっと置いてしまいましょう。

作成後のプロジェクトには次のようにライブラリが設置されています。

f:id:gmatsu:20170830133224p:plain

この中身を全て、cocosのプロジェクトへと移動してしまいましょう。

Classes/External/Ncmb/Android/

f:id:gmatsu:20170830133438p:plain

その後、

Classes/External/Ncmb/Android/src/main/Androidmanifest.xml

Classes/External/Ncmb/Android/

に移動。

Classes/External/Ncmb/Android/src/test/java/com/example/gmatsu/ncmbmanager/ExampleUnitTest.java
の.javaファイルを
NcmbManager.java

に変更。

Classes/External/Ncmb/Android/src/test/java/com

Classes/External/Ncmb/Android/src/

に移動します。

Classes/External/Ncmb/Android/src/
にある、com/以外のディレクトリは消してしまってください。

また、

Classes/External/Ncmb/Android/

NcmbManager.cpp

を作成。

Classes/External/Ncmb/Android/libs/
にはNCMBのAndroidSDKを入れておきましょう。

すると次のようなディレクトリ構造になるはずです。

f:id:gmatsu:20170830134818p:plain

ここまでで設置が完了です。

文にすると結構長い…。

4.各種設定ファイルにライブラリを使用する為の記述を追加

後は設定の記述をしてしまえばライブラリの準備は完了です。

Classes/External/Ncmb/Android/build.gradle

apply plugin: 'com.android.library'

android {
    compileSdkVersion 22
    buildToolsVersion "25.0.0"

    defaultConfig {
        minSdkVersion 10
        targetSdkVersion PROP_TARGET_SDK_VERSION
        versionCode 1
        versionName "1.0"
    }

    sourceSets.main {
        java.srcDir "src"
        jniLibs.srcDir "libs"
        manifest.srcFile "AndroidManifest.xml"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            if (project.hasProperty("RELEASE_STORE_FILE")) {
                signingConfig signingConfigs.release
            }
        }
    }
}

dependencies {
    compile project(':libcocos2dx')
    compile 'com.google.code.gson:gson:2.3.1'
    compile files('libs/NCMB.jar')
}

java.srcDirにjavaファイルのルートとなるディレクトリの指定、dependenciesにNCMBのSDKを追記します。

Classes/External/Ncmb/Android/AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.gmatsu.ncmbmanager">

    <application android:allowBackup="true">
    </application>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>

パッケージ名、通信設定等を指定。

proj.android-studio/settings.gradle

include ':libcocos2dx'
project(':libcocos2dx').projectDir = new File(settingsDir, '../cocos2d/cocos/platform/android/libcocos2dx')
include ':mbaas_test'
project(':mbaas_test').projectDir = new File(settingsDir, 'app')
include ':ncmbmanager'
project(':ncmbmanager').projectDir = new File(settingsDir, '../Classes/External/Ncmb/Android')

ライブラリのパスを追加。

proj.android-studio/app/build.gradle

    :
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile project(':libcocos2dx')

    compile project(':controlotherapp')
}

ライブラリ名の追加。

proj.android-studio/app/jni/Android.mk

    :
LOCAL_SRC_FILES := hellocpp/main.cpp \
                   ../../../Classes/AppDelegate.cpp \
                   ../../../Classes/HelloWorldScene.cpp \
                   ../../../Classes/External/Ncmb/Android/NcmbManager.cpp
    :
LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../../Classes
LOCAL_C_INCLUDES += $(LOCAL_PATH)/../../../Classes/External/Ncmb

ビルドに加えるソースファイル、ヘッダファイルを追加。

ここまでの設定が終わると以下の処理をコメントにする事でAndroidのビルドが通るようになります。

  1. AppDelegate.cpp、HelloWorld.cpp等でのNcmbManager.hのインクルード
  2. NcmbManager.cppに何も記述しない、又は全てコメント
  3. NcmbManager.javaの「package com.example.gmatsu.ncmbmanager;」以外が全てコメント

iOSで既に実装していた部分等ですね。

やっと設置が終わりました…。

後はソースを書いていくことになります。

2.NCMBのAPI呼び出し作成

APIの呼び出しはjavaで書きます。

先程名前を変えたファイル、

Classes/External/Ncmb/Android/src/com/example/gmatsu/ncmbmanager/NcmbManager.java

に処理を書いていきます。

Classes/External/Ncmb/Android/src/com/example/gmatsu/ncmbmanager/NcmbManager.java

package com.example.gmatsu.ncmbmanager;

import android.util.Log;
import android.content.Context;
import org.cocos2dx.lib.Cocos2dxActivity;
import com.nifty.cloud.mb.core.NCMB;
import com.nifty.cloud.mb.core.NCMBException;
import com.nifty.cloud.mb.core.NCMBObject;
import com.nifty.cloud.mb.core.DoneCallback;

public class NcmbManager {
    // Context
    private static Context context = null;

    /**
     * @brief SDK初期化
     */
    static public void init(String app_key, String client_key) {
        // Context取得
        context = Cocos2dxActivity.getContext();
        
        // NCMB初期化
        NCMB.initialize(context.getApplicationContext(), app_key, client_key);
    }

    /**
     * @brief データ登録
     * @param const char* class_name クラス名
     * @param int lv レベル
     * @param int hp HP
     * @param int exp 経験値
     */
    static public void add(String class_name, int lv, int hp, int exp) {
        // NCMBObject生成
        NCMBObject obj = new NCMBObject(class_name);

        // 登録データの指定
        obj.put("lv", lv);
        obj.put("hp", hp);
        obj.put("exp", exp);

        // 登録
        obj.saveInBackground(new DoneCallback() {
            @Override
            public void done(NCMBException error) {
                if (error != null) {
                    // 失敗
                    Log.d("NCMB", "データ登録失敗");

                } else {
                    // 成功
                    Log.d("NCMB", "データ登録失敗");
                }
            }
        });
    }
}

iOS側(NcmbManager.mm)に準備した同様の役割、名前の関数を準備しています。

初期化方法、登録データの設定・登録の流れはほとんどiOS版と変わりません。

JavaとObjective-cの違いがあるくらいです。

イントロダクション (Android) : クイックスタート | ニフティクラウド mobile backend

公式のドキュメントに詳細が載っていますので詳しくはそちらで確認してください。

3.C++からJavaの関数を呼び出す

Objective-cのようにC++のコードからそのままJavaの関数が呼び出せれば良いのですが、それは行なえません。

そこでJNIを利用し、C++からJavaの関数を呼び出せるようにします。

JNIについてはこちらのサイトで解説して下さっています。

qiita.com

Classes/External/Ncmb/Android/NcmbManager.cpp

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

namespace NcmbConnect {
    using namespace cocos2d;
    const char* MBAAS_CONNECT_CLASS_NAME = "com/example/gmatsu/ncmbmanager/NcmbManager";

    /**
     * @brief コンストラクタ
     */
    NcmbManager::NcmbManager() {
    }

    /**
     * @brief デストラクタ
     */
    NcmbManager::~NcmbManager() {
    }

    /**
     * @brief SDK初期化
     * @param app_key アプリケーションキー
     * @param client_key クライアントキー
     */
    void NcmbManager::init(const char* app_key, const char* client_key) {
        cocos2d::JniMethodInfo method_info;

        // java内関数取得
        if (cocos2d::JniHelper::getStaticMethodInfo(method_info, MBAAS_CONNECT_CLASS_NAME, "init","(Ljava/lang/String;Ljava/lang/String;)V")) {
            // 文字列変換
            jstring in_app_key = method_info.env->NewStringUTF(app_key);
            jstring in_client_key = method_info.env->NewStringUTF(client_key);

            // Java関数呼び出し
            method_info.env->CallStaticVoidMethod(method_info.classID, method_info.methodID, in_app_key, in_client_key);

            // 解放
            method_info.env->DeleteLocalRef(in_app_key);
            method_info.env->DeleteLocalRef(in_client_key);
            method_info.env->DeleteLocalRef(method_info.classID);
        }
    }

    /**
     * @brief データ登録
     * @param class_name クラス名
     * @param lv レベル
     * @param hp HP
     * @param exp 経験値
     */
    void NcmbManager::add(const char* class_name, int lv, int hp, int exp) {
        cocos2d::JniMethodInfo method_info;

        // java内関数取得
        if (cocos2d::JniHelper::getStaticMethodInfo(method_info, MBAAS_CONNECT_CLASS_NAME, "add", "(Ljava/lang/String;III)V")) {
            // 文字列変換
            jstring in_class_name = method_info.env->NewStringUTF(class_name);

            // Java関数呼び出し
            method_info.env->CallStaticVoidMethod(method_info.classID, method_info.methodID, in_class_name, lv, hp, exp);

            // 解放
            method_info.env->DeleteLocalRef(in_class_name);
            method_info.env->DeleteLocalRef(method_info.classID);
        }
    }
};

これでJava側の関数をC++で呼び出せるようになります。

ちなみにヘッダファイルと関数の呼び出し箇所ですが、新しく追記する必要はありません。

今回はiOS版で用意したヘッダファイル、呼び出しがそのまま使えるように作成を行いました。

関数名を同じにしていますので、iOSで実行した際はNcmbManager.mmの関数、Androidで実行した時はNcmbManager.cppから呼び出したNcmbManager.javaの関数が呼び出されるようになります。

iOS、Androidの関数名を分けてしまうと、OSを判別するマクロ等を利用しなければなりませんからこちらの方が断然扱いやすいと思います。

NCMBについては以上です。

そういえば今日からCEDEC2017が始まりましたね。

初参加なので少し緊張していますが、著名な方々のお話が聞けるのはとても楽しみです。

それではまた。