ネイティブプラグインを作るのに四苦八苦したので、ポイントをまとめてメモしておこうと思う。
ネイティブプラグインの作成について
http://www.slideshare.net/tatsuhikoyamamura/unity-15594873
#A131: モバイル向けNative Pluginの開発作法
http://vimeopro.com/unity3djp/unity-asia-bootcamp-tour-tokyo-for-the-public/video/41906327
ネイティブ拡張の実際
http://pukapuka-ahirusan.blogspot.jp/2012/08/unity_31.html
[Unity][Unity3d]iPhone/iPadのプラグインを作成する方法(改訂版)Add Star
http://d.hatena.ne.jp/nakamura001/20120504/1336095282
UnityでAndroid JARファイルを呼び出す最も簡単な方法
http://tech.basicinc.jp/Unity/2013/04/14/unity_andoird_jar_plugin/
■機能の実装で最低限考えること
ネイティブプラグインとして実装することも重要だが、その前に本当にその機能がネイティブプラグイン上で実装できるか確認する必要がある。ネイティブで実現出来ない機能はネイティブプラグインを使っても実現不可能なので、その辺りはよく確認しておこう。
後はAndroidのUI関連の処理は全てUIThreadで動かすことを徹底すれば、概ね動くと思う。
なお、Unityが使っていそうなハードウェア・機能は干渉することがある。例えば音だったりサーフェイスビューだったり。
■ネイティブプラグインの開発しやすく
ネイティブプラグインを開発する上で、「Androidは毎回jarを作らなきゃいけないから面倒!」とか「iOSのプラグイン作る際、入力補完が効かないし、これが最新の開発手法かーと思った」と稀に聞く。
これについては資料が無いので勘違いされがちだが、実は「Plugins/iOS」や「Plugins/Android」に入れないと実行出来ないといった事はない。
ビルドすれば判る通り、UnityのネイティブプラグインはiOSやAndroidのネイティブな機能を呼び出す物であり、結局最終的にネイティブとして組み込まれてさえいれば呼び出すことが出来るらしい。
つまりUnityはeclipseやxcodeプロジェクトを作った後にソースコードを追加しても良いということだ。
(※ランタイムという意味ではありません)
具体的な手順はこんな感じとなる。
ちなみに3の手順で最後まで行っても問題ない。(オートで組み込んでくれないだけ)---iOS---
1.ネイティブプラグインを呼び出す部分だけ書いてiOSビルド
2.ネイティブプラグインのソースをプロジェクトに参照で配置
3.開発(Unityの更新は基本的にAppendする。リプレースした場合は2の手順から再開)
4.機能確定後、2の手順で配置したソースを削除し、参照元をPlugins/iOSフォルダへ移動
- Android---
1.ネイティブプラグインを呼び出す部分だけ書いてAndroidビルド
2.ネイティブプラグインのソースをEclipseのsrc以下に作成。
3.開発(Unityの更新は基本的にAppendする。リプレースした場合は2の手順から再開)
4.機能確定後、ネイティブプラグインのソースをjarにパックし、Plugins/Androidフォルダへ移動
この手順の場合、ソースコードの入力補完を使えるし、毎回別の場所で作ってビルドするといった手間も必要ない。コードの変更にもそれなりに柔軟に対応できる。
この辺りの手順を自動化してくれるエディタ拡張はよ!!
■ネイティブプラグインとUnityの連携について
連携の考え方的な話。
UnitySendMessageと構造体取得のサンプル
https://github.com/tsubaki/pluginsSample
Unity→ネイティブプラグインの場合は、Unityからネイティブプラグインを呼べば良い。ただし逆となると地味に面倒な話になってくる。
まず候補として上がるのがUnitySendMessageだが、これで渡せる引数はstringsのみであり、他のパラメータを渡すことは出来ない。
ちなみにUnitySendMessageの引数はこんな感じ。
iOS
void UnitySendMessage(
const char* オブジェクト名, const char* メソッド名, const char* 引数);
Android
void UnityPlayer.UnitySendMessage(
string オブジェクト名, string メソッド名, string 引数);
これをUnityで再現するなら、こんな処理になる。(機能的には「単一のオブジェクトをシーン内から探す」なので、階層とかは余り気にしないらしい。同名が複数あった場合何処に連絡がいくか分からないので注意されたし。要・ユニークなオブジェクト名)GameObject.Find(オブジェクト名).SendMessage(メソッド名, 引数) ;
何が問題かと言えば、stringしか渡す事ができないので、構造体やクラスを渡したい場合は渡す際に一度json等にシリアライズし取得後にデシリアライズする処理時間が必要となる。
逆に、戻り値のあるget*****的なメソッドを用意しておき、UnitySendMessageの通知を受け取った後にget****で中身を受け取るといった手もある。こちらは一時的にデータを(Hashテーブル等に)保持する処理が必要となる。
どちらが良いかは、後々決着を付ける必要がある。
ちなみにUnitySendMessageは到着に必ず1フレーム必要となる。(音やデシリアライズで実際はもっと遅く感じるかもしれない)。そのため、ネイティブプラグインを無駄に多用しているアプリは、レスポンスが悪いアプリになりやすい気がする。
なので、特に音みたいなレスポンスが必要な部分はネイティブ側で実装してしまうのも手かもしれない。
(なおAndroid端末は「ファイル転送」と「音のレーテンシー」が殆どの端末で非常に悪いので、ネイティブで実装した場合も非常に遅い。先読みでがんばろう)
なお、AndroidのオブジェクトとUnityのオブジェクトを同期することは出来ないので、一旦Androidのオブジェクトを取得し、Unity側のオブジェクトに流しこむ必要がある。
Result r;
AndroidJavaObject obj = testclass.CallStatic<AndroidJavaObject>("GetResult", key);
r.msg = obj.Get <string>("msg");;
r.x = obj.Get <int>("x");
r.y = obj.Get <int>("y");
return r;
個人的には、こういったコールバックを最低限にするのが最適解な気もする。
追記(2014/03/26)
もう一つ、Unity4より「デリゲートを登録」出来るようになっていたらしい。状況にも寄るが、SendMessageより此方のほうがスマートかもしれない。ただしC(C++ Objective-C)専用っぽい。
[Unity]NativePluginsにC#デリゲートを登録する
■ネイティブコードからUnityを起動する
じゃあUnity側をネイティブで管理しよう…といった発想が出るのかもしれないが、曰く、絶対にお勧めできない手法らしい。
これをするとマルチプラットフォームの意味がなくなる上、バージョンアップで手順が不可になる可能性が高く、さらにその手順でUnityを起動する場合、動作を一切保証できないとの事。また既に再起動時に挙動がおかしかったり、サスペンドから復帰できなかったり唐突に落ちたりと不具合も満載の上、解決法も見つかっていなかったりと地雷原ってレベルじゃない。
ようするに、茨の道みたい。
(でも定期的にチャレンジしてる人がいる所を見ると、何か需要があるのかな)
■作るのが面倒な場合
ネイティブプラグインはJava・iOS・Unityの知識が必要なので、
面倒な場合は、こういったサービスに作ってもらうのも手かもしれない。
angework
http://angework.com/development.html
もしかしたら続編に続く。