[Unity]Swift だけで作ったNative PluginでUnityにSendMessage

# Swift だけで作った Native Plugin で Unity に SendMessage
* [Swift だけで、Unity の iOS の Native Plugin を作る方法](https://qiita.com/fuziki/items/955c2b35514bcfc37969)を紹介しました
* Swift から Unity へコールバックしたい場合がよくあります
* [Unity の Native Plugin のドキュメント](https://docs.unity3d.com/ja/2018.4/Manual/PluginsForIOS.html)には、UnitySendMessage を使う方法が紹介されています
* UnitySendMessage を使うと、**とても容易に**コールバックを実装可能です
* ゲームオブジェクト名、スクリプト名、呼び出されたメソッドに渡す文字列を指定します

```
UnitySendMessage("GameObjectName1", "MethodName1", "Message to send");
```

* これを Swift だけで作った Native Plugin から呼び出す話です

# 今回のサンプル
* リポジトリはこちらです

https://github.com/fuziki/UnityPluginXcodeTemplate

* [Swift だけで、Unity の iOS の Native Plugin を作る](Swift だけで、Unity の iOS の Native Plugin を作る方法) の内容を前提知識にしています


# 処理の流れ
1. Unity で実行されるゲームオブジェクトとスクリプトを用意する  
   == ここまで C# ここから Swift ===
1. Swift で、UnityFramework を読み込む
1. UnityFramework を使って、UnitySendMessage を実行する

# Unity の実装
## 最終目標

* "Cube" オブジェクトの "Sent" 関数に任意の文字を渡して実行することを目標とします

```c#
public class Cube : MonoBehaviour
{
    public void Sent(string message)
    {
        Debug.Log("Sent message -> " + message);
    }
}
```
* これで UnitySendMessage からメッセージを受け取る事が可能です

# Swift の実装
## やること

* UnityFramework.framework には、UnityFramework クラスが実装されています
* UnityFramework のシングルトンのインスタンスを利用します
* sendMessageToGO 関数を呼ぶと UnitySendMessage が実行されます

```swift
let objectName = "Cube" as NSString
let scriptName = "Sent" as NSString
let message = "Hello" as NSString
UnityFramework.getInstance()
    .sendMessageToGO(withName: objectName.utf8String,
                     functionName: scriptName.utf8String,
                     message: message.utf8String)
```

## 実装計画
* Framework を事前にビルドする形だと、ビルド時に UnityFramework とリンクできません
* そのため、事前にビルドした Framework から名前を指定して実行します

## UnityFramework のシングルトンのインスタンスを取得する

* UnityFramework.framework の PrincipalClass として UnityFramework クラスが設定されているので利用します
* getInstance の値を名前を指定して取得します

```swift
let bundlePath = Bundle.main.bundlePath.appending("/Frameworks/UnityFramework.framework")
let bundle = Bundle(path: bundlePath)!
let principalClass = bundle.principalClass as! NSObject.Type
let unityFramework: NSObject = principalClass.value(forKey: "getInstance") as! NSObject
```

これで、`UnityFramework.getInstance()` に相当する処理を実装できました

## `sendMessageToGOWithName:functionName:message` を実行する

### やりたいこと
* 上で取得した unityFramework インスタンスで sendMessageToGOWithName の関数を実行したいです
* この関数を名前を指定して実行します

```swift
unityFramework.sendMessageToGO(withName: objectName.utf8String,
                               functionName: scriptName.utf8String,
                               message: message.utf8String)
```

### 文字列から関数を実行する

* `class_getMethodImplementation` で IMP を取得し、unsafeBitCast を使って、methodType にキャストして実行します
* sendMessageToGOWithName は引数として、`char*` を受け取るので、Swift で同等の `UnsafePointer<CChar>` として実行します

```swift
private func callInstanceMethod<T: NSObject>(targetInstance: T,
                                             selector: Selector,
                                             argCStr1: UnsafePointer<CChar>,
                                             argCStr2: UnsafePointer<CChar>,
                                             argCStr3: UnsafePointer<CChar>) {
    typealias methodType = @convention(c) (
        Any, Selector,
        // args
        UnsafePointer<CChar>, UnsafePointer<CChar>, UnsafePointer<CChar>
    ) -> Void
    let methodImplementation: IMP = class_getMethodImplementation(type(of: targetInstance), selector)!
    let methodInvocation = unsafeBitCast(methodImplementation,
                                         to: methodType.self)
    methodInvocation(targetInstance, selector, argCStr1, argCStr2, argCStr3)
}
```

### 実行

* sendMessageToGOWithName 関数のセレクタの sendMessageToGOWithNameSelector を作成します
* unityFramework のインスタンスで、sendMessageToGOWithNameSelector を実行します

```swift
let sendMessageToGOWithNameSelector: Selector = NSSelectorFromString("sendMessageToGOWithName:functionName:message:")
callInstanceMethod(targetInstance: unityFramework,
                   selector: sendMessageToGOWithNameSelector,
                   argCStr1: ("Cube" as NSString).utf8String!,
                   argCStr2: ("Sent" as NSString).utf8String!,
                   argCStr3: ("from send message" as NSString).utf8String!)
```

# Framework の出力と設定

* こちらの記事の `Framework でビルドする` の章以降と同じ手順で実行ビルド可能です

https://qiita.com/fuziki/items/955c2b35514bcfc37969#framework-%E3%81%A7%E3%83%93%E3%83%AB%E3%83%89%E3%81%99%E3%82%8B

* `callInstanceMethod` を実行したときに、`Cube.Sent` が指定した引数で実行されていることを確認します

# おわりに
* UnitySendMessage は Unity で複雑な実装をせずに、Swift から Unity へコールバックする事が可能です
* 1frame 遅れたり、文字列1つしか送れない、などが問題ない場合は十分に利用する事が可能だと思います
* Swift から Unity コールバックするのは、応用の幅が広いので、活用して行きたいです