Unity x LeapMotionでもAssemblyDefinitionFilesを使いたい

# Leap Motion
Leap Motionは独自のセンサとカメラ技術により、人の手をセンシングするデバイスです。
安価で入手しやすいハンドトラッキングツールとして、発売から5年以上たった今でも人気のデバイスです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274974/ba6a9558-c9ed-a871-f976-81f1a8b29dbe.png)

[Leap Motion リープモーションLM-C01-JP](https://www.yodobashi.com/product/100000001002138480/) : yodobashi.com(現在は取り扱いなし)

# SDK
Leap Motionを使った開発をする上で、以前は公式サイトから開発環境に応じたSDKを入手する必要があったようですが、現在はGitHubからダウンロードすることが出来ます。

* [leapmotion/UnityModules](https://github.com/leapmotion/UnityModules)

UnityのSDKも[4.5.0](https://github.com/leapmotion/UnityModules/releases/tag/UM-4.5.0)が最近アップデートされるなど、VRなどの用途からかまだまだ開発が続いていきそうです。

# Assembly Definition
さて、Unityにはディレクトリ単位でAssemblyを分ける機能があり、ビルド時間短縮などのために自分のプロジェクトと3rdのライブラリのAssemblyを分けるために、Assembly Definition File(asmdef)を定義することがよく行われます。

* [Unity Assembly Definition 完全に理解した](https://qiita.com/toRisouP/items/d206af3029c7d80326ed)

上の記事でも

> ライブラリや、スクリプトを含んだアセットを公開する場合はadfを定義してアセンブリを分割するべきです。
むしろやってくださいおねがいします。

とあるように、開発者がUnity向けにライブラリ開発を行う場合はAssembly Definitionを考慮して開発することが望まれます。しかし、LeapMotionのUnity向けライブラリには2020/5/16日時点で

**Assembly Definition Fileは定義されていません。**

**Assembly Definition Fileは定義されていません。**

😇

## 無いとどうなる
無くても問題ないでしょ？というのはもちろんそうです。LeapMotionの中の人たちもこのスタンスなのでしょう。
しかし、すでにAssembly Definitionを持った自分のプロジェクトにLeapMotionのSDKを突っ込みたくなった時問題になります。具体的には

* LeapMotionの定義を使用するAssemblyのディレクトリ内部でSDKを展開する
    * つまり自プロジェクトと同じAssemblyに含めてしまう
* 自分でSDKのAssembly Definitionを設定する

の二択を迫られます。前者の場合で回避できるなら良いですが、Assembly Definitionの利点を殺しています。また、今後LeapMotionを必要とするAssemblyすべてに配置する必要が出てきます。

## 自分で定義

私が定義したAssembly DefinitionをGitHubに公開しています。

* https://github.com/naninunenoy/LeapMotionAsmdef

使ってみたい方は [ここからunitypackageをダウンロード](https://github.com/naninunenoy/LeapMotionAsmdef/releases/tag/1.1) し、自分のUnityプロジェクトにimportしてください。

UnityModules-4.5.0のデフォルトからディレクトリ構成を変えていなければ、そもまま同じディレクトリに.asmdefが配置されます。

余談ですが、[UniVRMでは.asmdefのみのunitypackageを分けてリリースしているようです](https://github.com/vrm-c/UniVRM/releases)。

### 注意
* 現状 4.5.0 の Core.unitypackage にしか対応していません。
* 将来LeapMotionが正式にAssembly Definitionに対応した場合、間違いなく競合します。

＊＊＊

ここで「それでは良いLeapMotion+Assembly Definitionライフを」で終わってもよいですが、折角なので苦労話も載せておきます。

# 苦労話
さて、新しくAssembly Definitionを追加する場合、まず何をするでしょうか？
そう、とりあえず右クリックで[Create]->[Assembly Definition]から.asmdefを作りますね。

では、UnityModules-4.5.0の *Assets/Plugins/LeapMotion/Core* にLeapMotion.Core.asmdefを作ってみましょう。何が起こるでしょうか？ちなみにUnity のバージョンは2019.3.12です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274974/cc4a740e-ef24-284e-c63d-39a12d232dfd.png)

正解は603件のコンパイルエラーでした。

## why?
LeapMotionは殊勝なことにSDKのテストをしっかり実装しており、テストコードもunitypackageに含めています。しかし、LeapMotion.Core.asmdefにて独自のAssemblyを定義したことにより、`NUnit` などのテストのための定義が *Assets/Plugins/LeapMotion/Core/* から参照できなくなってしまったのが原因です。
すべての定義が `Assembly-CSharp` にある状態なら問題にならなかったのですが、Assembly Definitionにて *Assets/Plugins/LeapMotion/Core* 以下のディレクトリが独立したAssemblyになったため、`NUnit` など必要な定義を参照できる設定を.asmdefにしてやる必要があります。

## how?
LeapMotion.Core.asmdefの設定だけを良い感じにすればよいのでしょうか？

答えは **No** です。

#### 全ての *Tests/* のディレクトリにテスト用のAssembly Definition設定をする必要があります。

しかもこれ、以前(Unity2018.x)ではチェックボックスで切り替えれたのですが、2019.2からなくなりました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">2019.2 だとテスト関連の asmdef を参照させないとダメかな？ <a href="https://twitter.com/hashtag/UniteTokyo?src=hash&amp;ref_src=twsrc%5Etfw">#UniteTokyo</a> <a href="https://twitter.com/hashtag/RoomD?src=hash&amp;ref_src=twsrc%5Etfw">#RoomD</a></p>&mdash; もんりぃ先生 (@monry) <a href="https://twitter.com/monry/status/1177060908360556545?ref_src=twsrc%5Etfw">September 26, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

[Create]->[Testing]->[Tests Assembly Folder]で作ることはできますが、いくつもあるLeapMotionの *Tests/* ディレクトリ全てにはとてもやってられません。

Unityの仕様上.asmdefが追加される度にコンパイルが走ってハングするので尚更です。

#### 全ての *Editor/* ディレクトリの.asmdefに `platfrom=Editor` を定義してやる必要があります。

Assembly Definition無しなら *Editor/* という名のディレクトリは自動でEditor専用のディレクトリとして認識されますが、Assembly Definitionを追加した場合、*Editor/* 内に `platfrom=Editor` になるように自分で.asmdefを設定しなくてはなりません。

なぜ[Create]->[Editor Assembly Definition]のような操作がないのでしょうね。こちらも1つ設定するたびにUnityがハングするのでとても手作業ではやってられません。

余談ですが、おそらく最短の `platfrom=Editor` 手動設定順は

 * [Any Platformのチェックを外す]
 * [Deselect all]
 * [Editorのチェックを入れる]
 * [Apply]

だと思います。

## これキツくね?

私も手間がリターンに合わないと判断して一度は諦めました。しかし、できそうな手順を思いついたのでやってみました。キツかったですが詰んではなかったです。

### .asmdefの自動生成

人間の温かみのある手作業で.asmdefを追加していくと毎回Unityがハングするので、Unityに気づかれないように.asmdefを一括で追加します。

*LeapMotion/Core/* 以下にある *Editor/* と *Tests/* という名前のディレクトリを発見し、ディレクトリのパスに応じた名前の.asmdefを自動生成するエディタ拡張を実装しました。

* [LeapMotionEditorExtension.cs](https://github.com/naninunenoy/LeapMotionAsmdef/blob/master/Assets/n5y.LeapMotionEditorExtension/Editor/LeapMotionEditorExtension.cs)

例えば、 *LeapMotion/Core/Scripts/Animation/Editor/* のディレクトリには LeapMotion.Core.Scripts.Animation.Editor.asmdef というAssembly Definition Fileが生成されます。

### .asmdefの一括参照設定

.asmdefを追加しただけでは参照の設定がないのでコンパイルエラーは直りません。
人間の温かみのある手作業で.asmdefを一つ一つ編集するとそのたびにUnityがハングするので、LeapMotionとは別の個人開発でAssembly Definition Fileを同時に編集するエディタ拡張を作っていたので活用しました。

* https://github.com/naninunenoy/AsmdefHelper#multiple-edit

とはいえ、内部の依存関係はSDK開発者でない私にはわからないので、取り合えずは全ての.asmdefに LeapMotion.Core.asmdef への参照を追加し、以降はエラー内容を確認して対応しました。

流石はLeapMotion、Assembly Definitionこそ無かったものの破綻した依存関係にはなっておらず、苦労はしましたが.asmdefの設定に無事成功しました。これからは `Frame` や `Image` クラスを使いたいAssemblyにだけ LeapMotion.Core.asmdef の参照を追加すればよいのです。

# おわりに

UnityのAssembly Definitionは便利で強力なのでもっと広く使われてほしいのですが、今回のようなややこしい問題に直面するとなると、正直まだ難しいのかなと思ってしまいます。

しかし、ライブラリ作成者は別です。Scriptを含む場合は是非Assembly Definitionを設定してください。