ゲーム開発の備忘録

趣味のゲーム開発でのノウハウや、技術的に嵌ったポイントを忘れないように書き記しておくブログです。

libGDXでRoboVMからIntel MOEへの移植を行う エミュレータ上での動作確認編

RoboVMやIntel-MOEについてご存知でない方や、RoboVMのコード移植が完了していない方は、先にコーディング編からご確認ください。
deep-verdure.hatenablog.com

動作環境構築

RoboVMコードからIntel-MOEコードへの移植作業が完了したら、さっそくiOSエミュレータで動作確認を行います。
まずは、MacBookAndroid StudioMulti-OS Engine Pluginをインストールして有効にします。
通常のプラグインインストールと同様の作業なので、作業手順は割愛します。

因みに、私が動作確認した際は、Mac OS Xのバージョンは10.14.2で、Android Studio for Macのバージョンは3.2.1でした。
Multi-OS Engine Pluginのバージョンは1.4.3でした。

Multi-OS Engine Pluginが有効になると、Android Studioの実行設定にMulti-OS Engine用の設定が追加されます。しかし、libGDXのプロジェクトでは実行モジュールに不正なものが選択されているため、そのままでは実行することができません。
「Edit Configurations...」から設定編集画面を開き、LaunchタブのProject ⇒ Moduleで「ios-moe」を選択します。
すると、実行ボタン押下時にビルドが走るようになります。

絶望のjava.lang.NoClassDefFoundError

ビルドが成功し、いざ実行!と思いきや、アプリが立ち上がった後に強制終了してしまいます。
あれ?と思い、エラーログを確認すると、iOSMoeLauncherでjava.lang.NoClassDefFoundErrorが出ています。

ここからが絶望の始まりです。

実は、iOS-MOEはKotlinコードから生成した中間コードを解釈できないようです。
理由は不明ですが、libGDXのiOS-MOEの新規プロジェクトをJavaのまま実行すると上手く動作するのに対し、Kotlinに変換してから動作させるとiOSMoeLauncherが見つからなくなることを確認しています。
そのため、Kotlinがマズイことは確実でしょう。

(公式コミュニティでも質問が上がっていましたが、誰も回答してくれておらず埋もれていました)
discuss.multi-os-engine.org

という訳で、ios-moeモジュール配下のKotlinコードを全てJavaに書き直す必要があります。
もちろん、最初からJava一筋で書いていた場合は書き直し作業は発生しないので、飛ばして読んでください。

KotlinコードのJavaコードへの書き直し

KotlinコードをJavaコードへ直すには、対応する文法に手動で直していくしかありません。この作業自体に関してはKotlin in Action等の本を読みながら対応していくと良いでしょう。
本記事では基本的な書き換えについては割愛します。ただし、一つ引っかかるポイントがあるので、それを解説します。

Retrolambdaとの共存のための対応

Intel-MOEは内部でRetrolambdaを用いているため、Kotlinパッケージとの共存や、Java8以降の機能との共存ができません。
Retrolambdaとは、Java8やKotlinで利用可能なラムダ式をJava7以前でも使えるようにするためのバックポートツールです。詳しくは以下の記事を参考にしてください。

qiita.com

Retrolambdaを利用している場合、Kotlinパッケージや、Java8以降の機能とは共存できないため、それらを徹底的に排除する必要があります。

libGDXでCore側とのインターフェイスにあたる部分でラムダをやり取りしている場合、Intel-MOEから波及して全モジュールでRetrolambdaの影響を受けてしまいます。
全モジュールのKotlinラムダをRetrolambda化するのは避けたいので、このような場合にはios-moe側とCore側との間でやり取りするインターフェイスの部分だけ、ラムダや関数型インターフェイスではなく無名クラスを渡すようにすると良いでしょう。

また、ios-moe側ではStream APIも利用できないので、コレクションのイテレーション処理やリダクション処理で面倒な記述を強要されます。
ソート処理にはApache Commons CollectionsやApache Commons ComparatorChainを利用する等、Java7以前の時代の遺物を駆使して出来るだけ楽にしていきましょう。

バインディングライブラリのリンク

一部の(正確には、カテゴリが定義されている)バインディングライブラリを使用している場合、ビルドは成功するのに実行時に「unrecognized selector sent to instance」エラーが出力されることがあります。
これは、そのライブラリのUNIX Static Library実装と、iOS上で解釈されるObjective-Cの動的な性質の違いにより、シンボルのマッピングが上手くできずロードが失敗するためです。
このような場合は、Xcodeからios-moeのプロジェクトファイルを開き、Build SettingsタブのOther Linker Flagsに-ObjCオプションを設定しましょう。
注意点として、動作環境が64bitの場合はリンカにバグがあるため、-force_loadオプションも追加して、引数にバインディングライブラリを指定する必要があります。
詳しくは、以下のサイトが参考になります。

Cocoaの日々: [Mac][iOS] Static Library (7) カテゴリを使う場合の注意点 "-ObjC" と "-all_load"

なお、XcodeでOther Linker Flagsを設定する際、libGDXのプロジェクトでは、

$(inherited) $(MOE_OTHER_LDFLAGS) $(LIBGDX_NATIVES)

が最初から指定されています。
このうち、$(inherited)を残しておくと、実行時にいきなりARTおよびmainスレッド、Dalvikスレッドが異常終了する謎の不具合に悩まされることになります。
そのため、$(inherited)だけは削除して、-ObjCと-force_loadを指定しましょう。
$(MOE_OTHER_LDFLAGS) $(LIBGDX_NATIVES)も削除してしまうと、ビルド時に「Undefined symbols for architecture i386:」エラーが出力され、ビルドが失敗しますので、注意してください。

おわりに

お疲れ様でした。ここまでの作業をすべて実施していれば、iOSエミュレータ上でアプリケーションが動作するはずです!
Intel-MOEは資料が非常に少なく、エラーも分かりにくい強敵ですが、諦めずに格闘し続ければ道は開けますので頑張ってください!
次回は、実機上での動作確認等で特筆すべき事項があれば記事を書きたいと思います。