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 のビルドをせずに、ライブラリの開発が可能です
* 今回の話のリポジトリです↓

https://github.com/fuziki/UnityPluginXcodeTemplate

# やりたい事

* 今回は、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

```c#
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
```

![Select_SwiftPmPlugin_swift.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214313/c1f5a36c-a2ff-6d34-c960-dea5771498ce.png)

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

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

```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 整数なので戻り値も、揃えます

```swift
@_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
```
![header.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214313/5f41c761-2b19-e7f9-f6a1-00a64659d0c5.png)

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

![inspector.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214313/13584663-32ae-b1d1-720c-0bdbbb9e0b3e.png)

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

![embed_framework.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214313/e37b35b9-df92-0e37-2c92-c66a7ca5757d.png)

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

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

![xcworkspace.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214313/29e29df2-ad3a-a5d0-a408-3c467f022b82.png)

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

![embed_native.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214313/39ed3432-42ab-60ad-6d31-406fe58fd7a2.png)

* コードから呼び出すことが可能なので、呼び出します

```objective_c
#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 からも呼び出すことが可能です

```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 をやりとりする事が可能です

https://qiita.com/fuziki/items/8ab522516a19e2323811

* SendMessage のやり方はこちら

https://qiita.com/fuziki/items/4980f1aa8be785cc0378

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

https://qiita.com/fuziki/items/b51353e9fe8f59640cc3

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

https://qiita.com/fuziki/items/974f70b663ebfadfb136