Unity は、Unity で作成したアプリケーションがさまざまなハードウェア設定で動作するように、オープンソースの .NET プラットフォームを使用しています。.NET は様々な言語や API ライブラリをサポートします。
Unity には、Mono と IL2CPP (C++ への中間言語) という 2 つのスクリプティングバックエンドがあり、それぞれが異なるコンパイル手法を採用しています。
JIT ベースのスクリプティングバックエンドを使用する利点は、コンパイル時間が通常、AOT よりもはるかに短いことです。
デフォルトでは、Unity は Mono をサポートするプラットフォームでは、Mono バックエンドを使用します。アプリケーションのプレイヤーを構築するときに、使用するスクリプティングバックエンドを選択することができます。これをエディターで行うには、Edit > Project Settings > Player の順に移動し、Other Settings パネルを開き、 Scripting Backend ドロップダウンをクリックし、使用したいバックエンドを選択します。詳細については、スクリプティングバックエンド を参照してください。
アプリケーションをビルドする際、Unity はコンパイルした後に、プロジェクトのアセンブリ (.DLL) を検索して、使用されていないコードを検出して削除します。コードを削除するこのプロセスにより、ビルドの最終的なバイナリサイズは小さくなりますが、ビルド時間は長くなります。
Mono を使用する場合、コードストリッピングはデフォルトで無効になりますが、IL2CPP ではコードストリッピングを無効にすることはできません。Unity が削除するコードの量は、Managed Stripping Level プロパティを使用して制御できます。
このプロパティを変更するには、Edit > Project Settings > Player 、 Other Settings の順にパネルを開き、 Managed Stripping Level ドロップダウンをクリックし、ストリッピングレベルを選択します。
Managed Stripping Level を上げると、Unity はより多くのコードを削除するようになります。これは、特にリフレクションを使用したり、ランタイムにコードを生成する場合、アプリケーションが依存しているコードが削除されてしまうリスクを増加させます。
コードの特定の要素にアノテーションを使用すると、Unity によるコードのストリッピングを防ぐことができます。詳細については、マネージコードストリッピング を参照してください。
Unity は Boehm ガベージコレクターを使用しています。 を Mono と IL2CPP の両方のバックエンドに使用しています。Unity はデフォルトで インクリメンタル モードを使用します。Unity では インクリメンタル モードの使用を推奨していますが、インクリメンタル モードを無効にして “stop the world” ガベージコレクションを使用することもできます。
インクリメンタルモードと “stop the world” を切り替えるには、Edit > Project Settings > Player の順に移動し、Other Settings パネルを開き、Use incremental GC チェックボックスをクリックします。インクリメンタルモードでは、Unity のガベージコレクターは限られた時間だけ実行され、必ずしも 1 回のパスですべてのオブジェクトを収集するわけではありません。これにより、オブジェクトの収集にかかる時間が複数のフレームに分散され、スタッターと CPU スパイクが減少します。詳しくは、マネージメモリ を参照してください。
アプリケーションで、割り当て数や CPU スパイクの可能性を確認するには、Unity Profiler を使用してください。また、GarbageCollector API を使用して、プレイヤーのガベージコレクションを完全に無効にすることもできます。コレクターが無効になっているときは、過剰なメモリを割り当てないように注意してください。
Unity は多くのプラットフォームをサポートしており、プラットフォームによって異なるスクリプティングバックエンドを使用する場合があります。.NET システムライブラリが正しく動作するためには、プラットフォーム固有の実装が必要な場合があります。Unity は可能な限り多くの .NET エコシステムをサポートするように努めていますが、.NET システムライブラリの一部には、Unity が明示的にサポートしていない例外があります。
Unity のバージョン間での .NET システムライブラリのパフォーマンスや割り当ては保証されていません。一般に、Unity は .NET システムライブラリのパフォーマンスリグレッションを修正しません。
Unity は System.Drawing ライブラリをサポートしておらず、すべてのプラットフォームで動作することが保証されているわけではありません。
Mono スクリプトバックエンドが使用する JIT コンパイルでは、アプリケーションのランタイムに動的な C#/.NET 中間言語 (IL) コード生成を行うことができます。IL2CPP スクリプティングバックエンドが使用する AOT コンパイルは、動的なコード生成をサポートしません。
サードパーティのライブラリを使用する場合、この点を考慮することが重要です。なぜなら、JIT と AOT でコードパスが異なったり、動的に生成されたコードに依存するコードパスを使用する場合があるためです。ランタイムにコードを生成する方法の詳細については、Microsoft 社のModuleBuilder のドキュメントを参照してください。
Unity は複数の .NET API プロファイルをサポートしていますが、以下の理由から、すべての新規プロジェクトには、.NET Standard API Compatibility Level を使用する必要があります。
他のプロファイルは、例えば、古い既存のアプリケーションのサポートを提供する必要がある場合に便利です。Api Compatibility Level の設定を変更するには、Edit > Project Settings > Player に移動します。Other Settings のセクションで、Api Compatibility Level を希望の設定にします。
詳しくは、.NET プロファイルのサポート を参照してください。
サードパーティ製の .NET ライブラリは、さまざまな Unity の設定やプラットフォームで広範にテストされたものだけを使用するようにしてください。
サードパーティ製ライブラリの JIT コードパスと AOT コードパスの性能特性は大きく異なる場合があります。AOT は一般的に起動時間を短縮することができ、大きなアプリケーションに適していますが、コンパイルされたコードを格納するためにバイナリファイルのサイズが大きくなります。また、AOT は開発中のビルドに時間がかかります。
JIT は、実行中のプラットフォームに基づいてランタイムに調整するため、アプリケーションの起動時間が長くなる可能性がありますが、実行パフォーマンスを向上させることができます。そのため、エディターとターゲットプラットフォームの両方でアプリケーションのプロファイルを作成する必要があります。詳細については、プロファイラー概要 のドキュメントを参照してください。
すべてのターゲットプラットフォームの .NET システムライブラリの使用状況をプロファイリングする必要があります。なぜなら、使用するスクリプトバックエンド、.NET バージョン、プロファイルによってパフォーマンスの特性が異なる可能性があるためです。
サードパーティ製のライブラリを検討する際には、以下の点を考慮してください。
Mono および IL2CPP は、すべての C# リフレクション (System.Reflection) オブジェクトを内部的にキャッシュしますが、設計上、Unity はそれらをガベージコレクションしません。この動作の結果、ガベージコレクターはアプリケーションの生存期間中にキャッシュされた C# リフレクションオブジェクトを継続的にスキャンすることになり、ガベージコレクターの不必要で潜在的に大きなオーバーヘッドの原因になります。
ガベージコレクターのオーバーヘッドを最小限にするために、アプリケーションで Assembly.GetTypes やType.GetMethods() などのメソッドを避けてください。これらのメソッドは、ランタイムに多くの C# リフレクションオブジェクトを作成します。代わりに、必要なデータのためにエディターでアセンブリをスキャンし、ランタイムに使用するためにそれをシリアライズおよび/またはコード化する必要があります。
UnityEngine.Object は、ネイティブ C++ 対応オブジェクトにリンクされているため、Unity の特殊なタイプの C# オブジェクトです。例えば、Camera コンポーネントを使用する場合、Unity は、オブジェクトの状態を C# オブジェクト自体ではなく、オブジェクトのネイティブ C++ 対応オブジェクトに保存します。
Unity は現在、UnityEngine.Object のインスタンスでの C# WeakReference クラスの使用をサポートしていません。このため、ロードされたアセットを参照するために WeakReference を使用するべきではありません。WeakReference クラスの詳細については、Microsoft の WeakReference のドキュメント を参照してください。
Object.Destroy や Object.DestroyImmediate などのメソッドを使ってUnityEngine.Object 派生オブジェクトを破壊すると、Unity はネイティブの対応するオブジェクトを破棄 (アンロード) します。ガベージコレクターがメモリを管理しているため、明示的な呼び出しで C# オブジェクトを破棄することはできません。マネージオブジェクトへの参照がなくなると、ガベージコレクターがオブジェクトを回収して破棄します。
If your application tries to access a destroyed UnityEngine.Object again, Unity recreates the native counterpart object for most types. Two exceptions to this recreation behavior are MonoBehaviours and ScriptableObjects: Unity never reloads them once they have been destroyed.
MonoBehaviour と ScriptableObject は、等式 (==) と不等式 (!=) の演算子をオーバーライドします。つまり、破棄された MonoBehaviour や ScriptableObject と null を比較すると、マネージオブジェクトがまだ存在し、ガベージコレクションされていなければ、演算子は true を返します。
その理由は、??
と ?
の演算子はオーバーロードできないため、UnityEngine.Object から派生したオブジェクトとの互換性はありません。マネージオブジェクトがまだ存在するときに、破棄された MonoBehaviour や ScriptableObject に対してこれらの演算子を使用しても、等式演算子や不等式演算子と同じ結果は得られません。
Unity provides built-in Await support with specifically optimized async/await compatible types. In most cases, you should prefer using Awaitable over .net Task when creating an async method.
Most of the Unity API isn’t thread safe and therefore, you should only use Unity APIs from the main thread. Unity overwrites the default SynchronizationContext with a custom UnitySynchronizationContext and runs all the .net Tasks continuations on the main thread in both Edit and Play modes by default. If you explicitly want to run some code in a background thread (for heavy compute operations), you can do that by awaiting Awaitable.BackgroundThreadAsync as in the following example:
private async Awaitable<float> DoSomeHeavyComputationInBackgroundAsync(bool continueOnMainThread = true)
{
await Awaitable.BackgroundThreadAsync();
// do some heavy math here
float result = 42;
// by default, switch back to main thread:
if(continueOnMainThread){
await Awaitable.MainThreadAsync();
}
return result;
}
public async Awaitable Start()
{
var computationResult = await DoSomeHeavyComputationInBackgroundAsync();
}
Unity doesn’t automatically stop code runnining in the background when you exit Play mode. To cancel a background operation on exiting playmode, use Application.exitCancellationToken.
開発ビルドにおいて、マルチスレッドコードで Unity の API を使用しようとすると、以下のようなエラーメッセージが表示されます。
UnityException: Internal_CreateGameObject can only be called from the main thread. \
Constructors and field initializers will be executed from the loading thread when loading a scene. \
Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.
For performance reasons, Unity doesn’t perform checks for multithreaded behavior in non-development builds and doesn’t display this error in live builds. This means that while Unity doesn’t prevent execution of multithreaded code on live builds, random crashes and other unpredictable errors are likely if you do use multiple threads. Thus when writing code potentially running in a background thread, you need to be extra-careful at the APIs you are calling.
このため、独自のマルチスレッドを使用せず、代わりに Unity の ジョブシステム を使用する必要があります。ジョブシステムは複数のスレッドを安全に使用してジョブを並行して実行し、マルチスレッドのパフォーマンス上の利点を実現します。詳細については、ジョブシステムの概要 を参照してください。
Using Awaitable.BackgroundThreadAsync and getting back on main thread with Awaitable.MainThreadAsync is suitable for relatively long running background operations (e.g. longer than a frame), to avoid blocking the main game loop, but is not suitable for taking advantage of multi-core CPUs within a single frame.