ゲーム開発の備忘録

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

libGDXでSpriteの座標が(0, 0)から移動できない場合に確認すべきこと

新年一発目は超絶短い記事ですが、地味に嵌ったのでメモ。

libGDXでSpriteの座標の初期位置としてSprite#setPosition()に正方向の十分大きい数を渡し、画面内に描画させないようにすることを考えます。

この時、KotlinのFloat.MAX_VALUEを設定すると、なぜか座標が(0, 0)に固定され、以後一切動かせなくなります。Sprite#setPosition()で座標を設定し、画面内に描画させないようにする場合、99999fなどの値を渡して対応しましょう。

なお、上記の仕様(バグ?)も相まって、描画呼び出しの通過/非通過を切り替えたほうがコードが綺麗に見えます。

moe-gradle pluginのタスクを実行してIntel MOEプロジェクトのビルドや実行を行う

Intel MOEについては以下の記事を参照してください。

deep-verdure.hatenablog.com

また、Intel MOEのプロジェクトをGradle 5.1.1でビルドする方法については以下を参照してください。

deep-verdure.hatenablog.com

はじめに

Intel MOEのプロジェクトについてGradle 5.1.1以上でビルドする時などは、
IDEのMulti-OS Engine Pluginを未適用状態にするか、アンインストールする必要があります。

そうなると、Intel MOEプロジェクトにおけるビルドや実行は、
moe-gradle pluginで定義されるタスクを直接起動して実施する必要があります。

本記事では、moe-gradleのタスクを実行してビルドやアプリケーションの実行などの各種操作を行う方法を説明します。

Xcodeアップデート時の対応

Xcodeをバージョンアップした場合、他のタスクを実行するよりも前にmoeUpdateXcodeSettingsを実行する必要があります。

IPAの作成

IPAを作成するには、moeIpaBuildを実行します。これは今までも使用していたかと思いますが、念のため掲載しておきます。

シミュレータでのアプリケーションの実行

シミュレータで実行するには、まずmoeListSimulatorsを実行し、利用可能なシミュレータの一覧を取得します。
以下に実行例を簡略化して示します。
(UDIDは全てXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXに置き換えています)

./gradlew moeListSimulators

Available Simulators:
- XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - iOS 12.4 - iPhone 5s
- XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - iOS 12.4 - iPhone 6
- XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - iOS 12.4 - iPhone 6 Plus
- XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - iOS 12.4 - iPhone 6s
- XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - iOS 12.4 - iPhone 6s Plus
- XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - iOS 12.4 - iPhone 7
- XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - iOS 12.4 - iPhone 7 Plus
- XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - iOS 12.4 - iPhone 8
- XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - iOS 12.4 - iPhone 8 Plus
- XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - iOS 12.4 - iPhone SE
- XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - iOS 12.4 - iPhone X
- XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - iOS 12.4 - iPhone Xs
- XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - iOS 12.4 - iPhone Xs Max
- XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - iOS 12.4 - iPhone Xʀ
- XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - iOS 12.4 - iPad Air (3rd generation)
- XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - iOS 12.4 - iPad Air
- XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - iOS 12.4 - iPad Air 2
- XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - iOS 12.4 - iPad (5th generation)
- XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - iOS 12.4 - iPad Pro (9.7-inch)
- XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - iOS 12.4 - iPad Pro (12.9-inch)
- XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - iOS 12.4 - iPad Pro (12.9-inch) (2nd generation)
- XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - iOS 12.4 - iPad Pro (10.5-inch)
- XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - iOS 12.4 - iPad (6th generation)
- XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - iOS 12.4 - iPad Pro (11-inch)
- XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - iOS 12.4 - iPad Pro (12.9-inch) (3rd generation)

以上のように、シミュレータのUDIDが列挙されますので、利用したいシミュレータのUDIDを控えておきます。

次に、moeLaunchを実行してアプリケーションを実行させます。
moeLaunchのオプションとして -Pmoe.launcher.simulators=<シミュレータのUDID> を付加することで、シミュレータでアプリケーションを実行できます。

なお、シミュレータで実行する場合はデバッグ実行をすることはできませんのでご注意ください。

実機でのアプリケーションの実行

実機での実行時にも、まずは利用可能なデバイスのUDIDを取得します。
アプリケーションを実行させたい端末をMacに接続し、moeListDevicesを実行します。
moeListSimulators実行時と同様にUDIDが列挙されますので、利用したい端末のUDIDを控えておきます。

次に、moeLaunchでアプリケーションを実行します。
オプションとして -Pmoe.launcher.devices=<デバイスのUDID> を付加することで、実機でアプリケーションを実行できます。
デバッグ実行をさせたい場合は、さらに -Pmoe.launcher.options=debug:5005 を付加して実行してください。

なお、当然ですが、先にApple Developers Siteで、利用したい端末を端末登録しておく必要があります。

※-Pmoe.launcher.simulators、-Pmoe.launcher.devicesのどちらも付加しなかった場合は、利用可能な端末やシミュレータを検出して実行すると公式ドキュメントに記載されていますが、実際にはmoeLaunchが95%に達してから先に進まなくなります。必ず-Pmoe.launcher.simulators、-Pmoe.launcher.devicesのどちらかは付加しておきましょう。

公式ドキュメント

moe-gradleには、ここで紹介した以外にも様々なタスクやオプションが用意されています。
気になった方は、以下のmoe-gradleのGitHubリポジトリのReadme.mdをご確認ください。

github.com

Intel MOEのプロジェクトをGradle5.1.1でビルドする

Intel MOEについては以下の記事を参照してください。

deep-verdure.hatenablog.com

はじめに

Intel MOEをGradleでビルドする際は、build.gradleのbuildscriptのdependenciesで、moe-gradle pluginをimplementationしておく必要があります。
しかし、このmoe-gradle pluginは、2018年2月のver 1.4.4のリリースを最後に更新されなくなってしまいました。
その後、Gradleのバージョンが上がり、ver 5.1.1に達したところで、Intel MOEのプロジェクトはビルドできなくなってしまいました。

本記事では、Intel MOEのプロジェクトをGradle 5.1.1でもビルドできるようにする方法を紹介します。

コミュニティ版のmoe-gradle pluginの導入

moe-gradle pluginの公式リリースは止まってしまいましたが、Intel MOEはOSSであり、コミュニティの有志によってmoe-gradle pluginのアップデートが行われています。
コミュニティのユーザフォーラムは以下からアクセスできます。
discuss.multi-os-engine.org

Is Multi Os Engine alive?という悲痛なスレッドも建てられています……

2019年7月6日に、ver 1.4.5がコミュニティ版として公開されました。このバージョンを適用することで、Gradle 5.1.1でもIntel MOEのプロジェクトをビルドできるようになります。
ver 1.4.5の更新内容や適用方法が掲載されているスレッドは以下です。
discuss.multi-os-engine.org

なお、2019年1月19日にもver 1.4.4-Cがコミュニティ版のmoe-gradle pluginとしてリリースされています。ver 1.4.4-Cの更新内容もver 1.4.5に引き継がれているので、更新内容をチェックしておくと良いでしょう。
1.4.4-Cの更新内容が掲載されているスレッドは以下です。
discuss.multi-os-engine.org

コミュニティ版のプラグイン利用時の注意点

ver 1.4.4-C以降を利用する場合、必ずAndroid StudioのMulti-OS Engine Pluginを未適用状態にするか、アンインストールしてください。
Multi-OS Engine Pluginが適用されたままビルドしようとすると、既存プロジェクトを認識できなくなったり、ビルドがエラーメッセージ無しで失敗する等の現象が発生します。

Multi-OS Engine Pluginが利用できないため、ビルドはGradleのタスクをCLIで直接起動することになります。
Intel MOEのプロジェクトをCLIで操作する場合のIPAビルド、エミュレータや実機でのアプリケーション実行・デバッグの実施方法については、以下の記事を参照してください。

deep-verdure.hatenablog.com

余談

開発が終了し放置されていたRoboVMのGitHubリポジトリですが、1~2ヶ月前くらいからちょこちょこと更新が入っています。
compilerやpluginなど、メインの箇所に更新が入っているため、そのうち復活するのかもしれません。今のところrobovm-gradle-pluginの更新はありませんが、今後に期待です。

com.google.android.gms.drive.DriveのScope定数をDeprecatedでない代替手段に置き換える

はじめに

2019年6月に、Google Play Servicesの各種ライブラリがバージョンアップされ、それを契機としてGoogle Drive Android APIの各種クラスに@Deprecatedが付与されました。
もともと、Google Drive Android APIは2019年12月に廃止される告知が2018年12月になされていました。
告知開始から6ヵ月経過した今のタイミングで@Deprecatedがされたものと思われます。

developers.google.com

さて、本記事ではGoogle Drive Android APIに含まれるcom.google.android.gms.drive.DriveのScope定数をDeprecatedでない代替手段に置き換える方法を説明します。
com.google.android.gms.drive.DriveのScope定数は、Google Driveそのものを直接扱う場合以外にも、Google Play Games ServicesのSaved Games利用時のsignInOptions構築に必要になっていました。
以下に、Saved Games利用時のsignInOptions構築のコードを掲載します。(本記事のコードは、ライブラリのJavaコード以外は全てKotlinで記述しています)

val signInOptions = GoogleSignInOptions
                .Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN)
                .requestEmail()
                .requestIdToken(parent.getString(R.string.default_web_client_id))
                .requestScopes(Drive.SCOPE_APPFOLDER)
                .build()

5行目の requestScopes(Drive.SCOPE_APPFOLDER) がSaved Games利用時に必要なScope要求の記述です。
しかし、DriveクラスはDeprecatedになったため、別の手段で置き換える必要があります。

Scope定数の中身を確認する

ここで、Drive.SCOPE_APPFOLDERの定義を確認してみましょう。
すると、com.google.android.gms.drive.Driveで以下のように定義されていることが確認できます。

public static final Scope SCOPE_APPFOLDER = new Scope("https://www.googleapis.com/auth/drive.appdata");

Scopeクラスを、コンストラクタにURL文字列を渡して生成すればよいことが分かります。
ScopeクラスはGoogle Drive Android APIには含まれておらず、かつ、コンストラクタに渡されるURLはGoogle Drive関連の全てのAPIで利用されるURLであり、Google Drive Android API廃止後も継続して利用可能なURLであることが分かっているため、上記コードの右辺をそのまま利用すれば良さそうです。
従って、以下のように書くことができます。

val signInOptions = GoogleSignInOptions
                .Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN)
                .requestEmail()
                .requestIdToken(parent.getString(R.string.default_web_client_id))
                .requestScopes(Scope("https://www.googleapis.com/auth/drive.appdata"))
                .build()

Drive REST API Java wrappersを利用してURL文字列のハードコーディングを排除する

廃止対象のcom.google.android.gms.drive.Driveは利用しなくても良くなりましたが、先程のコードにはURL文字列がハードコーディングされているという問題点があります。将来的にこのURLが変更される可能性がありますし、それはGoogle Driveおよびライブラリ側の事情なので、URLが変更されても実際に問題が発生するまで気付くことができません。
そこで、本記事冒頭で紹介したGoogleの公式ドキュメントで、Google Drive Android APIの移行先として示されているDrive REST API Java wrappersを利用します。

まずは、Drive REST API Java wrappersを利用するために、build.gradleに以下を追記します。

implementation 'com.google.apis:google-api-services-drive:v3-rev110-1.23.0'

Drive REST API Java wrappersには、com.google.api.services.drive.DriveScopesというクラスが存在します。
このクラスには、Scopeとして指定するURL文字列定数が用意されています。
以下に、Drive REST API Java wrappersで定義されているDriveScopes.DRIVE_APPDATAの定義を示します。

public static final String DRIVE_APPDATA = "https://www.googleapis.com/auth/drive.appdata";

このURLは、先ほどcom.google.android.gms.drive.Driveの定義を確認して見つけたURL文字列と一致していますね。
従って、以下のように記述できます。以下が今回の修正の最終形です。

val signInOptions = GoogleSignInOptions
                .Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN)
                .requestEmail()
                .requestIdToken(parent.getString(R.string.default_web_client_id))
                .requestScopes(Scope(DriveScopes.DRIVE_APPDATA))
                .build()

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は資料が非常に少なく、エラーも分かりにくい強敵ですが、諦めずに格闘し続ければ道は開けますので頑張ってください!
次回は、実機上での動作確認等で特筆すべき事項があれば記事を書きたいと思います。

libGDXでRoboVMからIntel MOEへの移植を行う コーディング編

はじめに -libGDXでのiOSアプリケーションの作成について-

libGDXでiOSアプリケーションを動作させる場合、以下の2通りの選択肢があります。

・RoboVMでiosモジュール配下のiOSLauncherを動作させる。
Intel MOEでios-moeモジュール配下のiOSMoeLauncherを動作させる。

資料が多く取り組みやすいのは前者ですが、残念なことに、RoboVMの親会社であるXamarinが2016年4月5日にMicrosoftに買収されたことで、RoboVMの開発は終了してしまいました。
そして、RoboVMはiOS12をサポートしない旨の声明が公表され、RoboVMを利用していたiOSアプリケーションはRoboVM以外の動作環境への移植を余儀なくされました。

かの有名なIngressも、iOS版はlibGDXかつRoboVMを利用していたため、iOS12非サポートの告知がされ話題になりました。

「Ingress」はiOS 12をサポートしない。公式がようやく発表。Ingress Primeリリースまでの対応は? | TeraDas

その後、iOS12非サポートの告知が撤回されましたが、Intel MOEに移植して対応したのかどうかは不明です。
なお、Ingressの次世代版のIngress PrimeはUnityで作られているため、UnityがApple社に追随出来ている限りは問題なさそうですね。


さて、私もlibGDXかつRoboVM利用でiOSアプリケーションを作成していたため、RoboVMを捨ててIntel MOEへ移植する必要がありました。
しかし、libGDXでのIntel MOEの利用に関するマニュアルはlibGDXの公式サイトには存在せず、Intel MOE側のマニュアルも2016年で更新停止しているという有様でした。結局ひたすら試行錯誤するしかありませんでした。

Using LibGDX — Multi-OS Engine Documentation

そこで、今回はlibGDXのRoboVMのプロジェクトを、Intel MOEに移植する方法についてまとめることにしました。
とにかく情報が少ないので、もし間違いやより良い方法などあればご指摘いただけると助かります!

基本的なランチャーコードの移植

RoboVM関連のimport文の修正

まずは、iOSLauncher側のコードにあってiOSMoeLauncher側のコードにない部分をそのままコピペします。
iosモジュール側でiOSLauncher以外にクラスやインターフェイスを作成していた場合は、それも全てios-moeモジュール側にも同様に作成しましょう。

すると、コードがエラーで真っ赤っ赤になると思います。覚悟を決めましょう。
まずは、インポートするパッケージ名を以下のように変更します。
※なお、本記事のコードは全てKotlinで書かれています。

追記:Kotlinで書いていた場合、後々Javaに書き直すことになりますが、まずはRoboVM版のKotlinコードをIntel-MOE版にKotlinのまま書き直したほうが楽です。

(例)

//変更前
import org.robovm.apple.uikit.UIApplication
//変更後
import apple.uikit.UIApplication

org.robovm.の部分を削除すればOKです。
エラーを吐いているimport文を全て残らず修正していきましょう。

メソッド名の変更

次に簡単なのは、単にメソッド名を修正すればよいものです。
全てのパターンを本記事で網羅できているわけではありませんが、以下のような変更が殆どだと思いますので一気に直していきましょう。
下記のパターンにあてはまらない場合も、代替のメソッドがある場合が多いので公式ドキュメントをもとに探していきます。

パターン1:プロパティで取得できていた箇所を「プロパティ名()」 の形式に修正する
(例)

//変更前
saveData.name
//変更後
saveData.name()

Javaの場合は、Kotlinのプロパティの取得がgetterに当たりますので、getXXX()からXXX()の形式に修正することになりますね。

パターン2:イベントハンドラを渡すメソッドを名前が長いものに置き換える
(例)

//変更前
GKNotificationBanner.showBanner()
//変更後
GKNotificationBanner.showBannerWithTitleMessageCompletionHandler()

//変更前
localPlayer.generateIdentityVerificationSignature()
//変更後
localPlayer.generateIdentityVerificationSignatureWithCompletionHandler()

ソースコードが非常に横に長くなっていき、気持ち悪いですが我慢します。
他にもイベントハンドラを渡すメソッドに限らず、上記と同じように名前が長くなっただけの同機能のメソッドが用意されている場合がありますので、まずは置き換え可能なメソッドが無いかどうかを探してみましょう。

パターン3:プロパティをsetterで置き換える
(例)

//変更前
window.backgroundColor = UIColor.white()
//変更後
window.setBackgroundColor(UIColor.whiteColor())

//変更前
localPlayer.authenticateHandler = VoidBlock2{
            view_controller: UIViewController?, error: NSError? ->
  /* 実装は省略 */
}
//変更後
localPlayer.setAuthenticateHandler {
            view_controller: UIViewController?, error: NSError? ->
  /* 実装は省略 */
}
ジェネリッククラスインスタンスの共変指定

ジェネリッククラスのインスタンスを渡すようなメソッドでは、引数に共変指定が追加されている場合があります。
実引数へのout修飾を求められた場合はout修飾を行い、共変制約に引っかからないようにコードを調整しましょう。

(例)

//変更前
val deleteSaveData = NSArray(it.value.drop(1))
localPlayer.resolveConflictingSavedGames(deleteSaveData, null){
  savedGames, fetchError ->

       readData(savedGames, fetchError, syncData, conflictRetryCount + 1, completedFunc)
}
//変更後
val deleteSaveDataArray = it.value.drop(1) as NSArray<out GKSavedGame>
localPlayer.resolveConflictingSavedGamesWithDataCompletionHandler(deleteSaveDataArray, null){
  savedGames, fetchError ->

  readData(savedGames, fetchError, syncData, conflictRetryCount + 1, completedFunc)
}
配列取得コードの変更

RoboVMでは配列変数はByteArray等の配列型で取得できていましたが、Intel MOEではConstVoidPtr型等、ポインタの形式で値が降ってくる場合があります。
そのような場合はBuffer型を介して配列型を取得する必要があります。

(例)

//変更前
syncData.add(CloudSaveData(block.name, block.ordinal, data.bytes))
//変更後
val ptr = data.bytes()
val buffer = ptr as Buffer
val array = buffer.array() as ByteArray
syncData.add(CloudSaveData(block.name, block.ordinal, array))
NSMutableArrayが必要なコードの対応

最も面倒くさい修正パターンです。RoboVMでMutableList型が必要だった箇所は、全てNSMutableArray型に変換しなければなりません。
しかし、NSMutableArray型は初期化時( NSMutableArray.alloc().init() )にスター投影されて返却されるため、そのままだと要素の追加が一切できません。
そこで、NSMutableArray<String>等にキャストする必要がありますが、スター投影された型からのキャストはUnchecked Cast警告が発出されるため、Suppressアノテーションで警告を抑制する必要があります。
Javaの場合は非境界ワイルドカード形式で返却されます。

(例)

//変更前
val myDeviceID = "AAA"
request.testDevices = Arrays.asList(myDeviceID)
//変更後
@Suppress("UNCHECKED_CAST")
val devices = NSMutableArray.alloc().init() as NSMutableArray<String>
val myDeviceID = "AAA"
devices.add(myDeviceID)
request.setTestDevices(devices)

特殊なコードの移植

ある特定機能を利用している場合に必要になる修正作業をまとめます。

Intel MOEのNSDate型を利用してComparableが要求される比較処理を行う

RoboVMのNSDate型はDate型に変換できますが、Intel MOEのNSDate型はDate型に変換できません。
その場合、Comparableを実装していないかつ、Dateに変換することができないため、Comparableが要求される比較処理で値を利用する際に一工夫が必要になります。

(例)

//変更前
savedGames.sortByDescending { it.modificationDate.toDate() }
//変更後
class ComparableNSDate(val date :NSDate) : Comparable<ComparableNSDate>{
  override fun compareTo(other: ComparableNSDate): Int = date.compare(other.date).toInt()
}

savedGames.sortByDescending {
        ComparableNSDate(it.modificationDate())
 }

Comparableを実装した、NSDate型のメンバを保有するローカルクラスを定義し、そのインスタンスを使います。

Robopodに代わるバインディングライブラリ

RoboVMには、JVM言語で記述されたサードパーティライブラリをバインディングして使えるようにしたRobopodというバインディングライブラリがありました。
Intel MOEでは、以下の場所にあるmoe-bindingsを利用します。

github.com

Google Mobile Ads等、一部のバインディングライブラリはビルド時に名前の競合でエラーを吐くことがあるので、バインディングライブラリ内のメソッドの手動リネームが必要な場合があります。注意しましょう。
残念ながら、GDPR対応で必要なConsent SDKバインディングしてくれていないようです……

おわりに

お疲れ様でした。次は、実際に移植したIntel MOE利用アプリケーションをエミュレータ上で動作確認してみましょう。

deep-verdure.hatenablog.com

Kotlinで書いたlibGDX利用プロジェクトをAndroid Studio 3.2でビルド時に"Cannot change attributes of configuration ':[app名]:kapt' after it has been resolved."が出力される場合の対処法

経緯

Android Studio 3.2にアップデートすると、Gradle 4.6未満のプロジェクトは扱えなくなります。私はlibGDXを利用してAndroidアプリケーションをKotlinで書いていたのですが、gdx-setup.jarによるlibGDXプロジェクトセットアップ時のデフォルトのGradleは4.4だったため、そのままではビルドできなくなりました。
そこで、Gradleを4.4から4.10にバージョンアップしたのですが、ビルド時に

"Cannot change attributes of configuration ':[app名]:kapt' after it has been resolved."

というエラーが出力され、ビルドができなくなってしまいました。
このエラーはbuild.gradleのapply plugin: "kotlin-android"を除去すると発生しなくなり、ビルドが成功しますが、そうすると実行時にActivityを有効化できない旨のエラーが吐かれます。Kotlinで書いている以上kotlin-androidは必須です。ではどうすれば良いのでしょうか?

解決策

com.android.tools.build:gradleのバージョンを3.1.3にします。
Gradle 4.6以上にアップデートする時に自動的に3.2.0がインストールされるのですが、3.2.0のままだと当該エラーが発生するようです。(エラーが発生する理由までは分かっていません……)
手動でbuild.gradleを編集し、com.android.tools.build:gradleのバージョンを3.1.3に書き換えましょう。
なお、gradle.propertiesに追加されたorg.gradle.configureondemandはcom.android.tools.build:gradleが3.2.0以上でないと利用できないため、falseを設定しておきましょう。trueのままだとビルドできません。
(※org.gradle.configureondemandはビルドを高速化するための仕組みのようなので、それが使えないのは残念ですが仕方ありません……)

ネット上に全く情報が無かったので、この結論にたどり着くまでにかなり時間がかかりました……

真の解決策(2018/11/14更新)

libGDX ver1.9.8のGitHubリポジトリから、androidプロジェクトのbuild.gradleを参照し、35行目以下を全てコピーして現在利用しているbuild.gradleに適切に上書き(ペースト)します。
すると、Gradle周りの問題はすべて解決します。