Version: 2023.1
言語: 日本語
スクリプトの制限
Unity リンカー

マネージコードストリッピング

ビルドプロセス中、Unity はマネージコードストリッピングと呼ばれるプロセスを通じて、使用されていないコードや到達できないコードを削除し、アプリケーションの最終サイズを大幅に縮小します。マネージコードストリッピングは、プロジェクト内の C# スクリプトからビルドされたアセンブリ、パッケージやプラグインの一部であるアセンブリ、および .NET Framework のアセンブリなどの、マネージアセンブリからコードを削除します。

Unity は、Unity リンカーと呼ばれるツールを使用して、プロジェクトのアセンブリ内のコードの静的解析を実行します。静的解析は、実行中に到達できないクラス、クラスの一部、関数、または関数の一部を特定します。この解析には、ビルド時に存在するコードのみが含まれます。これは、Unity が静的解析を実行する際に、ランタイムに生成されたコードは存在しないからです。

Managed Stripping Level 設定を使用して、Unity がプロジェクトに対して実行するコードのストリッピング (削除) のレベルを設定できます。Unity がコードの特定の部分を削除しないようにするには、アノテーション (注釈、注記) を使用して、Unity リンカーがコードベースのどの部分を保持するべきかを指定します。詳細については、Unity リンカー を参照してください。

マネージコードストリッピングの設定

Managed Stripping Level プロパティ
Managed Stripping Level プロパティ

Managed Stripping Level プロパティは、Unity リンカーがアプリケーションのコードを解析して削除するときに従う一群の規則を決定します。設定を Minimal から High に上げると、規則により、リンカーは到達不可能なコードを探すために、より多くのアセンブリを検索するようになります。Unity リンカーは、レベルを高くするとより多くのコードを削除し、ビルドの最終サイズを縮小しますが、検索範囲が広がるため、各ビルドの生成に時間がかかるようになります。

Managed Stripping Level プロパティを変更するには、以下を行います。

  1. Edit > Project Settings > Player の順に移動します。
  2. Other Settings で、Optimization に移動します。
  3. **Managed Stripping Level プロパティを好みの値に設定します。
プロパティ 機能
Disabled Unity はコードを削除しません。

この設定は、Mono スクリプトバックエンドを使用する場合にのみ表示され、そのデフォルトの設定となります。
Minimal Unity は、UnityEngine と .NET のクラスライブラリのみを検索して、未使用のコードを探します。ユーザーが書いたコードは削除されません。これは、予期しないランタイムの動作を引き起こす可能性が最も低い設定です。

この設定は、ビルドサイズよりも使いやすさを優先するプロジェクトに便利です。これは、IL2CPP スクリプトバックエンドを使用する場合のデフォルト設定です。
Unity は、一部のユーザー作成アセンブリとすべての UnityEngine および .NET クラスライブラリを検索して、未使用のコードを探します。この設定は、一部の未使用のコードを削除する一群の規則を適用しますが、リフレクションを使用するランタイムコードの動作の変更などのような、意図しない結果を起こす可能性を最小限に抑えます。
Unity は、到達不可能なコードを見つけるために、すべてのアセンブリを部分的に検索します。この設定は、ビルドサイズを減らすために、より多くの種類のコードパターンを除去する一群の規則を適用します。Unity は到達不可能なコードの可能性をすべて除去するわけではありませんが、この設定により、望ましくない、または予期しない動作変更のリスクが高まります。
Unity はすべてのアセンブリを広範囲に検索して到達不可能なコードを見つけます。この設定では、Unity はコードの安定性よりもサイズの縮小を優先し、可能な限り多くのコードを削除します。

この検索は、より低いストリッピングレベルの場合よりもはるかに時間がかかることがあります。この設定は、ビルドサイズをコンパクトにすることが非常に重要なプロジェクトにのみ使用してください。アプリケーションを徹底的にテストし、[Preserve] 属性と link.xml ファイルを慎重に使用して、Unity リンカーが重要なコードを除去しないようにします。

アノテーションを用いたコードの保存

アノテーションを使用すると、Unity リンカーがコードの特定のセクションを除去するのを防ぐことができます。これは、アプリケーションが静的解析を実行するときに存在しないランタイムコードを生成する場合に便利です。リフレクション などがその例です。アノテーションは、Unity リンカーに、削除すべきでないコードパターンに関する一般的なガイダンスを提供するか、または、コードの特定の定義されたセクションを削除しないように指示します。

マネージコードのストリッピングからコードを保護するためのアノテーションには、大きく分けて 2 つの方法があります。

  • ルートアノテーションは、コードの一部をルートとして識別します。Unity リンカーは、ルートとしてマークされたコードを除去することはありません。ルートアノテーションの使用はそれほど複雑ではありませんが、Unity リンカーが取り除くべきコードを残してしまう可能性もあります。
  • 依存関係アノテーションは、コード要素間の接続を定義します。依存関係アノテーションは、ルートアノテーションと比較して、コードの維持し過ぎを減らすことができます。

それぞれのテクニックは、高いストリッピングレベルで Unity リンカーがストリッピングするコードの量をより良く制御し、重要なコードが削除される可能性を低くすることができます。アノテーションは、コードがリフレクションを通して他のコードを参照する場合に特に有用です。なぜなら、Unity リンカーは、リフレクションの使用を常に検出できるわけではないからです。

アプリケーションの実行時に予期しない動作が発生する可能性を大幅に減らすために、リフレクションを使用するコードや、ランタイムに他のコードを生成するコードを保存してください。Unity リンカーが認識できるリフレクションパターンの例については、Unity Intermediate Language Linker reflection test suite を参照してください。

ルートアノテーション

ルートアノテーションは、Unity リンカーにコード要素をルートとして扱わせ、コードのストリッピング処理で削除されないようにします。使用できるルートアノテーションには 2 つの種類があり、コンストラクターやアセンブリと共に個々のタイプを保持する必要があるかどうかによって異なります。

  • Preserve Attribute: 個々のタイプをルートとしてマークし、それらを保存します。
  • Link.xml: アセンブリと、そのアセンブリ内のすべてのタイプや他のコードエンティティをルーツとしてマークし、保存します。

Preserve 属性でルートにアノテーションを加える

Preserve 属性を使用すると、コードの特定のセクションを Unity リンカーの静的解析から個別に除外することができます。コードの一部にこの属性をアノテーションとして加えるには、保持したいコードの最初の部分の直前に [Preserve] を追加します。以下のリストは、以下のリストは、[Preserve] 属性を使用してさまざまなコード要素に注釈を付けるときに、Unity リンカーが保持するエンティティを示しています。

  • Assembly: アセンブリのすべての型を保存します。[Preserve] 属性をアセンブリに割り当てるには、アセンブリに含まれるいずれかの C# ファイルに、名前空間の宣言の前に、属性宣言を配置します。
  • Type: クラスまたは型とそのデフォルトコンストラクターを保持します。
  • Method: メソッド、メソッドを宣言した型、メソッドが返す型、そしてすべての引数の型を保持します。
  • Property: プロパティ、プロパティを宣言する型、プロパティの値型、プロパティ値を取得設定するメソッドを保持します。
  • Field: フィールド、フィールド型、フィールドを宣言する型を保持します。
  • Event: イベント、イベントを宣言した型、型、イベントが返す型、[add] アクセサー、[remove] アクセサーを保持します。
  • Delegate: デリゲート型とデリゲートが呼び出すすべてのメソッドを保持します。

[Preserve] 属性は、ある型とそのデフォルトコンストラクターの両方を保持したい場合に使用します。両方ではなくどちらか一方を残したい場合は、link.xml ファイルを使用します。

[ Preserve] 属性は、すべてのアセンブリと名前空間で定義できます。UnityEngine.Scripting.PreserveAttribute クラスを使用したり、UnityEngine.Scripting.PreserveAttribute のサブクラスを作成したり、独自の PreserveAttribute クラスを作成することができます。以下はその例です。

class Foo
{
    [Preserve]
    public void PreservedMethod(){}
}

Link XML ファイルによるルーツのアノテーション

プロジェクトに link.xml という名前の .xml ファイルを加えると、一群の特定のアセンブリやアセンブリの一部を保持することができます。link.xml ファイルを、プロジェクト内の Assets フォルダーまたは Assets フォルダーのサブディレクトリに置き、ファイル内に <linker> タグを加える必要があります。Unity リンカーは、link.xml ファイルに保存されたアセンブリ、型、またはメンバーをルートタイプとして扱います。

プロジェクト内の link.xml ファイルは数の制限なく使用することができます。その結果、各プラグインに対して個別の保存宣言を提供できます。link.xml ファイルをパッケージに加えることはできませんが、パッケージ以外の link.xml ファイルからパッケージアセンブリを参照することはできます。

以下の例は、link.xml ファイルを使用して、プロジェクトのアセンブリのルートタイプを宣言できるさまざまな方法を示しています。

<linker>
  <!--Preserve types and members in an assembly-->
  <assembly fullname="AssemblyName">
    <!--Preserve an entire type-->
    <type fullname="AssemblyName.MethodName" preserve="all"/>

    <!--No "preserve" attribute and no members specified means preserve all members-->
    <type fullname="AssemblyName.MethodName"/>

    <!--Preserve all fields on a type-->
    <type fullname="AssemblyName.MethodName" preserve="fields"/>

    <!--Preserve all fields on a type-->
    <type fullname="AssemblyName.MethodName" preserve="methods"/>

    <!--Preserve the type only-->
    <type fullname="AssemblyName.MethodName" preserve="nothing"/>

    <!--Preserving only specific members of a type-->
    <type fullname="AssemblyName.MethodName">
        
      <!--Fields-->
      <field signature="System.Int32 FieldName" />

      <!--Preserve a field by name rather than signature-->
      <field name="FieldName" />
      
      <!--Methods-->
      <method signature="System.Void MethodName()" />

      <!--Preserve a method with parameters-->
      <method signature="System.Void MethodName(System.Int32,System.String)" />

      <!--Preserve a method by name rather than signature-->
      <method name="MethodName" />

      <!--Properties-->

      <!--Preserve a property, it's backing field (if present), 
          getter, and setter methods-->
      <property signature="System.Int32 PropertyName" />

      <property signature="System.Int32 PropertyName" accessors="all" />

      <!--Preserve a property, it's backing field (if present), and getter method-->
      <property signature="System.Int32 PropertyName" accessors="get" />

      <!--Preserve a property, it's backing field (if present), and setter method-->
      <property signature="System.Int32 PropertyName" accessors="set" />

      <!--Preserve a property by name rather than signature-->
      <property name="PropertyName" />

      <!--Events-->

      <!--Preserve an event, it's backing field (if present), add, and remove methods-->
      <event signature="System.EventHandler EventName" />

      <!--Preserve an event by name rather than signature-->
      <event name="EventName" />

    </type>
  </assembly>
</linker>

次の例は、アセンブリ全体を宣言する方法を示しています。

<!--Preserve an entire assembly-->
  <assembly fullname="AssemblyName" preserve="all"/>

  <!--No "preserve" attribute and no types specified means preserve all-->
  <assembly fullname="AssemblyName"/>

  <!--Fully qualified assembly name-->
  <assembly fullname="AssemblyName, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
    <type fullname="AssemblyName.Foo" preserve="all"/>
  </assembly>

  <!--Force an assembly to be processed for roots but don’t explicitly preserve anything in particular. Useful when the assembly isn't referenced.-->
  <assembly fullname="AssemblyName" preserve="nothing"/>

この例では、ネスト型または汎用型のいずれかを保持する方法を示しています。

<!--Examples with generics-->
    <type fullname="AssemblyName.G`1">

      <!--Preserve a field with generics in the signature-->
      <field signature="System.Collections.Generic.List`1&lt;System.Int32&gt; FieldName" />

      <field signature="System.Collections.Generic.List`1&lt;T&gt; FieldName" />

      <!--Preserve a method with generics in the signature-->
      <method signature="System.Void MethodName(System.Collections.Generic.List`1&lt;System.Int32&gt;)" />

      <!--Preserve an event with generics in the signature-->
      <event signature="System.EventHandler`1&lt;System.EventArgs&gt; EventName" />

    </type>

    <!--Preserve a nested type-->
    <type fullname="AssemblyName.H/Nested" preserve="all"/>

    <!--Preserve all fields of a type if the type is used.  If the type isn't used, it will be removed-->
    <type fullname="AssemblyName.I" preserve="fields" required="0"/>

    <!--Preserve all methods of a type if the type is used. If the type isn't used, it will be removed-->
    <type fullname="AssemblyName.J" preserve="methods" required="0"/>

    <!--Preserve all types in a namespace-->
    <type fullname="AssemblyName.SomeNamespace*" />

    <!--Preserve all types with a common prefix in their name-->
    <type fullname="Prefix*" />

付加的な Assembly XML 属性

link.xml ファイルの <assembly> 要素には、3 つの特別な属性があり、アノテーションをより詳細に制御できます。

  • ignoreIfMissing: すべてのプレイヤービルド中に存在しないアセンブリの保存を宣言する必要がある場合に、この属性を使用します。
<linker>
  <assembly fullname="Foo" ignoreIfMissing="1">
    <type name="TypeName"/>
  </assembly>
</linker>
  • ignoreIfUnreferenced: そのアセンブリが別のアセンブリによって参照されている場合にのみ、アセンブリのエンティティを保持したい場合があります。アセンブリ内で少なくとも 1 つの型が参照されている場合にのみアセンブリ内のエンティティを保持したい場合は、この属性を使ってください。
<linker>
  <assembly fullname="Bar" ignoreIfUnreferenced="1">
    <type name="TypeName"/>
  </assembly>
</linker>
  • windowsruntime: Windows Runtime Metadata (.winmd) アセンブリの保持を定義する場合は、link.xml ファイルの <assembly> 要素に windowsruntime 属性を加える必要があります。
<linker>
  <assembly fullname="Windows" windowsruntime="true">
    <type name="TypeName"/>
 </assembly>
</linker>

依存関係アノテーション

依存関係のアノテーションは、さまざまなコード要素の間の依存関係を定義します。これらのアノテーションは、リフレクションのように Unity リンカーが静的に分析できないコードパターンを保持するのに便利です。また、これらのアノテーションは、ルート要素がコード要素を使用しないとき、コード要素を誤って保存しないようにします。Unity リンカーがコード要素を処理する方法を変更するには、以下の 2 つの方法を使用します。

  • Annotation attributes: これらの属性は、Unity リンカーが特定のコードパターン (注記された型から派生する型など) を保持すべきことを示します。
  • AlwaysLinkAssemblyAttribute: この属性を使用すると、プロジェクト内の他のアセンブリから参照されていない場合でも、Unity リンカーがアセンブリを処理する必要があることを示します。

アノテーションの属性

[ Preserve] 属性は、API が常に必要な場合に有用です。他の属性は、より一般的な保存に役立ちます。例えば、RequireImplementorsAttribute] を使ってインターフェースに注釈を付けると、特定のインターフェースを実装する全ての型を保存することができます。

特定のコーディングパターンを注釈するには、以下の属性を 1 つ以上使用します。

これらの属性をさまざまな方法で組み合わせることで、Unity リンカーがコードを保持する方法をより正確に制御することができます。

AlwaysLinkAssembly 属性

[assembly: UnityEngine.Scripting.AlwaysLinkAssembly] 属性を使用すると、ビルドに含まれる別のアセンブリによってそのアセンブリが参照されているかどうかにかかわらず、Unity リンカーに強制的にアセンブリを検索させます。AlwaysLinkAssembly 属性は、アセンブリに対してのみ適用できます。

この属性は、直接、アセンブリ内のコードを保持するわけではありません。そうでなく、この属性は、ルートをマークする規則をアセンブリに適用するように Unity リンカーに指示します。アセンブリのルートマーキング規則に一致するコード要素がない場合でも、Unity リンカーはビルドからアセンブリを削除します。

この属性は、[RuntimeInitializeOnLoadMethod] 属性を持つ 1 つ以上のメソッドを含むプリコンパイルされたアセンブリまたはパッケージアセンブリで使用します。ただし、プロジェクト内の任意のシーンで直接または間接的に使用される型が含まれていない場合があります。

アセンブリが [assembly: AlwaysLinkAssembly] を定義し、かつ、ビルドに含まれる別のアセンブリによって参照されている場合、属性はその結果に影響を与えません。

スクリプトの制限
Unity リンカー