アプリ事業部で主にAndroid開発を担当しています。簡単に自己紹介をしますとオープン系SIerからスタートしてクラウドサービスの立ち上げおよびモバイル開発を経験してまいりました。Speee入社してからは主にAndroid開発に努めています。詳しい情報はMEMBERSページをご覧ください。概要さて、今回お話する内容はiOSソシャゲをAndroid向けに移植した時のことです。タイトル通りにアプリ一部機能だけをcocos2d-xに実装する必要があったのでそれに関する内容になります。移植元のアプリは主にWebViewで動作するアプリです。コアな部分としてバトル機能があり、そこだけがcocos2dで実装されていました。ところがandroidにはcocos2dをそのまま使えることはできないためcocos2d-xに書き換える必要があります。3.0は独自インターフェースになっています。)工数コストは置いといても、問題になるのは構成上の連携方式です。objective-cでそのまま書くcocos2dに対し、cocos2d-xはc++で実装した上でJNI経由で利用することになります。androidの構成図です。Interfaceを経由しなければなりませんが、今回はcocos2d-xとandroidとの連携だけ紹介します。ゲームの流れとしてバトルシーンが呼ばれるタイミングはマップ(webview)上の敵と遭遇した時です。webviewのjavascriptがバトル開始とそのパラメータ(json)をandroid(activity)に渡します。activityはそのパラメータを基にバトルシーンを呼び出します。バトルが終わったら呼び出し元のactivityを再び呼び出す(もちろん結果パラメータと一緒に)ことになります。その一連の処理が何十回も続けて行います。ゲームの流れの簡単な図です。基本的にはマップ上イベント(バトル)が中心になる流れです。この中で、バトル開始・終了と課金への導線がjavaであるactivityとc++であるcocos2d-xとの連携部分です。「ただJNIで連携部だけ実装すればいいのでは?」と思う方もいるでしょう。実際にはもっと複雑かつ面倒なことになります。その中で何個かを簡単にまとめてみました。c++側のバトル開始と終了のクラスが異なるためJNI受け口の引数参照仕組みが必要CCDirectorのendメソッドを使うとアプリが完全終了される先読み(CCSpriteFrameCache)が10倍以上遅いCCLabel系の一部UI部品が化けるそれではどのように対応したのか簡単にお話します。Interface)について次に進める前にJNIを利用するための準備についてご紹介します。APIを利用するためのインタフェース仕様です。javaからネイティブ呼び出しはもちろんネイティブからjavaを呼び出すこともできます。javaの拡張性に深く関係する重要な機能の一つとも言えるでしょう。androidでもJNIを利用できます。SDKでは開発できないためNDKおよびcocos2d-x開発環境構築が必要です。今回の趣旨には合わないため以下の手順に関しては他記事を参考してください。ここでは簡単に手順だけまとめました。生成されたsoファイルを該当Activity内に静的ライブラリとして宣言cocos2d-xからjavaを呼び出す時の引数問題バトルはいろんな機能を持っているため数十個のクラスで構成されています。iOSではcocos2dとネイティブの区別を認識しなくても特に問題ないためどこからもバトル終了や課金処のような連携ができますが、androidではそのような連携処理をJNI経由で行うため開始時で渡された引数を保持する必要があります。参照カウンターを増やします。Applicationに渡します。CCApplication::sharedApplication()->run();}第2引数をNewGlobalRefを利用してjavaによるGCで回収されないようにしています。NewGlobalRefしなかった場合は後で参照すると下記のエラーになります。8)そしてAppDelegateに渡してどのクラスでも参照出来るようにします。このようにJNI受け口の第2引数だけ保持して置けば特に問題はありません。下記は例として終了するコードです。applicationオブジェクトを取得します。infoから参照できます。これをしないと解除されません。cocos2d-xを完全終了します。CCDirector::sharedDirector()->end();}CCDirector終了時の挙動問題バトルを終了するため先ほどCCDirectorのendメソッドを利用しましたが、バトルだけではなくアプリ全体が完全終了されます。これはcocos2d-xでそのように実装されているためです。望ましいのはバトルの使い回しですがアプリ仕様関係上バトルが終わったタイミングでcocos2d-xを完全解除する必要がありました。それではCCDirectorのendではどのようにアプリを完全終了しているかについて確認しましょう。android.os.Process.killProcess(android.os.Process.myPid());}結果的にCCDirectorのendで実行されるのはcocos2d-xのヘルパークラス(android限定)にあるterminateProcessです。この処理はプロセスをkillすることになるためアプリ全体が終了されます。この処理だけを削除することでアプリ完全終了は回避できます。それによる影響をなくすためには各々クラスでのメモリ解放処理をしっかり行う必要があります。先読みの遅さ問題CCSpriteFrameCacheを利用する時に遅くなる問題です。iOSでは数十個を先読みしても1秒以内で済みましたが、Androidだと大体15秒から30秒が掛かります。これはandroidのio性能や全体処理速度に関係があります。従って全部読み込むことは避けた方がいいです。ここでは必要なものだけを読み込むように変更して対応しました。.}効果発動タイミングとかにはdelay処理かユーザインプット待ちの所がいるはずです。その隙間を上手く活用すれば全部先読みしなくても短時間ローディングでスムーズに描画できます。CCLabelTTFが化けて表示される現象です。この問題は課金や広告SDKとの連携でJNI経由を使うと高確率で起こります。v3)処理後JNI経由でバトルに復帰するタイミングで起こりました。対応する方法はJNI経由の復帰する処理をglスレッドにすることです。これによりJNI経由でもcocos2d-xオブジェクトが化けて表示されたりする現象はなくなります。Cocos2dxRenderer.nativeEndPurchase(result);}最後にこれで一通りandroidの一部機能をcocos2d-xで実装した時に遭遇する課題についてお話しました。気づいている方も多いと思いますが、この構成の最大の課題は紛れもなく実装コスト問題です。当然ですがJNIを用いることでプログラム構造が複雑になってしまいます。これは製造コストはもちろん維持補修に影響を及ぼします。実際マルウェアは対応されにくくするため極力複雑な構造を採用しますが、その一つの方法としてJNIを採用するケースが増えています。一つはcocos2d-xを拡張することになってしまったので、今後のバージョンアップにコストが発生します。個人の考えに過ぎませんがフレームワークやライブラリを独自で拡張するのは宜しくないことです。安定化されたとは言えバージョンアップが進んでいるcocos2d-xだとなおさらです。皆さんはどこを目指してエンジニアリングをしていますか?私はソフトウェアアーキテクトを目指しています。そのためにもなるべく多くの言語、フレームワーク、システムに関わるよう心がけています。その一番の理由は最大の学習に繋がるのが課題から得られた知識だからです。未熟な知識から生まれたノウハウですがどうか今回の記事が皆さんのエンジニアリング経験に繋がることを願います。