Cocos2d-xのPluginを作る

前提

  • cocos2d-xのディレクトリを汚さずにiOS/Androidで使えるPluginを作る
  • メソッドの引数にURLを指定すると、そのURLを外部ブラウザで開くPluginを作る
  • JNIの解説はしない
  • 細かいこと気にしない(とりあえず動かすのを目標にする)
  • 作業時のcocos2d-xのversionは3.1.1
    • version依存のところは無いと思うけど、一応記載

Plugin入れるところ

Android.mk書いて、パス通せば何処に入れても良いんだけど、今回はcocosコマンドで作ったプロジェクトにlibs/を作って、そこにPluginを入れる。

.
├── CMakeLists.txt
├── Classes
├── Resources
├── cocos2d
├── libs // <- ここに入れる
├── proj.android
└── proj.ios_mac

フォルダ作る

libs/にsample_pluginってフォルダを作って、そこで作業する。 iOSの場合、Xcodeに追加するのはsample_plugin/sample.hsample_plugin/ios/以下。

sample_plugin
├── sample.h // 共通のコードは`sample_plugin/`に直接置く
├── android // Android用のcpp/jni/javaのコード
│   ├── Android.mk
│   ├── sampleAndroid.cpp
│   ├── java
│   └── jni
└── ios // iOS用のコード
    └── sampleIOS.mm

共通のinterfaceを宣言する

sample.hに以下の宣言をする。Sample::func()を実現するのに、Android/iOSのネイティブの機能を呼び出したいという感じ。

#ifndef __Sample_H_
#define __Sample_H_

class Sample {
public:
    static void func(std::string text);
};

#endif //__Sample_H_

iOSの実装をする

sample_plugin/ios/sampleIOS.mmiOS用の実装を書いていく。ファイルの命名は、cocos2d-xに準じた。

Objective-C++なので、C++メソッドの中でUIApplicationなどのクラスを使える。 iOSについては、全く難しいところが無いのでさらっと終わる。

#include "sample.h"

void ShareText::func(std::string text) {
    [[UIApplication sharedApplication] openURL:@(text.c_str())];
}

Androidの実装

  • cocos2d-xから呼び出すC++
  • C++からJavaを呼び出すためのJNI(sampleJni.cpp/sampleJni.h)
  • JNIから呼ばれるJava

cocos2d-xからAndroidネイティブコード呼び出すのには、コード書くのだけで上記作業が必要。今回は上から順に作業していくけど、別の所にAndroidのプロジェクトを作って、そこでJavaだけ書いてから、C++と繋いだほうがスムーズに行くと思う。

上記とは別に、書いたコードをcocos2d-xで利用するために、以下の作業が必要。

  • Plugin用のAndroid.mkを書く
  • Plugin用のandroidProject作る
  • proj.android/jni/Android.mkに作ったPluginを追加する
  • proj.androidにPluginのクラスパスを追加する

大変だった。

cocos2d-xから呼び出すC++クラスの実装

C++のコードからは、JNIのメソッドを呼び出すだけにした。 具体的な実装は、極力funcJNIから呼び出されるjavaのコードで行う。

#include "sample.h"
#include "jni/sampleJni.h"

void ShareText::func(std::string text) {
    funcJNI(text);
}

C++からJavaを呼び出すためのJNI書く

sampleJni.h

Cで書いた。調べた感じ、cppでも書けるっぽいけど、cocos2d-xのJNIコードはCで書かれてたので、それを参考にした

#ifndef __Java_org_Sample_H_
#define __Java_org_Sample_H_

extern "C"
{
  void funcJNI(std::string url);
}

#endif //__Java_org_Sample_H_

sampleJni.cpp

cocos2d::JniHelperがJNI使うの少し楽にしてくれてるっぽい。 package me.gin0606SampleHelperというクラスを作成して、その中のstaticメソッドを呼び出す事にしている。SampleHelper.javaはあとで書く。

#include "jni/JniHelper.h"
#include <string.h>
#include <jni.h>

// packageの`.`を`/`にしたものを使う。
#define kSampleHelper "me/gin0606/SampleHelper"

using namespace cocos2d;

extern "C" {
  void funcJNI(std::string url) {
    JniMethodInfo t;
    if (JniHelper::getStaticMethodInfo(t, kShareTextHelper, "funcJava", "(Ljava/lang/String;)V")) {
      jstring jUrl = t.env->NewStringUTF(url.c_str());
      t.env->CallStaticVoidMethod(t.classID, t.methodID, jUrl);

      t.env->DeleteLocalRef(jUrl);
      t.env->DeleteLocalRef(t.classID);
    }
  }
}

PluginのAndroid.mkを書く

Java呼び出す前に、一旦funcJNIの中身を空にして、先にAndroid.mkを書いてしまう。 これを書いて、cocos2d-xからSample::funcを呼び出せば、funcJNIが呼ばれるようになる。わかりやすいようにLog仕込んでもいいと思う。

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE := sample_static

LOCAL_MODULE_FILENAME := libsample

LOCAL_SRC_FILES := \
sampleAndroid.cpp \
jni/sampleJni.cpp

LOCAL_WHOLE_STATIC_LIBRARIES := cocos2dx_static

LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
LOCAL_EXPORT_C_INCLUDES += $(LOCAL_PATH)/..

LOCAL_C_INCLUDES := $(LOCAL_PATH)/../ \
                    $(LOCAL_PATH)/ \
                    $(LOCAL_PATH)/jni

include $(BUILD_STATIC_LIBRARY)
$(call import-module,.)

JNIから呼ばれるJava書く

funcJNIを元に戻して、Javaを書く。

Androidプロジェクト作成

この作業をやらなくても、proj.android/srccocos2d/cocos/platform/android/java/srcJavaのファイルを置いてしまってもいいが、「cocos2d-xのディレクトリを汚さず」という前提があるので、lib-projectを作る。

cd libs/sample_plugin/android/java
android create lib-project --target android-14 --package me.gin0606 --path ./

これを実行すると、AndroidManifest.xmlなどのファイルが生成される。

パス設定

作ったProjectから、cocos2d-xのパッケージにアクセス出来るようにする。

cd libs/sample_plugin/android/java
android update project -p . -l ../../../../cocos2d/cocos/platform/android/java

これでimport org.cocos2dx.lib.Cocos2dxActivity;とか出来るようになった。

Java書く

libs/sample_plugin/android/java/src/me/gin0606/SampleHelper.javaを作って、Java書く。

cocos2d-xのAndroidアプリの大本のCocos2dxActivityがgetContextでアプリのContext(実体はActivity)を何処からでも取れるので、ネイティブコード書きやすいと思う。

package me.gin0606;

import org.cocos2dx.lib.Cocos2dxActivity;
import android.content.Intent;
import android.net.Uri;
import android.app.Activity;

public class SampleHelper {
  public static void funcJava(String url){
    Intent intent = new Intent();
    intent.setAction(Intent.ACTION_VIEW);
    intent.setData(Uri.parse(url));
    try {
      ((Activity)Cocos2dxActivity.getContext()).startActivityForResult(intent, 0);
    } catch (Exception e) {
    }
  }
}

アプリに作ったPluginを含める

cd proj.android
android update project -p . -l ../libs/sample_plugin/android/java

これを実行すると、proj.android/project.propertiesに差分が出る。android.library.reference.1がcocos2dのjavaで、android.library.reference.2が作ったjavaになるはず。

アプリ起動する

あとは任意の方法でiOS/AndroidのアプリをBuild&Runすればいい。

終わり。

参考