【Unity】asmref(asmdefではない)を使うとinternalアクセスできて便利【Assembly Definition Reference Files】


はじめに

Assembly Definition Files( *.asmdef )については解説を見かけるのですがAssembly Definition Reference Files( *.asmref )についての解説を見かけないのでメモがてら書き残します。

Assembly Definition Files

影響下にあるスクリプトを一つのDLLとしてコンパイルすることで全体のコンパイル時間を短縮してくれたりするやつです。

参考:

Assembly Definition Reference Files

Unity2019.2.0で追加された機能で、

Scripting: Added support for Assembly Definition Reference Files (asmref). These allow for adding additional source code directories to an existing Assembly Definition File (asmdef).

DeepL翻訳:

アセンブリ定義参照ファイル(asmref)のサポートを追加しました。これにより、既存のアセンブリ定義ファイル(asmdef)にソースコードのディレクトリを追加することができます。

とのことです。

機能としてはシンプルで、

  • 自身の影響下にあるスクリプトを指定した *.asmdef と同じDLLファイルに含める
  • 以下のスクリプトファイルに影響する( *.asmdef と同じ)
    • *.asmref が存在するディレクトリ内
    • 同ディレクトリ内に存在するディレクトリ、その子,孫,..(以下ループ)ディレクトリ
      • 子ディレクトリを辿る途中で別の *.asmref (X)が存在したら、それ以下は(X)の影響下になる

となっています。

作成方法は *.asmdef とほぼ同じで、

  Projectウィンドウを右クリック → CreateAssembly Definition Reference

で作成できます。

実際に *.asmref ファイルを作ってInspectorで確認するとこんな感じ。

ファイルの実態は短いテキストファイルで、開いて確認してみると

Use GUIDがオンのとき:

{
    "reference": "GUID:d00ea8eea3b8a4df79ba6bcba16173ef"
}

Use GUIDがオフのとき:

{
    "reference": "AutoScreen"
}

といった感じになります。

用途

とりあえず思いついたやつを3つほどご紹介します。

*.asmdefによって強制されるスクリプトの配置を好きに変更できる

*.asmdef を使用するとディレクトリ構造がある程度強制されますが、*.asmref を使うことで *.asmdef の配置場所とは関係なくスクリプトを配置できます。

ちなみに同一の *.asmdef を参照する *.asmref は複数追加できます。

アセットやPackage Managerから入れたパッケージに外から手を加える

例えば UniRx には Observable.Using() が実装されてないのですが、これを追加するためにリポジトリをForkして手を加えたり、 Assets/ 以下にソースコードをすべて配置してカスタマイズするのは面倒です。

*.asmref を使用すると UniRx をPackage Managerから追加しつつ、 Observable.Using() のような独自の処理を追加できます。

  1. Package ManagerからUniRxを追加
  2. 適当なディレクトリ(例えば Assets/UniRxExtensions/ )(A)を作成
  3. (A)に *.asmref ファイルを追加& Assembly DefinitionUniRx.asmdef を指定
  4. UniRxに追加したい処理を(A)に追加
Observab.Using.cs
using System;

namespace UniRx
{
    public static partial class Observable
    {
        // https://github.com/Reactive-Extensions/Rx.NET/blob/371c83c621562a2259580a03f0cb5bf8680ea720/Rx.NET/Source/System.Reactive.Linq/Reactive/Linq/QueryLanguage.Creation.cs#L417-L441
        public static IObservable<TSource> Using<TSource, TResource>(
            Func<TResource> resourceFactory,
            Func<TResource, IObservable<TSource>> observableFactory)
        where TResource : IDisposable
        {
            return Observable.Create<TSource> (observer =>
            {
                var source = default(IObservable<TSource>);
                var disposable = Disposable.Empty;
                try
                {
                    var resource = resourceFactory();
                    if (resource != null)
                    {
                        disposable = resource;
                    }
                    source = observableFactory(resource);
                }
                catch (Exception exception)
                {
                    return new CompositeDisposable(Throw<TSource>(exception).Subscribe(observer), disposable);
                }

                return new CompositeDisposable(source.Subscribe(observer), disposable);
            });
        }
    }
}

*** 注意点 ***

  1. 処理を「足す」ことはできるが「引く/変更する」ことはできない
  2. Package Managerで入れたパッケージ内の *.asmdefAssembly Definition References に何かを足す・上書きすることはできない
    • 例えば こちら で紹介されてる Observable.FromUniTask()*.asmref で足すのは無理
      • そもそも UniRx.asmdef には UniTask.asmdef への参照が含まれていない
      • なので UniRx.dll から UniTask.dll を参照できない

Unityの internal クラスにアクセスする

(使用は自己責任でお願いします)

こちらの記事では特殊な *.asmdef を追加する手法が紹介されてますが、同様のことは *.asmref でも可能です。

例えば UnityEditor.UI を指定した *.asmref を作成すると、その影響下にあるスクリプトはUnityの internal クラスにアクセスできます。

*.asmdef を使用する場合は同一名のものを複数追加するとコンフリクトが起きて以下のようなエラーになります。

Assembly with name 'Unity.InternalAPIEditorBridgeDev.001' already exists (Assets/Foo/NewAssembly.asmdef)

が、*.asmref を使用する場合は上記のようなコンフリクトを気にする必要がありません。

ただし、配布するライブラリなどでこのテクニックを使用する場合は、 *.asmref に指定した *.asmdef がプロジェクトに確実に含まれている必要があります。不要なパッケージは削除されてる場合があるので注意が必要です。

ちなみに

同一のディレクトリに *.asmdef*.asmref を両方配置すると以下のようなエラーになります。

Folder 'Assets/Foo/' contains multiple assembly definition files (Assets/Foo/NewAssemblyReference.asmref)
Folder 'Assets/Foo/' contains multiple assembly definition files (Assets/Foo/NewAssembly.asmdef)

同一のディレクトリに *.asmref を複数配置した場合は、同じ *.asmdef を指定してある分にはエラーになりませんでした。エラーになるかと思った😇

さいごに

*.asmref*.asmdef のかゆいところに手が届く、そんな存在。ぜひ使ってみてください。