UnityのiOSネイティブプラグインはObjective-Cで記述して、Objective-CからUnity(C#)側のメソッド呼び出しはUnitySendMessage
関数、UnityからObjective-C側のメソッド呼び出しはextern "C"
で宣言されたCインターフェイスを通しておこなうのがセオリーとなっています。
近頃Swiftネイティブの開発者も増えてきたということもあり、タイトルの通り「UnityのiOSネイティブプラグインをSwiftで書くためのネイティブプラグイン」を作ってみました。
SwiftからのUnity側のメソッドを呼び出す
SwiftからCの関数やObjective-Cのクラスにアクセスするには、それらが宣言されているヘッダーファイルを、特定のヘッダーファイル(Bridging Header)でimportしておきます。
Bridging Headerは、Xcodeで新規プロジェクトを作成する際にLanguageにSwiftを選択したプロジェクトでObjective-Cのファイルを作成するとき、あるいは逆にObjective-CのプロジェクトでSwiftのファイルを作成するときに、自動的にこれを作成するかどうかを聞かれます。このときのデフォルトのファイル名は<Product Name>-Swift-Bridge.h
ですが、これはBuild Settings/Swift Compiler - Code GenerationのObjective-C Bridging Header
(SWIFT_OBJC_BRIDGING_HEADER
)の設定で変更できます。
Objective-CからUnity側のメソッドを呼び出すUnitySendMessage
関数やUnityシーンのルートビューコントローラーを取得するUnityGetGLViewController
関数は、Unityプロジェクトをビルドして生成されたXcodeプロジェクトに含まれているUnityInterface.h
で宣言されているので、これをBridging Headerでimportします。
また、UnityInterface.h
では、NSString
やUIView
といったクラスを参照しているので、Foundation.h
やUIKit.h
もimportしておきます。
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "UnityInterface.h"
これだけでCの関数をSwiftから直接呼び出せるようになり、UnitySendMessage
関数を使ってUnity側のメソッドを呼び出すことができます。
import Foundation
class Example : NSObject {
static func callUnityMethod() {
// Call a method on a specified GameObject.
UnitySendMessage("CallbackTarget", "OnCallFromSwift", "Hello, Unity!")
}
}
UnityからSwift側のメソッドを呼び出す
UnityからSwiftのクラスにアクセスする方法は、従来のUnityからObjective-Cのクラスにアクセスする方法とほぼ同様です。
ステップ 1: Swiftのクラスを作成する
import Foundation
class Example : NSObject {
static func swiftMethod(_ message: String) {
print("\(#function) is called with message: \(message)")
}
}
ステップ 2: .mm(Objective-C++)ファイルを作成して、extern "C"宣言でCインターフェイスを作成する
#import <Foundation/Foundation.h>
#import "unityswift-Swift.h" // Required
extern "C" {
void _ex_callSwiftMethod(const char *message) {
// You can access Swift classes directly here.
[Example swiftMethod:[NSString stringWithUTF8String:message]];
}
}
このとき、<Product Module Name>-Swift.h
という名前のヘッダーファイル(上の例ではunityswift-Swift.h
)をimportすることで、SwiftのクラスにObjective-C++からアクセスできます。
このヘッダーファイルは、Xcodeでビルドを実行するとDerivedDataの下に自動的に生成されます。また、ファイル名はBuild Settings/Swift Compiler - Code Generationの Objective-C Generated Interface Header Name
(SWIFT_OBJC_INTERFACE_HEADER_NAME
)の設定で変更できます。
ステップ 3: C#側のインターフェイスを作成する
ステップ 2でextern "C"
で宣言したCインターフェイスは、C#側で[DllImport("__Internal")]
属性を付けることでアクセス可能になります。
using System.Runtime.InteropServices;
public class Example {
#if UNITY_IOS && !UNITY_EDITOR
[DllImport("__Internal")]
private static extern void _ex_callSwiftMethod(string message);
#endif
public static void CallSwiftMethod(string message) {
#if UNITY_IOS && !UNITY_EDITOR
_ex_callSwiftMethod(message);
#endif
}
}
これで、C#のクラスから以下のような記述でSwift側のメソッドを呼び出せます。
Example.CallSwiftMethod("Hello, Swift!");
従来のObjective-Cで記述したネイティブプラグインでは、ステップ 1のクラス定義とステップ 2のインターフェイス宣言を1つの.mmファイルに記述していました。
インターフェイス宣言はそのままに、クラス定義部分をSwiftに移植することで、従来と同様にC#からアクセスできます。
Runpath Search Pathsの設定
ここまででSwift-C#間で相互にメソッドの呼び出しをおこなえるようになりましたが、実行時に次のようなエラーが発生します。
dyld: Library not loaded: @rpath/libswiftCore.dylib
これは、Unityプロジェクトをビルドして生成されたXcodeプロジェクトではBuild Settings/LinkingのRunpath Search Paths
(LD_RUNPATH_SEARCH_PATHS
)が設定されていないためで、@executable_path/Frameworks
に設定することで回避できます。
ネイティブプラグイン化
Bridging HeaderやObjective-C Generated Interface Header Nameは、デフォルトではProduct Nameに依存するためプロジェクトによって毎回変わってしまうし、毎回Runpath Search Paths
を手動で設定するのも面倒なので、これをネイティブプラグイン化しました。
miyabi/unity-swift: Native plugin to write native code in Swift for Unity.
このプラグインでは、以下の設定をUnityプロジェクトのビルド時に自動的におこないます。
-
Objective-C Bridging Header
をUnitySwift-Bridging-Header.h
に設定し、あらかじめFoundation.h
、UIKit.h
、UnityInterface.h
をimportします。 -
Objective-C Generated Interface Header Name
をunityswift-Swift.h
に設定します。 -
Runpath Search Paths
を@executable_path/Frameworks
に設定します。
unity-swiftを使って実際にSwiftでネイティブプラグインを作成した例は、Unity ReplayKit Bridgeのswiftブランチを参照してください。