Swift だけで Unity の iOS の Native Plugin を作る


Swift だけで Unity の iOS の Native Plugin を作る

  • Unity は Native Plugin を作成することで、Unity から提供されていない、OS 固有の機能を使用することができます
  • 例えば iOS では、Bluetooth や MP4 動画の作成、アルバムの利用などが出来るようになります
  • これらの Native の機能を Swift で実装し、C# から呼び出す手順です
  • Swift Package Manager (以下 SwiftPM) を使って、Framework を作成し、Unity に組み込みます
  • ついでに、ライブラリの開発用の Native のアプリターゲットを作成します
  • Framework 化するメリット
    • PostProcess の設定が不要です
    • Unity の Inspector 上で Embed の設定が完了します
    • 合わせて Native でアプリターゲットを実装する事で、動作確認のために Unity のビルドをせずに、ライブラリの開発が可能です
  • 今回の話のリポジトリです↓

やりたい事

  • 今回は、Swift で文字列型の数値を long 型に変換する Native Plugin を実装します
  • [DllImport("__Internal")] を付けて定義された、関数 swiftPmPlugin_toNumber が Swift で実装された関数です
  • 今回は、C# にも存在する機能ですが、呼び出した先の Swift で iOS 固有の機能を利用することが可能です

https://github.com/fuziki/UnityPluginXcodeTemplate/blob/develop/Examples/UnityExample/Assets/Scripts/Cube.cs

public class Cube : MonoBehaviour
{
    // Swift で実装された関数の定義
    [DllImport("__Internal")]
    private static extern long swiftPmPlugin_toNumber(string numberString);

    void Update()
    {
        // 呼び出し
        Debug.Log("number is: " + swiftPmPlugin_toNumber("30"));
    }
}

Native Plugin の実装

  • Swift Package Manager (SwiftPM) を使って実装し Framework を出力します
  • 上記の swiftPmPlugin_toNumber が実装された SwiftPmPlugin.framework を作成します

環境

  • maxOS 11.3.1
  • Xcode 12.5
  • Swift 5.4.0
  • Unity 2020.3.5f1

Swift Package Manager

  • type に libraryを、name に 作りたいパッケージの名前 (今回は SwiftPmPlugin) を指定して、package を初期化します
  • library package が作成されるので、Package.swift を開きます
  • Xcode が起動するので、SwiftPmPlugin.swift に実装していきます
swift package init --type=library --name=SwiftPmPlugin
open Package.swift

SwiftPmPlugin の実装

  • Swift でロジックを実装します
  • SwiftPmPlugin クラスに、文字列を数値に変換する toNumber 関数を実装しました
  • Int(string) ?? 0 として数値変換に失敗したときは、0 を返すようにしました

https://github.com/fuziki/UnityPluginXcodeTemplate/blob/develop/Sources/SwiftPmPlugin/SwiftPmPlugin.swift

class SwiftPmPlugin {
    var text = "Hello, World!"
    static func toNumber(string: String) -> Int {
        return Int(string) ?? 0
    }
}

Swift で関数を公開する

  • Swift で公開する関数を作成します
  • @_cdecl を使って、C の関数として定義します
  • char 配列のポインタを受け取って、Swift の String 型に変換します
  • 先ほど作った SwiftPmPlugin.toNumber を使って、String から Int に変換します
  • C# で定義した関数が戻り値が long 型で 64bit 整数なので戻り値も、揃えます
@_cdecl("swiftPmPlugin_toNumber")
public func swiftPmPlugin_toNumber(_ stringPtr: UnsafePointer<CChar>?) -> Int64 {
    let str = String(cString: stringPtr!)
    return Int64(SwiftPmPlugin.toNumber(string: str))
}

Framework でビルドする

  1. SwiftPM から xcodeproj を出力して
  2. xcodebuild を使って出力した xcodeproj から framework を作成します
  3. CONFIGURATION_BUILD_DIR に指定したディレクトリに、SwiftPmPlugin.framework 作成されています
swift package generate-xcodeproj --skip-extra-files
xcodebuild -project SwiftPmPlugin.xcodeproj -scheme SwiftPmPlugin-Package -configuration Release -sdk iphoneos CONFIGURATION_BUILD_DIR=.
  • Framework の Headers を確認すると、先ほど実装した swiftPmPlugin_toNumber が公開されていることが分かります
open SwiftPmPlugin.framework/Headers/SwiftPmPlugin-Swift.h

Unity でビルドする

Framework の設定

  • Plugins/iOS/ を作成して、SwiftPmPlugin.framework を配置します
  • SwiftPmPlugin.framework の Inspector で、Add To Embedded Binaries を有効にします
    • Add To Embedded Binaries を有効にすることで、ビルドしたときに Framework が自動で Embed され ます

iOSアプリをビルドする

  • Unity で、通常通り iOS 向けにビルドします
  • PostProcess などは、設定していないです
  • 作成した target の frameworks に、SwiftPmPlugin.framework があり、
  • Embed の項目が、Embed & Sign になっていることを確認します
    • 自動で追加されていない場合は、Unity の設定に失敗している可能性があります
    • 手動で追加しても問題ないです
  • 確認できれば通常通り iOS アプリを実行できるはずです

Framework 開発用の Native のアプリターゲットを作る

  • Framework の開発が便利になる tips 的なやつです
  • Unity で使う Framework の動作確認のために、Framework のビルド→Unityのビルド→iOSのサイクルを回すのは大変です
  • そこで、SwiftPM で作ったライブラリを取り込んだ、アプリターゲットを作成し、開発すると効率的だと考えています

xcworkspace を作成する

  • xcworkspace を作成し、Package を取り込み、アプリターゲットのターゲットを用意します
    ※ アプリを Objective-C で作る必要はないです、Swift でも問題ないです

  • アプリターゲットの Frameworks に SwiftPmPlugin を追加します

  • コードから呼び出すことが可能なので、呼び出します
#import "ViewController.h"
@import SwiftPmPlugin;
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    NSLog(@"value: %lld", swiftPmPlugin_toNumber("20"));
}
@end
  • Swift からも呼び出すことが可能です
import SwiftUI
import SwiftPmPlugin

struct ContentView: View {
    var body: some View {
        Text("number: \(swiftPmPlugin_toNumber("20"))")
            .padding()
    }
}

おわりに

  • Native Plugin を実装することで、ゲームの開発は Unity に集中しつつ、OS 固有の機能を十分に利用することが可能です
  • Swift で実装し、SwiftPM で管理し、Native アプリで動作確認することで、効率的な開発を目指しています

関連記事です

  • この実装を拡張して、Unity の Texture をやりとりする事が可能です

  • SendMessage のやり方はこちら

  • この実装を拡張して、Unity から関数のポインタを受け取って、コールバックすることも可能です

  • Bundle target を作成することで Unity Editor と共通化させて実装することが可能です