テラシュールブログ

旧テラシュールウェアブログUnity記事。主にUnityのTipsやAR・VR、ニコニコ動画についてのメモを残します。

【Unity】Assembly Definition Filesという神機能

f:id:tsubaki_t1:20180118110129j:plain

今回は地味ですが神機能なAssembly Definition Filesについて紹介します。
この機能は地味ですが、Unity開発の上で発生してる問題を(意図せず)かなり解決してくれるので、Unity 2017.3以降を使う人は是非覚えておいた方が良いかもしれません。

 

物自体は、C#のコードが出力するアセンブリを複数のアセンブリに分割する機能です。

ただし、使い方によってはコンパイル時間の削減によるイテレーションの高速化やビルドサイズ、アセットストアでの不満など様々な要素を解決する可能性がある機能です。

 

目次

 

Assembly Definition Filesという神機能

 Assembly Definition Filesは、C#のコードが出力するアセンブリを複数のアセンブリに分割するだけの、超地味な機能です。

 

 

使い方は単純で

  1. Assets >  Assembly Definition Filesを選択
  2. 名前を指定
  3. アセンブリをまとめたいフォルダに生成したAssembly Definition Filesを移動

これだけで、Assembly Definition Filesを配置したフォルダ以下のコードが別のアセンブリとして登録されます。
下の図の場合、SampleAフォルダ以下のコードが別アセンブリになります。

f:id:tsubaki_t1:20180118165816j:plain

所属するアセンブリは、スクリプトをInspectorで確認すると分かります。

f:id:tsubaki_t1:20180118212540j:plain

 

アセンブリ同士の依存関係を持つ場合…例えばコードから他のアセンブリに対してアクセスを行いたい場合、Referenceで対象のAssembly Definition Filesを選択すればOKです。

逆にAssembly Definition Filesで隔離されていないクラス…つまりAssemblyCSharpに入っているクラスは、Assembly Definition Filesで隔離したクラスに直接アクセス可能です。

f:id:tsubaki_t1:20180118165219j:plain

 

 

こんな地味な機能の何が神なのか

 

コンパイル時間を削減してくれるという神機能

Assembly Definition Filesの主な用途は、コンパイル時間の削減です。

この機能でプロジェクト内のコードを分割すると、コード編集時に再コンパイルすべき範囲を小さく抑えることができて、コンパイル時間が短くなります。

 

例えば今までのUnityプロジェクトだと、一つのコードを変更した場合、特別なフォルダに分割されていない限り全てのコードがコンパイル対象になっていました
その為、プロジェクトが大きくなるに連れてコンパイル時間が30秒~1分もかかり、少しの変更に対する確認コストが大きく増大してしまう事がありました。

 

Assembly Definition Filesを使用した場合、

例えば下のような依存関係の状況ならば、Game Script(例えばステージギミックやゲーム進行等々)を編集しても、NGUIやUniRXといった巨大なコードはコンパイルがスキップされずGameScriptの部分のみがコンパイルされるようになり、コンパイル時間が短縮されます。
(青枠がコンパイル範囲)

 

f:id:tsubaki_t1:20180118163904j:plain

ただし、アセンブリ間で参照関係がある場合、参照元が変更になると参照先も再コンパイルとなります。

System(AssetBundleやリソースの管理、自作当たり判定等々)のコードを変更した場合、その機能を使用しているGame ScriptとAny Systemの部分がコンパイルされます。
ただしNGUIやUniRXは変更がない限りコンパイルされないので、コンパイル時間は短縮されます。

f:id:tsubaki_t1:20180118164133j:plain

 

プラットフォーム毎にコードを分けてくれるという神機能

Assembly Definition Filesはアセンブリをインポートするかをプラットフォーム毎に設定できるみたいです。

f:id:tsubaki_t1:20180118193326j:plain

Unityでプラットフォーム毎に違う動作を定義する場合には、通常はプラットフォーム依存コンパイルでコードを分割する必要がありましたが、上手く使えば「参照先だけを残す」といった事もできそうです。

f:id:tsubaki_t1:20180118193105j:plain

なお少し難しいのがプラットフォーム=Editorの扱いです。
Editorのチェックは「現在選択しているプラットフォームによらず、エディター上動作しているなら有効になる」といった感じです。

少し分かりにくいので、下のような感じと思ってもらえれば良いです。

  • エディターのみにチェックがある場合
    エディター上で認識されビルドには含まれない
    (Editorフォルダと同じような動作)
  • エディターとAndroidにチェックがある場合
    エディター上でも認識され、Android上でも動作する
  • Androidのみにチェックがある場合
    エディター上では認識されず、Android実機上でのみ確認できる

要するに

  • エディタースクリプトはエディターのみにチェックを入れれば良いです。
  • 他は使用したいプラットフォームは全部入れます。
  • プラットフォーム毎に処理を分けたいならば、指定のプラットフォームのみ残した物を用意します。

f:id:tsubaki_t1:20180118195800j:plain

また、このような動作のためコンポーネントをこの機能でON/OFFするのはオススメしませんコンポーネントはファイル毎に割り当てられるGUID依存なので、コンパイル対象から外すとMissingになるためです。
コンポーネントのようなフロント部分はプラットフォームはAny Platformで指定しておき、その先の処理で切替があるならPlatform指定するのが良さそうです。

 

AssetStoreから購入したアセットのNamespaceやクラス名が被っても大丈夫になる事がある神機能

Assembly Definition Filesはアセンブリを分割するので、同じnamespaceの同じクラス名でも、異なるクラスとして扱われるようになります。

f:id:tsubaki_t1:20180118203107j:plain

 通常、同じnamespaceに同じクラス名が存在する場合「error CS0101: The namespace `global::' already contains a definition for `Hoge'」といった感じのエラーが表示されます。

Assembly Definition Filesで分けることで別のクラスとして扱われるようになるので、既に定義されているエラーは出なくなります。

また同じアセンブリからならば呼出も可能です。

f:id:tsubaki_t1:20180118203506j:plain

厄介なのが他のアセンブリから参照している場合で「error CS0433: The imported type `Hoge' is defined multiple times」が出ます。

f:id:tsubaki_t1:20180118203244j:plain

また同名のコンポーネントの場合、AddComponentボタンから取得しようとするとどちらがどちらか分からなくなります。

 

要するに、namespaceが被っても、お互いが自身のパッケージ内で完結した使い方しかしてないならAssembly Definition Filesでちゃんと動くかもしれない…というお話です。

 

 

注意点

便利ですが、いくつか注意点が有ります。

特殊フォルダは無視される

Assembly Definition Filesで定義したフォルダ以下の特殊フォルダ…具体的にはEditorフォルダは無視されるようになります。

その場合はEditorフォルダにAssembly Definition Filesを置いて、Editorのみにチェックを入れる方法で回避出来ます。

f:id:tsubaki_t1:20180118195800j:plain

 

AssetBundleからのみ参照されるコードはストリップされやすくなる

コードがAssetsからDLLに移動することで、コードがよりストリップされやすくなります。例えばAssetBundleのみしか参照していないコードが合った場合、ストリップされるかもしれません。

tsubakit1.hateblo.jp

 

少し補足しておくと、

通常のAssets以下のコード(.csファイルから生成されるDLL)は、基本的に一切ストリップされる事はありません。例えば購入したアセットのexampleコードにPhysicsを参照するコードがあった場合、Physics系のコードは参照されると判断されストリップされず残ります。

しかし、Assembly Definition Filesや別プロジェクトにてコードを別アセンブリにしていた場合、ストリップされることがあります。
(例えばuGUIやUnity Analytics等のAPIはストリップされます)

 

これをなんとかしようとしたのが、これ

tsubakit1.hateblo.jp

 WebGLでテストしたらuGUIでよくあるようにコードが削られてビビリました。

  

循環参照は無理

残念ながら循環参照は起こせません。

循環参照は、例えばAはBに依存するが、BはAに依存するという状態です。

 

unity 2017.3はまだテストには対応してないかも

Unity 2018.1にはテスト用の項目がありますが、2017.3にはありませんでした。

2018.1からはテスト用フォルダを明示的に作るようになっています。

f:id:tsubaki_t1:20180118211434j:plain

 

使いすぎに注意

こんな神機能ですが、使いすぎると依存関係がゴッチャになり、「アレ?このアセンブリ何処のアセンブリから参照されてんだ?」状態になります。

 

私見ですが、以下のくらいの粒度かなと思いますが、どいうでしょう?

  • アセットストアといった外部から取り入れて変更しない系のもの
  • アセットストアのexample系の処理群
  • 用途別のシステム(リソースマネージャ、ABマネージャ、シーケンス制御等)
  • 各システムのエディター拡張
  • プラットフォーム毎の挙動(エディター向け・各プラットフォーム向け)
  • ゲーム進行

 

関連

qiita.com

似たようなアプローチとして個別にDLLする等があります。こちらの「コードをDLLに退避」が今回のAssembly Definition Filesと似ていますが、DLL化の際にGUIDが再発行となり参照が外れる事があります。

コンポーネントのようなGUID依存の機能に使用する場合には注意が必要です。

tsubakit1.hateblo.jp

Unity 2017.3のブログ記事に詳細が載っています。マニュアルにもありますが、何となくコチラを。

blogs.unity3d.com

 namespaceとクラス名が一致してても普通に読めちゃった例

tsubakit1.hateblo.jp