Help us understand the problem. What is going on with this article?

UnityのiOSネイティブプラグインをSwiftで書くためのネイティブプラグイン

More than 3 years have passed since last update.

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 HeaderSWIFT_OBJC_BRIDGING_HEADER)の設定で変更できます。

Objective-CからUnity側のメソッドを呼び出すUnitySendMessage関数やUnityシーンのルートビューコントローラーを取得するUnityGetGLViewController関数は、Unityプロジェクトをビルドして生成されたXcodeプロジェクトに含まれているUnityInterface.hで宣言されているので、これをBridging Headerでimportします。
また、UnityInterface.hでは、NSStringUIViewといったクラスを参照しているので、Foundation.hUIKit.hもimportしておきます。

UnitySwift-Bridging-Header.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "UnityInterface.h"

これだけでCの関数をSwiftから直接呼び出せるようになり、UnitySendMessage関数を使ってUnity側のメソッドを呼び出すことができます。

Example.swift
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のクラスを作成する

Example.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インターフェイスを作成する

Example.mm
#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 NameSWIFT_OBJC_INTERFACE_HEADER_NAME)の設定で変更できます。

ステップ 3: C#側のインターフェイスを作成する

ステップ 2でextern "C"で宣言したCインターフェイスは、C#側で[DllImport("__Internal")]属性を付けることでアクセス可能になります。

Example.cs
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 PathsLD_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 HeaderUnitySwift-Bridging-Header.hに設定し、あらかじめFoundation.hUIKit.hUnityInterface.hをimportします。
  • Objective-C Generated Interface Header Nameunityswift-Swift.hに設定します。
  • Runpath Search Paths@executable_path/Frameworksに設定します。

unity-swiftを使って実際にSwiftでネイティブプラグインを作成した例は、Unity ReplayKit Bridgeのswiftブランチを参照してください。

unity-replay-kit-bridge/Example/Assets/UnityReplayKitBridge at swift · miyabi/unity-replay-kit-bridge

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away