突然KotlinでUnity向けのSDKやPluginを作ることになった人向け資料

# はじめに

この記事は[突然仕事でUnity向けのSDKやPluginを作ることになった人向け資料＜Android実践編＞](https://qiita.com/keidroid/items/455c61de9355eff2a907)の続編です。
最近、Unityはほぼほぼやっていないのですが
受託開発でも[自社開発](http://play.google.com/store/apps/details?id=red.torch.synapse)でもKotlinを使っており、
UnityPlugin開発に使えそうな気づきがあったのでまとめます。

環境は以下です

- Unity2017.2
- Android Studio 3.0.1 (Kotlin 1.2.21)

# 1 開発方法について

aarの作り方、UnityのPlugin仕様は[前回記事](https://qiita.com/keidroid/items/455c61de9355eff2a907)でJavaで行った方法と同じなので省略。
New Module > Android Library でPlugin用Moduleを作成します。

# 2 Android Studio3/KotlinのPlugin開発で変わったこと

以下、サンプル開発に使用したPlugin ModuleのGradleファイルです。

```nantokaplugin.gradle
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 26

    defaultConfig {
        minSdkVersion 21
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

}

dependencies {

    //コメントアウト
    //implementation fileTree(dir: 'libs', include: ['*.jar'])
    //implementation 'com.android.support:appcompat-v7:26.1.0'

    implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"

    //追加
    compileOnly fileTree(dir: 'libs', include: ['unity-classes.jar'])

    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}

repositories {
    mavenCentral()
}
```

## 2.1 Gradleファイルの変更点: Kotlin

Kotlinを有効にしたので以下が増えています。

```nantokaplugin.gradle
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

:

implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
```

プロジェクトのGradleにはKotlinのバージョンが追加されます。

<img width="640" alt="スクリーンショット 2018-02-23 1.00.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/19692/d18b7630-5e4f-2574-29a0-94c525ac3fc7.png">


## 2.2 Gradleファイルの変更点: Android Studio 3

unityのjarをコンパイル時のみ使用して、ビルドしたaarに含まない必要があります。
以下のAかBを行います。

A. libsフォルダ以下のjarを全部いれてunityのjarだけ引っこ抜く

```nantokaplugin.gradle
:
    implementation fileTree(dir: 'libs', include: ['*.jar'])
}

android.libraryVariants.all { variant ->
    variant.outputs.each { output ->
        output.packageLibrary.exclude('libs/unity-classes.jar')
    }
}
```
B. unityのjar以外にjarがなければコンパイル時のみ参照する`compileOnly`を使用

```nantokaplugin.gradle
:
    //コメントアウト
    //implementation fileTree(dir: 'libs', include: ['*.jar'])

    compileOnly fileTree(dir: 'libs', include: ['unity-classes.jar'])
:
```

# 3. KotlinでUnityPluginを書く

サンプルとしてKotlin側(Plugin側)をざっくり書くと以下のようになります。

```UnityBridge.kt
package red.torch.nantokaplugin

import android.app.Activity
import com.unity3d.player.UnityPlayer
import com.unity3d.player.UnityPlayer.UnitySendMessage

@Suppress("unused")
class UnityBridge {

    //3.1 通常のメソッド呼び出し
    fun callUnityCode() {
        UnitySendMessage("ReceivedGameObject", "callbackMethod", "hogehoge")
    }

    //Javaと同じくUI操作はメインスレッドで
    fun someUIAction() {
        val activity = UnityPlayer.currentActivity
        activity.runOnUiThread {
            //UI操作の処理
        }
    }

    companion object {

        //3.2 companion objectメソッド呼び出し
        fun companionMethod(a: Int, b:Int) {
            UnitySendMessage("ReceivedGameObject", "callbackMethod", (a + b).toString())
        }

        //3.3 @Jvmstaticのcompanion objectメソッド呼び出し
        @JvmStatic fun jvmCompanionMethod(a: Int, b: Int) {
            UnitySendMessage("ReceivedGameObject", "callbackMethod", (a * b).toString())
        }
    }
}
```

Unity側のサンプルコードは以下のようになります

```NewBehaviourScript.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class NewBehaviourScript : MonoBehaviour {

    //確認用
    [SerializeField]
    private Text text;

    public void onButton1()
    {
        //3.1 通常のメソッド呼び出し
        using (AndroidJavaObject jo = new AndroidJavaObject("red.torch.nantokaplugin.UnityBridge"))
        {
            jo.Call("callUnityCode");
        }
    }

    public void onButton2()
    {
        //3.2 companion objectメソッド呼び出し
        using (AndroidJavaClass jc = new AndroidJavaClass("red.torch.nantokaplugin.UnityBridge"))
        {
            jc.GetStatic<AndroidJavaObject>("Companion").Call("companionMethod", 2, 3);
        }
    }

    public void onButton3()
    {
        //3.3 @Jvmstaticのcompanion objectメソッド呼び出し
        using (AndroidJavaObject jo = new AndroidJavaObject("red.torch.nantokaplugin.UnityBridge"))
        {
            jo.CallStatic("jvmCompanionMethod", 2, 4);
        }
    }

    //確認用: UnitySendMessageの第2引数にメソッド名を合わせる
    void callbackMethod(string str) {
        text.text = str;
    }
}
```

## 3.1 通常のメソッド呼び出し

Javaとあまり変わりませんがKotlin側のメソッドはpublic省略可、void省略可より以下のようになります。
Javaと同じくUnitySendMessageをゲームオブジェクト名, メソッド名, 引数文字列で呼び出します

```Plugin.kt
    fun callUnityCode() {
        UnitySendMessage("ReceivedGameObject", "callbackMethod", "hogehoge")
    }
```

Unity側ではAndroidJavaObject/AndroidJavaClassがそのまま使えます。
aar化したKotlinのコードJavaとして動作します。

```Unity.cs
        using (AndroidJavaObject jo = new AndroidJavaObject("red.torch.nantokaplugin.UnityBridge"))
        {
            jo.Call("callUnityCode");
        }
:
    void callbackMethod(string str) {
        text.text = str;
    }
```


## 3.2 companion objectメソッド呼び出し

Kotlinにはstatic methodがなくcompanion objectで代用します。
Kotlin上では下記メソッドは`UnityBridge.Companion.companionMethod `として
あたかもstatic methodのように呼び出すことが出来ます。

https://kotlinlang.org/docs/reference/object-declarations.html#companion-objects

```Plugin.kt
     companion object {
:
        fun companionMethod(a: Int, b:Int) {
            UnitySendMessage("ReceivedGameObject", "callbackMethod", (a + b).toString())
        }
```

ただし、Unity側から呼び出す場合は..

```Unity.cs
        using (AndroidJavaClass jc = new AndroidJavaClass("red.torch.nantokaplugin.UnityBridge"))
        {
            jc.GetStatic<AndroidJavaObject>("Companion").Call("companionMethod", 2, 3);
        }
```

上記のように面倒になります。companionはあくまでオブジェクトなので

1. 対象クラスを`AndroidJavaClass`として取得
2. 1からstaticのobjectである`Companion`を取得
3. 2から非staticのメソッド`companionMethod `呼び出し

となります。
＃ 上記危ういのですが、このパターンで実際に動作しました

## 3.3 Jvmstaticのcompanion objectメソッド呼び出し

Kotlinでstatic method実現は難しい..!
のですが`@Jvmstatic`をつけると`Companion`を経由せず
`UnityBridge.jvmCompanionMethod`として呼び出すことが出来ます。

```Plugin.kt
     companion object {
:
        @JvmStatic fun jvmCompanionMethod(a: Int, b: Int) {
            UnitySendMessage("ReceivedGameObject", "callbackMethod", (a * b).toString())
        }
```

Unity側でもJavaのstaticのように呼び出すことが出来ます。楽！

```Unity.cs
        using (AndroidJavaObject jo = new AndroidJavaObject("red.torch.nantokaplugin.UnityBridge"))
        {
            jo.CallStatic("jvmCompanionMethod", 2, 4);
        }
```

# 4. 動作確認

実際にKotlinで作ったaarを組み込んで動作確認を行いました。3.1, 3.2, 3.3が正常に動作することを確認済みです。

- 3.1で"hogehoge"の文字列が`UnitySendMessage`で返ってくる

<img width="240" src="https://qiita-image-store.s3.amazonaws.com/0/19692/429d8ddb-c0ba-391d-2d4f-3b0b64fe3100.png" />

- 3.2 companion objectへのアクセスで2+3の答えが返ってくる

<img width="240" src="https://qiita-image-store.s3.amazonaws.com/0/19692/6c3a74ef-7509-f9b8-4b2d-5734cb2e3787.png" />

- 3.3 jvmstaticのcompanion objectへのアクセスで2*4の答えが返ってくる

<img width="240" src="https://qiita-image-store.s3.amazonaws.com/0/19692/3c7d95fc-9555-ddb0-f959-4a71df53cd36.png" />


# まとめ

- KotlinでUnity Pluginは普通に作れる。
- AndroidJavaObjectも普通に使える。
- static methodはcompanion objectに`@JvmStatic`をつけると良い