強火で進め

このブログではプログラム関連の記事を中心に書いてます。

iPhone/iPadのプラグインを作成する方法


(2012/05/04 追記)
改訂版を書きました。こちらは Plugin/iOS フォルダを使って自動的にXcodeプロジェクトにファイルに含める方法を使った説明に改良してあります。

iPhone/iPadプラグインを作成する方法(改訂版) - 強火で進め
http://d.hatena.ne.jp/nakamura001/20120504/1336095282

(追記ここまで)
UnityからXcodeで記述した機能が呼べるiPhone/iPadプラグインを作成する方法を解説。今回のサンプルはこちらからDL出来ます。

プラグインを作ればネイティブコードで出来る事はなんでも出来ちゃいます。プラグインiOS Proライセンスはもちろん、iOSライセンスでも作成可能です。

手順は以下の様になります。
1. ビルドしてXcodeのプロジェクトを作成。
2. Xcode側のプログラムを作成する為にファイルを生成。プログラムを記述する。
3. Unity側でXcode側のプログラムとUnity側のプログラムを橋渡しするプログラムを作成。

順番に詳しく解説します。 1. は問題無いと思うので 2. から

Xcode側のプログラムを作成する為にファイルを生成

今回はiPhone/iPadで設定されている言語を取得するプログラムを例に解説します。
※言語はプラグインを作らなくても Application.systemLanguage で取得出来そうな気がしますが実はiPhone/iPadでは Unknown が返され取得出来ません(3.5から出来る様になりました)。

Xcode上で LanguagePlugin.h/LanguagePlugin.m を作成し、.mファイルに以下の様に記述します。

char* MakeStringCopy (const char* string)
{
	if (string == NULL)
		return NULL;
	
	char* res = (char*)malloc(strlen(string) + 1);
	strcpy(res, string);
	return res;
}

char *CurrentLanguage_ () {
	NSArray *languages = [NSLocale preferredLanguages];
	NSString *currentLanguage = [languages objectAtIndex:0];
	return MakeStringCopy([currentLanguage UTF8String]);
}

CurrentLanguage_ () がメインの関数で MakeStringCopy() で文字列を加工しているのは以下を詳しく読んで貰うと分かりますが

Unity - Plugins - Pro/Mobile-Only Feature
http://unity3d.com/support/documentation/Manual/Plugins.html

文字列についてはこの様なルールが有るからです。

String values returned from a native method should be UTF-8 encoded and allocated on the heap. Mono marshaling calls are free for them.

コレに対処する為の処理は自分で作成しても良かったのですが公式で配布しているこちらのサンプルに記述が有ったのでそのまま流用しています。それが MakeStringCopy () となります。

このXcode側のプログラムのファイルは置く場所によってはUnityでビルドしてXcodeプロジェクトが再生成された時に(場合によっては)上書きされてしまうので以下のいずれかの位置に置くのが推奨されています。
・Unity側のプロジェクトの Assets フォルダ
Xcode側のプロジェクトの Classes フォルダ

今回は Assets フォルダに NativeCode というフォルダを作成し、そこにファイルを移動した後に再度、Xcodeのプロジェクトに追加しました。

Unity側でXcode側のプログラムとUnity側のプログラムを橋渡しするプログラムを作成

Unityに戻ってプログラムの橋渡しをするプログラムを作成します。

Plugins というフォルダを作成し、そこにC#のファイルを Binding という名前で作成します。

プログラムは以下。

using UnityEngine;
using System.Runtime.InteropServices;

public class Binding {
    [DllImport("__Internal")]
    private static extern string CurrentLanguage_ ();
 
    public static string CurrentLanguage () {
        if (Application.platform != RuntimePlatform.OSXEditor) {
            return CurrentLanguage_ ();
        } else {
        	return "";
        }
    }
}

この CurrentLanguage_ () がXcode側で作成した関数になります。

UnityのIDE上で実行した時にはこの関数を呼ぶことは出来ないので Application.platform でチェックをし、IDE上で実行中の場合は "" を返しています。

実際にこの CurrentLanguage() を呼ぶ場合はこの様にします。

Debug.Log( Binding.CurrentLanguage() );

今回は画面の左上に取得した言語を表示される様に、こんなプログラムを作成しました。

private var lang: String;

function Start () {
	lang = Binding.CurrentLanguage();
}

function OnGUI () {
	GUI.Label(Rect(20, 20, 100, 100), lang);
}

これを実行すると以下の様に画面の左上に小さく(※)現在の言語設定が表示されます。日本語に設定されている場合は ja と表示されます。
※UnityでiOS向けビルドで動的に大きい文字を使うのはちょっと手間なのでこうなってます。

iPhone/iPadの言語設定については詳しくは以前書いたこちらを参照下さい。

補足情報

もし、Xcode側のプログラムをC++や.mmファイルで作成する場合はCから呼べるC++のプログラムを作成する時と同様に以下の様に extern "C" (リンケージ指定)が必要です。

extern "C" {
  float FooPluginFunction ();
} 

作成中になんだか上手く動かない場合には自分は以下の様なアラートを表示してデバッグしました。

	UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:@"テスト"
												   delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
	[alert show];
	[alert release];

関連情報

構造体を引数にする場合。

Specific steps to set up a plugin for iphone in XCode - Unity Answers
http://answers.unity3d.com/questions/10110/specific-steps-to-set-up-a-plugin-for-iphone-in-xc.html

Byte配列を引数にする場合。

Returning a byte array from ObjC to C# script on ios - Unity Answers
http://answers.unity3d.com/questions/132083/returning-a-byte-array-from-objc-to-c-script-on-io.html