なんだかこのUnityプロジェクト、エディタコンパイル時間が長いぞ、という時の対処

「Unity エディタ くるくる　時間　長い　なぜ」　と検索している皆様（半年前の僕含む）、こんにちは。困りますよね。

Unityはサンプルプロジェクトや、プロジェクトを作り始めたときはサクサクとコード編集してはエディタ実行が動いています。

ところが、アセットストアで買ったアセットがモリモリ増えたり、プロジェクトが開発終盤になってきたり大人数で開発していると、なんだかコードにDebug.Logを一行追加しただけなのに、エディタ実行出来るようになるまで（コンパイル中の時間）が1分以上掛かったりすることがあります。
なんで？Unityが壊れたの？元のようにサクサクと編集してエディタ実行が出来るように戻したい！

となっている人の為の対処法を書いていきます。(2019年12月現在)

先に結論を言うと、「Unityエディタ上でコンパイルされるソースコードの分量を減らす」というのが効いてきます。
Unityエディタ上でコンパイルされるソースコードの分量は、（ものすごく大雑把に言うと）実行ファイルを **ビルドしたAssembly-CSharp.dllのファイルサイズ** に比例します。これが2MBを超えているようだと、たぶん開発中に待ち時間でストレスを感じませんか？

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40627/5d916c08-fbac-2fbb-69d4-215ef726d492.png)


# 本当にコンパイル時間が長いのか測ってみよう
最適化をする前にまずは計測あるのみです。
この記事のスクリプトを使ってコンパイル時間を計測してみましょう！Debug.Log内の文字を書き換えて試しにコンパイルを走らせてみましょう。
何秒でしたか？僕は**110秒を超えたことがあります！**めちゃくちゃつらかったです…

Unityでコンパイル時間を可視化する
https://techblog.kayac.com/visualize-compile-time


# 初歩（あるいはUnity2017.3より前のバージョンを使用中）

## 不要なスクリプトを消しましょう
まずはこれをやりましょう！！
アセットストアからダウンロードしたり、OSSのunitypackageからインストールしたライブラリに大量のスクリプトがあって、その中の一部しか使っていない場合は、一部のスクリプトだけを残して不要なスクリプトは削除しましょう。
コンパイル時に書き換えていないソースコードのスクリプトであっても**書き換えていないことをUnityがチェックするために走査されます。**

あるいは、昔書き捨てたクラスで、今は実際使ってないなあ、というクラスが散らばっている場合も、思い切って削除してしまいましょう。必要になったらGitなどバージョン管理システムから復旧できますからね。
バージョン管理システムをお使いでない場合は、この記事を読むより前にバージョン管理システムをまずは導入しましょう！おすすめしますよ！

## 速いパソコンを買いましょう
お金で解決できることはお金で解決する、というのも立派なエンジニアリングです、納期まであと僅か、とか差し迫っていて、今から検証する時間が無い場合は、もう高いPCを買っちゃった方が早いかもしれません。

//ただし、納期の後の運用があるといずれ何か手を打たなきゃいけなくなりますけどね！

## 変更が頻繁ではないロジック系のモジュールはdllにしましょう
これは、Unity2017.3より前のバージョンでの定番手法でした。 2017.3以降のUnityを使っていて、後述する **Assembly Definition機能を使う、と決めたら不要** です。
以下のURLが参考になると思います。
dotnetコマンドでdllを作成しUnityで使う
https://matcha-choco010.net/2018/09/12/dotnet%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%81%A7dll%E3%82%92%E4%BD%9C%E6%88%90%E3%81%97unity%E3%81%A7%E4%BD%BF%E3%81%86/

この仕組みはAssembly Definitionが無いころに四苦八苦してコンパイル時間を短縮するために使う機会が多かったです。

# 本編:Assembly Definitionを使いましょう
Assembly Definition is 何、みたいな人、大丈夫です。僕もです。
Assembly Definitionの仕組みとしては**ユニティエディタの内部でdll相当の物を作る** ような機能です。
上の方で「dllにスクリプトを逃がすと、本体のコンパイル対象のソースコードが減るからコンパイル時間が減るよね！」と話していたのと同じ仕組みです。なのでコンパイル時間を減らすために導入しましょう。

解説については以下の2記事を読むことでありがたさとか、設定方法やオプションに困ったときにリファレンスとすることが出来ると思います。
（Unity本家のマニュアルのドキュメントがシンプルすぎる…）

Unity Assembly Definition 完全に理解した
https://qiita.com/toRisouP/items/d206af3029c7d80326ed

Unity2017.3のAssembly Definition Filesを適切に設定してコンパイルにかかる時間を削減する
https://qiita.com/nkjzm/items/aaad87dc7f7bc9703449

さて、この記事を読んでいる人は実際に肥大化したプロジェクトに対して

- コンパイル時間は実際減るの？どのくらい？
- プロジェクト全体をasmdef設定しないとダメなの？
- どの粒度で設定するの？
- 何もしてないのにasmdef入れたらビルドが通らなくなったんだけど
- PlayModeTestのテストコードとかあるんだけど
- asmdef内 #ifdef の扱いはどうなってるの、怖いが！？

という様々な疑問を持って不安な気持ちになると思います。
なので一度、**壊しても良い、小さなプロジェクトで練習** するのも手だと思います。

## コンパイル時間は実際減るの？どのくらい？
この例だと75%ほど短くなっています。すごい！（そして、モジュール分けは大雑把でもコンパイル時間に対する効果は大きいと分かります）
http://www.screaminggoose.com/blog/2019/2/4/how-i-cut-unity-compile-times-by-75

## プロジェクト全体をasmdef設定しないとダメなの？
No.まずは分けられるところから分けていきましょう。大丈夫です。**適切な粒度のモジュールに分かれているプロジェクトなら**、他に依存しないUtility的な部分から少しづつ設定していきましょう。

## どの粒度で設定するの
1クラスごとに設定したらリファレンスで死んでしまうので、大体単一機能ごとに分けるのがおすすめです。
UniVRMの設定の様子が参考になると思います。

https://github.com/vrm-c/UniVRM

単一機能ごとにフォルダや参照関係が切られているなら、良い感じに設定できそうです。
複雑なスパゲッティ参照が起きている場合は…フォルダ構成だけは機能ごとに見直して入れ替えて、その中でなんとかなりそうなフォルダだけasmdefを設定しましょう。

おすすめとしては、**アセットストアで買ったアセットや、Githubから導入したOSSのunitypackage、あるいは社内のライブラリから先にasmdefを切りましょう。**これらは依存関係が完結していることが多い上に、直接コードに手を入れる機会も少ない（コンパイルが走りにくい）為です。（イマドキのライブラリとかアセットは設定済みの事も多いですけどね！）

## 何もしてないのにasmdef入れたらビルドが通らなくなったんだけど
**大体エディタ拡張のせいです。** asmdefにエディタ用のコードが混入してしまって、ビルド時にコケます。

対処法としては、このエディタ拡張を入れてエディタ拡張専用のasmdefを自動生成しましょう。最初にババっと自動生成しちゃうのがおすすめです。

asmdefファイルをEditor以下に自動追加するエディタ拡張
https://gentome.com/gentomeblog/2456/asmdefextend/


## PlayModeTestのテストコードとかあるんだけど
PlayModeTestはUnity2018.1以降でasmdef必須になりました。逆に言うとasmdefを設定しないとPlayModeTestすら書けません。
うわ…つら…
(これはテスト実行時間を短縮する効果はあるんですが、実際テストを書こうと決意した勉強中の人の心を折るには十分に面倒くさいです)

なので、2018.1以降を使っている場合はPlayModeTestのコードがあるってことはasmdefが設定済みだと思います。
こんな感じでTestAssemblyであることを明示しておく（UnityEditorから生成したら自動で設定されます）必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40627/71c31853-92e9-b75d-c448-076f3aa392c6.png)

## asmdef内 #ifdef の扱いはどうなってるの、怖いが！？
ScriptDefineSymbolで設定した値や、 UNITY_IOS みたいな#ifdef は、AssembyDefinitionの中まで貫通します。大丈夫です。
逆に言うとifdefを多用していて、ScriptDefineSymbolを頻繁に切り替えるプロジェクトだと、そのたびにAssembyDefinitionの中のコンパイルが走ります。覚悟しましょう…

# 今から設定するけど何か手頃なサンプルは無いの？いきなり今の巨大プロジェクトに導入するのは怖い
- プロジェクト全体をasmdef設定しないとダメなの？
- どの粒度で設定するの
- 何もしてないのにasmdef入れたらビルドが通らなくなったんだけど
- PlayModeTestのテストコードとかあるんだけど
- asmdef内 #ifdef の扱いはどうなってるの、怖いが！？

と言う、上であげたAssembly Definitionのパターンを（なるべくコンパクトに）網羅しようとしたサンプルプロジェクトを作りました。
**壊しても良い、このプロジェクトで練習**しましょう！
https://github.com/neon-izm/AsmdefUnderstandingSample

ReadmeのStep by Stepの通りに設定していくことで、挙動を確認しながら学ぶことが出来る、と思います。

# 補足
## 頑張ってasmdef設定したのにコンパイル時間が縮まらないんだけど！
Auto Referencedのチェックが付いたままかもしれません。