ライブラリコードをDLL化してコンパイル時間を減らそう！

これは [Unity #2 Advent Calendar 2017](https://qiita.com/advent-calendar/2017/unity2) の12日目の記事です。
昨日は @orangesuzuki さんの [Unityのアニメーションデータを外部プログラムで利用するはじめの一歩](https://qiita.com/orangesuzuki/items/f94dcc80b709a039ff8a) と
@Kan_Kikuchi さんの [日本語から変数や関数名を生成するエディタ拡張](http://kan-kikuchi.hatenablog.com/entry/NameGeneration) でした。

今日は @Takaaki_Ichijo さんの [Unityでフレンドとアイテム交換とかする機能をサーバーレスで作る その１](https://qiita.com/Takaaki_Ichijo/items/d3e30beb3c367e879d6a) です。あわせてご覧ください！

---

Unityで何かを作っていると使い回しのできる便利なコードが溜まってくると思います。
普段はAssetsの中に入れてあるだけだと思いますが、その状態だとある一つの問題が・・・。

# コンパイル時間が延びる

Assetsに存在しているC#のコードはRefresh[^1]の度にコンパイルされています。
何かを更新する度に変更していないはずのコードをコンパイルする時間。その時間は無駄だと断言できます。
もちろん、修正が度々入るような出来たてのコードなら必要であるかもしれません。
しかしドッグフーディングも終わってバグも取れたようなライブラリコードや
AssetStoreやGithubなどからダウンロードしてきたコードなどを毎回コンパイルするのは**すごく勿体無い**！
(消極的な理由として、ダウンロードしてきたコードの警告を見たくないということもありますが)

その問題を解決するのが**DLL化**なのです！

# DLL化する手順

主に以下のような手順で進めていきます。
Windows上で行うため、VisualStudioを例に出していますが
MacやLinuxなどであればXamarinStudioやMonoDevelopment、dotnetコマンドでも可能だと思います。

1. [VisualStudioなどでDLLプロジェクトを作成する。](#visualstudioなどでdllプロジェクトを作成する)
1. [DLLプロジェクトファイルをUnity用に編集する](#dllプロジェクトファイルをunity用に編集する)
1. [DLLプロジェクトにライブラリコードを追加する。](#dllプロジェクトにライブラリコードを追加する)
1. [DLLプロジェクトをビルドする](#dllプロジェクトをビルドする)
1. [DLLをAssetsの中にコピーする](#dllをassetsの中にコピーする)
1. [UnityEditorをRefreshする](#unityeditorをrefreshする)

## VisualStudioなどでDLLプロジェクトを作成する

VisualStudio上でプロジェクトを作成します。
この時、`Class Library (.NET Core)`を選ぶと新しいcsproj形式[^2]になるためそれを選択してください。
(今回は便宜上`ClassLibrary1.csproj`という名前で作成しています)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/47699/b5fbf4fa-a9a4-c9c9-26c0-34298a209133.png)


## DLLプロジェクトファイルをUnity用に編集する

1. ソリューションエクスプローラ上のプロジェクトを右クリックして`Edit ClassLibrary1.csproj`を選択します。  
![image.png](https://qiita-image-store.s3.amazonaws.com/0/47699/f9f9c551-9b3c-45ca-a001-50a08099bd75.png)
1. 開いたcsprojにある  

    ```xml
    <TargetFramework>netcoreapp2.0</TargetFramework>
    ```

    となっている部分を  

    ```xml
    <TargetFramework>net35</TargetFramework>
    ```

    に書き換えてください。
    (.NET Framework 4.6を使用する場合は`net46`)  
1. 自動で作成されている`Class1.cs`は必要ないので削除してください。


### `using UnityEngine;`をしているコードを含む場合に必要な対応

1. ソリューションエクスプローラ上のプロジェクト内にある`Dependencies`を右クリックして`Add Reference`を選択します。  
![image.png](https://qiita-image-store.s3.amazonaws.com/0/47699/65f261be-14a2-17f0-2119-8e2fce9e631c.png)
1. `Browse`を選択して、以下の2つを参照に追加します。  
相対パスで設定されてしまうため、gitなどで管理する場合はレポジトリ内に配置するなどの対策が必要です。  
![image.png](https://qiita-image-store.s3.amazonaws.com/0/47699/85ec5ed2-fb56-556e-b8ad-a617aed10af0.png)
    - C:\Program Files\Unity\Editor\Data\Managed\UnityEngine.dll
    - c:\Program Files\Unity\Editor\Data\UnityExtensions\Unity\GUISystem\UnityEngine.UI.dll

## DLLプロジェクトにライブラリコードを追加する

ソリューションエクスプローラ上のプロジェクトを右クリックして`Open Folder in File Explorer`を選ぶと  
プロジェクトファイルのあるディレクトリがエクスプローラによって開かれます。
対象のcsファイルをこのディレクトリにコピーしてください。(Editor拡張の場合は[後述](#editor拡張コードの場合))
新しいcsproj形式の場合は自動で読み込まれます。(古い形式の場合は別途手動でプロジェクトへ追加してください)

## DLLプロジェクトをビルドする

Build - Build Solution を選択するとDLLがビルドされます。
エラーが出る場合などは適時修正してください。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/47699/95aa3521-9391-5f1d-2fd2-4e2dc2f70695.png)


## DLLをAssetsの中にコピーする

`ClassLibrary1.csprojのパス\bin\Debug\net35\`にDLLが作成されているのでAssets内にコピーします。

毎回手動でコピーするのは面倒なので[CopyDllsAfterBuild](https://www.nuget.org/packages/CopyDllsAfterBuild/)を使って
ビルド時に自動でコピーされるようにする方法を紹介します。

### CopyDllsAfterBuildを使用する

1. `ClassLibrary1.csproj`のあるソリューションに`Console App (.NET Core)`のプロジェクトを追加します。  
(今回は便宜上`CopyDlls`という名前で作成しました。)  
![image.png](https://qiita-image-store.s3.amazonaws.com/0/47699/7e698eb6-a1bc-405c-480b-f59eeb954ef5.png)
1. DLLプロジェクトと同様に`TargetFramework`を`net35`に変更します。
1. 先と同様に`Add Reference`を選択し、`ClassLibrary1`を追加します。  
![image.png](https://qiita-image-store.s3.amazonaws.com/0/47699/6c13312f-08e8-1019-c9c5-e030effe1346.png)
1. `CopyDlls.csproj`の`Dependencies`を右クリックして`Manage Nuget Packages`を選択します。  
![image.png](https://qiita-image-store.s3.amazonaws.com/0/47699/0be50108-223f-75fe-908b-acd759ae0c64.png)
1. CopyDllsAfterBuildを検索し、インストールします。  
![image.png](https://qiita-image-store.s3.amazonaws.com/0/47699/0c8f491a-b951-4a56-0c05-7973e074b084.png)
1. `CopyDlls.csproj`に`CopySettings.json`という名前のファイルを作成し、以下のように記述します。

    ```json
    {
      "destination": "UnityのAssetsまでのパス/DLLを配置するパス",
      "pattern": "*",
      "excludes": [ "UnityEngine", "UnityEditor", "CopyDlls" ]
    }
    ```

1. 先と同様にビルドすると、`ClassLibrary1.dll`が所定に位置へコピーされているはずです。

### エラーが発生した場合

もし以下のようなエラーが出た場合はPowerShellの実行に制限があるため、制限を解除します。

```
2>このシステムではスクリプトの実行が無効になっているため、ファイル C:\Users\ユーザー名\.nuget\packages\copydllsafterbuild\3.1.
2>2\tools\postbuild.ps1 を読み込むことができません。詳細については、「about_Execution_Policies」(https://go.microsoft.com/fwl
2>ink/?LinkID=135170) を参照してください。
2>    + CategoryInfo          : セキュリティ エラー: (: ) []、ParentContainsErrorRecordException
2>    + FullyQualifiedErrorId : UnauthorizedAccess
```

`コマンド プロンプト`又は`batchファイル`にて、以下のコマンドを実行します。
**PowerShellの実行制限を解くことになるため、自己責任でお願いします**

```batch
%WinDir%\SysWOW64\WindowsPowerShell\v1.0\powershell.exe -Command "Set-ExecutionPolicy RemoteSigned"
%WinDir%\System32\WindowsPowerShell\v1.0\powershell.exe -Command "Set-ExecutionPolicy RemoteSigned"
```

Rebuild Solution すればエラーがない状態でコピーされているはずです。

## UnityEditorをRefreshする

UnityEditorへ戻りRefreshされればこれまで通りライブラリコードが使用できます！

### ライブラリコードをデバッグしたくなった場合

DLL化したライブラリコードをデバッグしたくなった場合、  
Assetsの中にcsファイルを戻すことでも対応することはできますが、
UnitTestを作成しデバッグすることをおすすめします。

# Editor拡張コードの場合

Editor拡張コードの場合は以下の点が異なります。

- `UnityEditor.dll`への参照が必要
    - `C:\Program Files\Unity\Editor\Data\Managed\UnityEditor.dll`を参照に追加してください
- Editorディレクトリに配置することが必要（不要かも？**要検証**)
    - Editor用の`CopyDlls`を作成し、Editorディレクトリ内へコピーするよう設定します

# VisualStudio 15.5 以降の場合

unsafeを含んだコードをDLL化した場合、UnityEditorが落ちるという現象を確認しています。
(unsafeコードのコンパイル結果が変わったため?)
その場合はDLLプロジェクト(ClassLibrary1.csproj)にNugetで
`Microsoft.Net.Compilers`の`2.4.0`を入れることで解消できます。
(コンパイラを古いものに戻す対応)

詳しくは @ufcpp さんのブログを読んでみてください。
- [未確認飛行 ビルドで使う C# コンパイラーを差し替える](http://ufcpp.net/blog/2017/12/msnetcompilers/)

# 最後に

これにより、コンパイル時間が短くなっていると同時に、
DLLファイルにまとまったお陰でライブラリを他のプロジェクトへ共有しやすくなっていると思います！
ぜひお試しください！

明日は @kido0617 さんの [フリーのビジュアルノベルアセットFungusを使ってRPGのイベントを作る](http://kido0617.github.io/unity/2017-12-13-fungus-rpg/) と
@keidroid さんの [ScriptableObjectをマスターデータとして扱うあれこれ](https://qiita.com/keidroid/items/1745665c0786cd6cf800) です。

[^1]: 規定ではEditorウィンドウがアクティブになったタイミング
[^2]: ごちゃごちゃとした設定がなくなっているだけでなく、ディレクトリ内のcsファイルを自動で検索してくれるなどすごく便利になっています。