UnityでGradleを使ったAndroidプラグイン開発

## 本構成による恩恵

UnityとAndroidが密に連携している場合、Android Studioで効率良くプラグインを開発するのは難しくなる。
そこで、Android StudioでもUnityの実行環境の上で、デバッグ開発を行えるようにし、効率良くプラグインの開発を行えるように工夫した。

![text8111-4.png](https://qiita-image-store.s3.amazonaws.com/0/116939/63564038-4c6a-7fd6-0567-c9315c5eefcd.png)

## 動作確認環境

- Windows
- Unity 5.5.1f1
- Android Studio 2.2.3

## サンプルプロジェクト

[GitHub: UnityAndroidPluginSample](https://github.com/seachicken/UnityAndroidPluginSample/tree/unity5_5_1f1)

## プロジェクト全体の構成

```
UnityAndroidPluginSample
  │
  └── UnityProject                  // Unityプロジェクト
  │   └── copy_unity_export.bat     // コピーバッチ(Sync Android Studioから実行される)
  │   └── exclude_unity_export.list // コピー除外リスト
  │   └── clean_unity_export.bat    // エクスポートデータの初期化バッチ(Sync Android Studioから実行される)
  │   └── Assets/Plugins/Android
  │       └── AndroidManifest.xml   // 拡張項目を追加するAndroidManifest
  │       └── androidplugin.aar     // プラグイン生成モジュールのビルド結果
  │
  └── AndroidPluginFactory          // Androidプラグイン開発プロジェクト
  │   └── androidplugin             // プラグイン生成モジュール
  │   └── app                       // Unityからエクスポートされたモジュール
  │
  └── UnityExport                   // Unityからエクスポートされた中間データ
```

## 開発環境の構築

1. Androidプラグイン開発用に新規プロジェクトを作成する
   ![001.png](https://qiita-image-store.s3.amazonaws.com/0/116939/0937faf8-bbc5-0cfe-194c-12f2fe955a47.png)
-  ベースとなる`app`モジュールを作成する  
   中身は後の工程でUnityからエクスポートされる為、ここでは空のプロジェクトを選択する。
   ![002.png](https://qiita-image-store.s3.amazonaws.com/0/116939/2d9136a2-b1a4-3070-84a0-c4c2ca693ae8.png)
-  `AndroidPluginFactory/app`の下は、`build.gradle`を残し、削除する
   ![image8758.png](https://qiita-image-store.s3.amazonaws.com/0/116939/a46cec9e-b04a-f95e-a394-a2089a2e3dd7.png)
   また、`build.gradle`をプラグイン開発に必要な内容に変更する。`dependencies`に`androidplugin`モジュールを追加し、Android Studioで実行した際にプラグインと連携できるようにする。

    ```groovy:app/build.gradle
    apply plugin: 'com.android.application'
    
    android {
        compileSdkVersion 25
        buildToolsVersion "25.0.0"
        defaultConfig {
            applicationId "com.example.androidpluginfactory"
            minSdkVersion 19
            targetSdkVersion 25
        }
    }
    
    dependencies {
        compile files('libs/unity-classes.jar')

        // ビルドタイプに応じて、ライブラリモジュールのビルドタイプを変える
        releaseCompile project(path: ':androidplugin', configuration: 'release')
        debugCompile project(path: ':androidplugin', configuration: 'debug')
    }
    ```
-   Unityからエクスポートしたデータを、Android Studioの`app`モジュールへコピーするバッチを作成

    ```bat:copy_unity_export.bat
    @rem 古いデータを削除
    rd /s /q ..\AndroidPluginFactory\app\build
    rd /s /q ..\AndroidPluginFactory\app\libs
    rd /s /q ..\AndroidPluginFactory\app\src
    
    @rem Unityからエクスポートしたデータを、プラグイン開発プロジェクトへコピー
    echo A | xcopy /e /EXCLUDE:exclude_unity_export.list ..\UnityExport\UnityProject ..\AndroidPluginFactory\app\
    
    pause
    ```

    コピー不要なデータの除外リストを作成。

    ```bat:exclude_unity_export.list
    .mdb
    build.gradle
    androidplugin.aar
    ```

    また、Unityからのエクスポートは上書き出力になる為、初期化するバッチも併せて作成する。

    ```bat:clean_unity_export.bat
    rd /s /q ..\UnityExport
    ```
-   `AndroidManifest.xml`を`Plugins/Android`下に作成し、UnityのActivityを拡張する

    ```xml:AndroidManifest.xml
    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
      <application android:icon="@drawable/app_icon">
        <activity android:name="com.example.androidplugin.SampleActivity" android:label="@string/app_name">
          <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
          </intent-filter>
        </activity>
      </application>
    </manifest>
    ```
-   UnityプロジェクトにAndroid Studioと同期する為のエディタ拡張コードを作成

    ```csharp:DevelopmentTool.cs
    using System.Diagnostics;
    using System.Linq;
    using UnityEditor;
    
    public class DevelopmentTool
    {
        [MenuItem("Tools/Sync Android Studio")]
        public static void SyncAndroidStudio()
        {
            EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTarget.Android);
            EditorUserBuildSettings.androidBuildSystem = AndroidBuildSystem.Gradle;

            // エクスポートは上書き出力されてゴミが残ってしまう為、事前に古いエクスポートデータを初期化する
            RunBatch(@"{Application.dataPath}\..\clean_unity_export.bat");

            const BuildOptions options = BuildOptions.AcceptExternalModificationsToPlayer
                                    | BuildOptions.Development
                                    | BuildOptions.AllowDebugging;

            BuildPipeline.BuildPlayer(
                (from scene in EditorBuildSettings.scenes where scene.enabled select scene.path).ToArray(),
                "../UnityExport",
                BuildTarget.Android,
                options
            );

            // プラグイン開発プロジェクトへUnityからエクスポートしたデータをコピー
            RunBatch(@"{Application.dataPath}\..\copy_unity_export.bat");
        }

        private static void RunBatch(string path)
        {
            var process = new Process();
            var info = process.StartInfo;
            info.FileName = path;
            info.UseShellExecute = false;
            process.Start();
            process.WaitForExit();
            process.Close();
        }
    }
    ```
-   エディタ拡張により表示された「Sync Android Studio」を実行し、`AndroidPluginFactory/app`下にUnityの実行データをコピーする
    ![image8780.png](https://qiita-image-store.s3.amazonaws.com/0/116939/a48e6b8c-f7f5-9a5d-fee8-64a912eefa68.png)
-   Android Studioに戻り、プラグイン開発用のモジュールを新規作成する
    ![image8802.png](https://qiita-image-store.s3.amazonaws.com/0/116939/1036e982-b36f-cd66-9525-1413ba6e6881.png)
    
    ![image8813.png](https://qiita-image-store.s3.amazonaws.com/0/116939/7f2da939-ca38-cc33-a708-21549a87fcc0.png)
-   プラグイン開発用モジュールの`build.gradle`を編集する
    - ライブラリモジュールのデバッグビルドを有効化（`BuildConfig.DEBUG`等が有効化される）
    - `dependencies`に、Unityからエクスポートされたライブラリの参照を追加
    - `androidplugin`モジュールをAAR化してUnityへ転送するスクリプトを追加

    ```groovy:adnroidplugin/build.gradle
    android {
        ~~~

        // ライブラリモジュールのデバッグビルドを有効化
        publishNonDefault true
    }

    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compile files('../app/libs/unity-classes.jar')
    }

    android.libraryVariants.all { variant ->
        variant.outputs.each { output ->
            output.packageLibrary.exclude('libs/unity-classes.jar')
        }
    }
    
    def AAR_NAME='androidplugin.aar'
    task makeAndExportAar(type: Copy) {
        from('build/outputs/aar/')
        into('../../UnityProject/Assets/Plugins/Android/')
        include('androidplugin-release.aar')
        rename('androidplugin-release.aar', AAR_NAME)
    }
    makeAndExportAar.dependsOn(build)
    ```
-   `UnityPlayerActivity`を拡張する

    ```java:
    public class SampleActivity extends UnityPlayerActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            // 拡張に成功したら"Android拡張成功！"とToast表示
            Toast.makeText(this, R.string.complete_message, Toast.LENGTH_LONG).show();
        }
    }
    ```
-   最後に、正常にビルドできることを確認する
    ##### [ Android Studio側 ]  
    1. `app`モジュールを実行
    2. Toastで「Android拡張成功！」の表示を確認する

    ##### [ Unity側 ]
    1. Android Studioにおける右ペインGradleの、  
       `AndroidPluginFactory > :androidplugin > Tasks > other > makeAndExportAar`を実行
       ![image8824.png](https://qiita-image-store.s3.amazonaws.com/0/116939/4964f914-9b4f-dcaa-772e-44c46f203a23.png)
    2. Unityで「Build And Run」を実行
    3. Toastで「Android拡張成功！」の表示を確認する

## 運用時の注意点

- ブランチ切り替えを行った場合は、Unityと同期が取れていない為、「Sync Android Studio」を行う
- `androidplugin`モジュールを変更した場合は、`makeAndExportAar`を実行しAARを更新してからコミットする（あくまでもAndroid Studioは拡張機能を開発する為の補助的な位置づけなので、Unityでビルドした際に正常に動作することを意識する）