UnityのスマホネイティブプラグインをKotlin/Nativeで共通化する

# Unityのスマホネイティブプラグインを言語統一できないか？

昨今のスマホネイティブプラグインの言語の選択肢としてはJava, Kotlin, Objective-c, Swiftがあげられるかと思います。
たまに自分も趣味などでプラグインを使うことがあるのですが同じような処理を別で書かないといけないのが手間だと思っていました。
そこで昨年くらいからスマホのネイティブ界隈で話題になっていたKotlin/Nativeに目をつけてUnityで実行させてみたという記事です。

そもそもKotlin/Nativeとは…？
まとめている記事もありましたので参照させて頂きます。

- [公式リポジトリ](https://github.com/JetBrains/kotlin-native)
- [Kotlin/Native を Android/iOS アプリ開発に導入しよう](https://qiita.com/irgaly/items/3e6ee5bc3f59302bb76a)

基本的にはスマホネイティブの共通化できるロジックをKotlinで書いて共通化させようぜ！ってことなのですがAndroidでは.jarとしても吐き出せますし、iOSは.frameworkとして吐き出せるのでネイティブにとって扱いやすいものになっています。

さて、今回検証に使用したリポジトリです。動かして見たい人は是非ご活用ください。

- [AndroidProject -KotlinNativeSampleForUnity](https://github.com/MizoTake/KotlinNativeSampleForUnity)
- [UnityProject -KotlinNativeSample](https://github.com/MizoTake/KotlinNativeSample)

実行した結果

|Android|iOS|
|---|---|
|![Screenshot_20190502-040341.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127761/e6884e05-c701-729d-1edc-845ba6a8b78f.jpeg)|![Simulator Screen Shot - iPhone Xʀ - 2019-05-02 at 10.31.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127761/05acbdb2-e090-2168-a201-2107b6108534.png)|

文字列をOS毎に変えるという処理ですが呼び出すメソッドは１つにしてあります。

## 準備
Kotlin/Native自体の作成方法は既にわかりやすい記事がありますのでそちらを参照させていただきます。
[Kotlin/Nativeチュートリアル Android, iOS編](https://aakira.app/blog/2018/10/kotlin-native/)
自分はここを参考にさせて頂きました。そのため今回上記の記事をベースに進めます。

Androidのネイティブが自分はよくわからなかったので、最初は基本的にコピペで作って必要な箇所を変えていきました。

今回、上記記事の「Common moduleの解説」の章まで出来たら一旦は大丈夫です。
上記の記事ほぼそのままですがソースコードを載せておきます。

### 共通
```kotlin:common.kt

package com.sample.mizotake.kotlinnativeforunity

expect fun platformName(): String

public class common {
    public fun createApplicationScreenMessage(): String {
        return "Call Kotlin Native on ${platformName()}"
    }
}
```

### Android
```kotlin:actual.kt

package com.sample.mizotake.kotlinnativeforunity

actual fun platformName(): String {
    return "Android"
}
```

### iOS
```kotlin:actual.kt

package com.sample.mizotake.kotlinnativeforunity

import platform.UIKit.UIDevice

actual fun platformName(): String {
    return UIDevice.currentDevice.systemName() +
            " " +
            UIDevice.currentDevice.systemVersion
}
```

共通処理にUnityで呼び出すクラスとメソッドを定義します。OS毎に変える処理はexpect actual処理でinterfaceのように切り出して呼べるようです。

## Unityへの導入
### Android

自分もよく把握できていませんがGradle Syncをするとbuildというディレクトリができて
<img width="330" alt="スクリーンショット 2019-05-02 11.06.40.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127761/50dbb6cc-f05a-c26e-4eb6-8d7963dcd1d8.png">

`プロジェクト名 + android.jar`ができていました。
もし出来ていない場合は
<img width="387" alt="スクリーンショット 2019-05-02 11.04.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127761/cb4f8c59-4868-2c17-efd3-af31db38127e.png">
右端にGradleというタブがあるのでそこからbuildの項目を見るとbuildの詳細一覧があるのでandroidJarをダブルクリックすれば走り出してjarができるかと思います。

吐き出されたjarをUnityのPlugin/Androidに放り入れるだけです。
これでUnityへの導入は完了です。

### iOS
こちらは先ほどの[Kotlin/Nativeチュートリアル Android, iOS編](https://aakira.app/blog/2018/10/kotlin-native/)の「iOSアプリ」の章にあるbuild.gradleの追記だけ行いましょう。

```gradle:/common/build.gradle
...

task packForXCode(type: Sync) {
    final File frameworkDir = new File(buildDir, "xcode-frameworks")
    final String mode = project.findProperty("XCODE_CONFIGURATION")?.toUpperCase() ?: 'DEBUG'

    inputs.property "mode", mode
    dependsOn kotlin.targets.iOS.compilations.main.linkTaskName("FRAMEWORK", mode)

    from { kotlin.targets.iOS.compilations.main.getBinary("FRAMEWORK", mode).parentFile }
    into frameworkDir

    doLast {
        new File(frameworkDir, 'gradlew').with {
            text = "#!/bin/bash\nexport 'JAVA_HOME=${System.getProperty("java.home")}'\ncd '${rootProject.rootDir}'\n./gradlew \$@\n"
            setExecutable(true)
        }
    }
}

tasks.build.dependsOn packForXCode
```

ここの部分ですね。これを追記して`./gradlew build`することで
<img width="300" alt="スクリーンショット 2019-05-02 11.17.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127761/efc8ec51-ef39-e836-6bc6-3378838e0e57.png">
main.frameworkができます。これをUnityのPlugin/iOSに放り込めばframeworkの導入は大丈夫ですが、iOSの場合もう一手間必要です。
externの実装がないとC#では呼び出せませんそのためPlugin/iOSフォルダに

```objective_c:common.mm
#import <main/main.h>

extern "C" {
    const char* createApplicationScreenMessage() {
        NSString *message = [[Maincommon alloc] init].createApplicationScreenMessage;
        return strdup([message UTF8String]);
    }
}

```

を追加しましょう。これを追加することで先ほど作ったKotlinで書いたコードのframeworkを参照できます。
ここでcommonというkotlinファイルを作ったがMaincommonって何だろう？ってなると思います。どうやらframeworkに吐き出す時に変換されているようです。
それを確認するにはAndroidStudioでframeworkのHeaderを見ると一番下の行に自分で実装した処理が追記されていると思います。
<img width="971" alt="スクリーンショット 2019-05-02 11.22.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127761/0af8b621-ab94-a7f8-43b7-cb9f9193a00b.png">

これを参考にしてObjective-c++でインターフェースを定義する必要があります。
ちなみにSwiftだとMaincommonという変換名ではなくcommonで呼び出せそうですがSwiftを使うために手間をかけるよりObjective-c++を書いた方が早いと自分は思うのでこのまま進めます。

## C#で呼び出す
事前準備は終わりました。
UnityではuGUIのTextにネイティブで呼び出した文字列を表示させます。

```c#:CallKotlinNative.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Runtime.InteropServices;

public class CallKotlinNative : MonoBehaviour
{

#if UNITY_IOS
    [DllImport("__Internal")]
    private static extern string createApplicationScreenMessage();
#endif

    private Text viewableText;

    void Start()
    {
        var pluginMessage = "";
#if UNITY_ANDROID
        using (var plugin = new AndroidJavaObject("com.sample.mizotake.kotlinnativeforunity.common"))
        {
            pluginMessage = plugin.Call<string>("createApplicationScreenMessage");
            Debug.Log(pluginMessage);
        }
#elif UNITY_IOS
        pluginMessage = createApplicationScreenMessage();
#endif
        viewableText = GetComponent<Text>();
        viewableText.text = pluginMessage;
    }
}
```

C#側は普通にネイティブプラグインを呼び出すだけですね。
これを実機ビルドまたはシミュレータービルドすることで動作の確認ができると思います。

## 終わりに
Kotlin/NativeのUnityProjectへの導入は手間が必要かと思っていましたが思った以上に簡単でした。ただ、iOSの導入のexternだけどうにかならなかいとAndroidProject内に.mm入れてみて.frameworkだけ吐き出して更新させるなどをしようと思いましたがうまく行きませんでした…Androidネイティブのディレクトリ構成やtaskのカスタムに詳しければどうにかできるのかな？と思っています。
Kotlin/Nativeを使えば基本的に言語はKotlinひとつに統一できますし、共通処理やOS依存処理も問題ないのではない気がしています。個人的にKotlinでUIKitなどもimportして使えることに驚きました。
何にせよ扱う言語は少ないに限ると思っていますのでKotlin/Nativeは良いものだと思います。ただ現在betaなので書き方や吐き出し方が変わる可能性は高いです。

## 追記 (2019/11/26)

先日「Gotanda.unity」というイベントで「いつか使える Kotlin/Native With Unity」というタイトルでLTさせていただきました。スマホだけではなくデスクトップのネイティブプラグインも視野に入れた検証結果です。スライドとリポジトリはブログにまとめています → [Gotanda.unity #14 でLTしてました！](http://maketake.hatenablog.com/entry/2019/11/21/101841)