Unity スクリプトの基盤となるテクノロジである C# と.NETは、2002 年に最初にリリースMicrosoft以降も更新プログラムを受け取り続けています。 しかし、Unity 開発者は、C# 言語と .NET Framework に追加された新機能の安定した流れを認識していない可能性があります。Unity 2017.1 より前は、Unity は .NET 3.5 の同等のスクリプト ランタイムを使用しており、何年もの更新プログラムが不足しているためです。
Unity 2017.1 のリリースでは、Unity では、.NET 4.6、C# 6.0 互換バージョンにアップグレードされた、そのスクリプト ランタイムの試験的なバージョンが導入されました。 Unity 2018.1 では、.NET 4.x の同等のランタイムは実験用と見なされなくなりましたが、以前の .NET 3.5 同等のランタイムはレガシ バージョンと見なされるようになりました。 Unity 2018.3 のリリースでは、アップグレードされたスクリプト ランタイムを既定の選択にし、さらに C# 7 に更新する予定です。 このロードマップの詳細と最新の更新情報については、Unity の ブログ投稿 を参照するか、 実験用スクリプト プレビュー フォーラムを参照してください。 それまでの間、.NET 4.x スクリプト ランタイムで現在使用できる新機能の詳細については、以下のセクションを参照してください。
前提条件
- Unity 2022.2 以降 (2022.1.7 を推奨)
- Visual Studio 2019
Unity で .NET 4.x スクリプト ランタイムを有効にする
.NET 4.x スクリプト ランタイムを有効にするには、次の手順を実行します。
Edit > Project Settings > Player > Other Settings を選択して、Unity のインスペクターで PlayerSettings を開きます。
[構成] 見出しの下の [Api 互換性レベル] ドロップダウンをクリックし、.NET Framework を選択します。 Unity を再起動するように求められます。
.NET 4.x プロファイルと .NET Standard 2.1 プロファイルの選択
.NET 4.x の同等のスクリプト ランタイムに切り替えたら、PlayerSettings のドロップダウン メニュー ([ Project>) を使用して API 互換性レベルを指定できます。 次の 2 つのオプションがあります。
.NET Standard 2.1。 このプロファイルは、.NET Foundation によって発行された .NET Standard 2.1 プロファイルと一致します。 Unity では、新しいプロジェクト.NET Standard 2.1 をお勧めします。 .NET 4.x より小さいため、サイズに制約のあるプラットフォームに適しています。 さらに、Unity は、Unity がサポートするすべてのプラットフォームでこのプロファイルのサポートに取り組んでいます。
.NET Framework。 このプロファイルは、最新の .NET 4 API へのアクセスを提供します。 .NET Framework クラス ライブラリで使用可能なすべてのコードが含まれており、Standard 2.1 プロファイル.NETもサポートしています。 .NET Standard 2.0 プロファイルに含まれていない API の一部がプロジェクトに必要な場合は、.NET 4.x プロファイルを使用します。 ただし、この API の一部の部分は、Unity のすべてのプラットフォームでサポートされていない場合があります。
これらのオプションの詳細については、Unity の ブログ投稿を参照してください。
.NET 4.x API 互換性レベルを使用する場合のアセンブリ参照の追加
[API 互換性レベル] ドロップダウンで .NET Standard 2.1 設定を使用すると、API プロファイル内のすべてのアセンブリが参照され、使用できます。 ただし、より大きな.NET 4.x プロファイルを使用する場合、Unity に付属しているアセンブリの一部は既定では参照されません。 これらの API を使用するには、アセンブリ参照を手動で追加する必要があります。 Unity が付属しているアセンブリは、Unity エディターインストールの MonoBleedingEdge/lib/mono ディレクトリで確認できます。
たとえば、.NET 4.x プロファイルを使用していて、HttpClientを使用する場合は、System.Net.Http.dllのアセンブリ参照を追加する必要があります。 それがなければ、コンパイラはアセンブリ参照が見つからないと不平を言います。
Unity プロジェクトを開くたびに、Visual Studio は .csproj ファイルと .sln ファイルを再生成します。 その結果、プロジェクトを再度開くと失われるため、アセンブリ参照をVisual Studioに直接追加することはできません。 代わりに、 csc.rsp という名前の特別なテキスト ファイルを使用する必要があります。
Unity プロジェクトのルート Assets ディレクトリに csc.rsp という名前の新しいテキスト ファイルを作成します。
空のテキスト ファイルの最初の行に「
-r:System.Net.Http.dll」と入力し、ファイルを保存します。 "System.Net.Http.dll" は、参照が不足している可能性のある任意の含まれているアセンブリに置き換えることができます。Unity エディターを再起動します。
.NET互換性を活用する
新しい C# 構文と言語機能に加えて、.NET 4.x スクリプト ランタイムを使用すると、Unity ユーザーは従来の .NET 3.5 スクリプト ランタイムと互換性のない.NET パッケージの膨大なライブラリにアクセスできます。
NuGet から Unity プロジェクトにパッケージを追加する
NuGet は、.NETのパッケージ マネージャーです。 NuGet はVisual Studioに統合されています。 ただし、Unity プロジェクトでは、プロジェクトを Unity で開くと、そのVisual Studio プロジェクト ファイルが再生成され、必要な構成が元に戻されるため、NuGet パッケージを追加する特別なプロセスが必要です。 NuGet から Unity プロジェクトにパッケージを追加するには:
NuGet を参照して、追加する互換性のあるパッケージを見つけます (.NET Standard 2.0 または .NET 4.x)。 この例では、JSON を操作するための一般的なパッケージである Json.NET を .NET Standard 2.0 プロジェクトに追加する方法を示します。
[ ダウンロード ] ボタンをクリックします。
ダウンロードしたファイルを見つけて、拡張子を .nupkg から .zipに変更します。
zip ファイル内で lib/netstandard2.0 ディレクトリに移動し、 Newtonsoft.Json.dll ファイルをコピーします。
Unity プロジェクトのルート Assets フォルダーに、 Plugins という名前の新しいフォルダーを作成します。 プラグインは Unity の特別なフォルダー名です。 詳細については、 Unity のドキュメントを参照してください。
Newtonsoft.Json.dll ファイルを Unity プロジェクトの Plugins ディレクトリに貼り付けます。
Unity プロジェクトの Assets ディレクトリに link.xml という名前のファイルを作成し、次の XML を追加します。IL2CPP プラットフォームにエクスポートするときに、Unity のバイトコード除去プロセスで必要なデータが削除されないようにします。 この手順はこのライブラリに固有ですが、リフレクションを使用する他のライブラリでも同様の方法で問題が発生する可能性があります。 詳細については、この記事の Unity のドキュメント を参照してください。
<linker> <assembly fullname="System.Core"> <type fullname="System.Linq.Expressions.Interpreter.LightLambda" preserve="all" /> </assembly> </linker>
すべてが整ったので、Json.NET パッケージを使用できるようになりました。
using Newtonsoft.Json;
using UnityEngine;
public class JSONTest : MonoBehaviour
{
class Enemy
{
public string Name { get; set; }
public int AttackDamage { get; set; }
public int MaxHealth { get; set; }
}
private void Start()
{
string json = @"{
'Name': 'Ninja',
'AttackDamage': '40'
}";
var enemy = JsonConvert.DeserializeObject<Enemy>(json);
Debug.Log($"{enemy.Name} deals {enemy.AttackDamage} damage.");
// Output:
// Ninja deals 40 damage.
}
}
これは、依存関係のないライブラリを使用する簡単な例です。 NuGet パッケージが他の NuGet パッケージに依存している場合は、これらの依存関係を手動でダウンロードし、同じ方法でプロジェクトに追加する必要があります。
新しい構文と言語機能
更新されたスクリプト ランタイムを使用すると、Unity 開発者は C# 8 と多数の新しい言語機能と構文にアクセスできます。
自動プロパティ初期化子
Unity の .NET 3.5 スクリプト ランタイムでは、自動プロパティ構文を使用すると、初期化されていないプロパティをすばやく簡単に定義できますが、初期化はスクリプトの他の場所で行う必要があります。 .NET 4.x ランタイムを使用すると、同じ行で自動プロパティを初期化できるようになりました。
// .NET 3.5
public int Health { get; set; } // Health has to be initialized somewhere else, like Start()
// .NET 4.x
public int Health { get; set; } = 100;
文字列補間
以前の.NET 3.5 ランタイムでは、文字列連結にはぎこちない構文が必要でした。 .NET 4.x ランタイムでは、$文字列補間機能を使用して、より直接的で読みやすい構文で式を文字列に挿入できるようになりました。
// .NET 3.5
Debug.Log(String.Format("Player health: {0}", Health)); // or
Debug.Log("Player health: " + Health);
// .NET 4.x
Debug.Log($"Player health: {Health}");
式本体メンバー
.NET 4.x ランタイムで使用できる新しい C# 構文では、ラムダ式で関数の本体を置き換えて、より簡潔にすることができます。
// .NET 3.5
private int TakeDamage(int amount)
{
return Health -= amount;
}
// .NET 4.x
private int TakeDamage(int amount) => Health -= amount;
読み取り専用プロパティ内でも、式形式メンバーを使用できます。
// .NET 4.x
public string PlayerHealthUiText => $"Player health: {Health}";
タスク ベースの非同期パターン (TAP)
非同期プログラミング を使用すると、アプリケーションが応答しなくなることなく、時間のかかる操作を実行できます。 また、この機能を使用すると、時間のかかる操作が完了するのを待ってから、これらの操作の結果に依存するコードを続行することもできます。 たとえば、ファイルの読み込みやネットワーク操作の完了を待つことができます。
Unity では、通常、非同期プログラミングは コルーチンを使用して実行されます。 ただし、C# 5 以降、.NET開発での非同期プログラミングの推奨される方法は、System.Threading.Task でasyncキーワードとawaitキーワードを使用するタスクベースの非同期パターン (TAP) です。 要約すると、 async 関数では、アプリケーションの残りの部分の更新をブロックすることなく、タスクの完了を await できます。
// Unity coroutine
using UnityEngine;
public class UnityCoroutineExample : MonoBehaviour
{
private void Start()
{
StartCoroutine(WaitOneSecond());
DoMoreStuff(); // This executes without waiting for WaitOneSecond
}
private IEnumerator WaitOneSecond()
{
yield return new WaitForSeconds(1.0f);
Debug.Log("Finished waiting.");
}
}
// .NET 4.x async-await
using UnityEngine;
using System.Threading.Tasks;
public class AsyncAwaitExample : MonoBehaviour
{
private async void Start()
{
Debug.Log("Wait.");
await WaitOneSecondAsync();
DoMoreStuff(); // Will not execute until WaitOneSecond has completed
}
private async Task WaitOneSecondAsync()
{
await Task.Delay(TimeSpan.FromSeconds(1));
Debug.Log("Finished waiting.");
}
}
TAP は複雑なテーマであり、開発者が考慮する必要がある Unity 固有の微妙な違いがあります。 その結果、TAP は Unity のコルーチンの普遍的な代替物ではありません。ただし、使用する別のツールです。 この機能の範囲はこの記事を超えていますが、いくつかの一般的なベスト プラクティスとヒントを以下に示します。
Unity 用 TAP の入門リファレンス
Unity で TAP を使い始める際に役立つヒントを次に示します。
- 待機対象の非同期関数には、戻り値の型が
TaskまたはTask<TResult>必要があります。 - タスクを返す非同期関数には、名前にサフィックス "Async" が追加されている必要があります。 "Async" サフィックスは、関数を常に待機する必要があることを示すのに役立ちます。
- 従来の同期コードから非同期関数を起動する関数には、
async void戻り値の型のみを使用します。 このような関数自体は await の対象にできないため、名前に "Async" 接尾辞を付けるべきではありません。 - Unity では UnitySynchronizationContext を使用して、非同期関数が既定でメイン スレッドで実行されるようにします。 Unity API は、メイン スレッドの外部からはアクセスできません。
-
Task.RunやTask.ConfigureAwait(false)などのメソッドを使用して、バックグラウンド スレッドでタスクを実行できます。 この手法は、高コストの操作をメイン スレッドからオフロードしてパフォーマンスを向上させる場合に役立ちます。 ただし、バックグラウンド スレッドを使用すると、 競合状態など、デバッグが困難な問題につながる可能性があります。 - Unity API はメイン スレッドの外部からはアクセスできません。
- スレッドを使用するタスクは、Unity WebGL ビルドではサポートされていません。
コルーチンと TAP の違い
コルーチンと TAP/async-await には、いくつかの重要な違いがあります。
- コルーチンは値を返すことはできませんが、
Task<TResult>できます。 - try-catch ステートメントに
yieldを配置することはできません。コルーチンでのエラー処理が困難になります。 ただし、try-catch は TAP で動作します。 - Unity のコルーチン機能は、MonoBehaviour から派生しないクラスでは使用できません。 TAP は、このようなクラスでの非同期プログラミングに適しています。
- この時点で、Unity では、コルーチンの卸売代替として TAP は推奨されません。 プロファイリングは、特定のプロジェクトの 1 つのアプローチと他方のアプローチの特定の結果を知る唯一の方法です。
nameof 演算子
nameof演算子は、変数、型、またはメンバーの文字列名を取得します。
nameofが便利な場合は、エラーをログに記録し、列挙型の文字列名を取得します。
// Get the string name of an enum:
enum Difficulty {Easy, Medium, Hard};
private void Start()
{
Debug.Log(nameof(Difficulty.Easy));
RecordHighScore("John");
// Output:
// Easy
// playerName
}
// Validate parameter:
private void RecordHighScore(string playerName)
{
Debug.Log(nameof(playerName));
if (playerName == null) throw new ArgumentNullException(nameof(playerName));
}
呼び出し元情報の属性
呼び出し元情報属性は、 メソッドの呼び出し元に関する情報を提供します。 呼び出し元情報属性で使用する各パラメーターに既定値を指定する必要があります。
private void Start ()
{
ShowCallerInfo("Something happened.");
}
public void ShowCallerInfo(string message,
[System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
[System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
Debug.Log($"message: {message}");
Debug.Log($"member name: {memberName}");
Debug.Log($"source file path: {sourceFilePath}");
Debug.Log($"source line number: {sourceLineNumber}");
}
// Output:
// Something happened
// member name: Start
// source file path: D:\Documents\unity-scripting-upgrade\Unity Project\Assets\CallerInfoTest.cs
// source line number: 10
static の使用
static を使用 すると、クラス名を入力せずに静的関数を使用できます。 static を使用すると、同じクラスからいくつかの静的関数を使用する必要がある場合に、領域と時間を節約できます。
// .NET 3.5
using UnityEngine;
public class Example : MonoBehaviour
{
private void Start ()
{
Debug.Log(Mathf.RoundToInt(Mathf.PI));
// Output:
// 3
}
}
// .NET 4.x
using UnityEngine;
using static UnityEngine.Mathf;
public class UsingStaticExample: MonoBehaviour
{
private void Start ()
{
Debug.Log(RoundToInt(PI));
// Output:
// 3
}
}
IL2CPP に関する考慮事項
iOS などのプラットフォームにゲームをエクスポートする場合、Unity は IL2CPP エンジンを使用して IL を C++ コードに "トランスパイル" し、ターゲット プラットフォームのネイティブ コンパイラを使用してコンパイルします。 このシナリオでは、リフレクションの一部やdynamic キーワードの使用など、サポートされていない.NET機能がいくつかあります。 独自のコードでこれらの機能の使用を制御できますが、Unity と IL2CPP を念頭に置いて記述されていないサード パーティ製 DLL と SDK の使用で問題が発生する可能性があります。 この記事の詳細については、Unity のサイトの スクリプトの制限 に関するドキュメントを参照してください。
さらに、上記の Json.NET 例で説明したように、Unity は IL2CPP エクスポート プロセス中に未使用のコードを取り除こうとします。 通常、このプロセスは問題ではありませんが、Reflection を使用するライブラリでは、実行時に呼び出されるプロパティまたはメソッドが誤って取り除かれる可能性があります。これはエクスポート時に特定できません。 これらの問題を解決するには、削除プロセスを実行しないアセンブリと名前空間の一覧を含む link.xmlファイルを プロジェクトに追加します。 詳細については、 バイトコードの削除に関する Unity のドキュメントを参照してください。
.NET 4.x サンプル Unity Project
このサンプルには、4.x .NET機能の例が含まれています。 プロジェクトをダウンロードするか、GitHubでソース コードを表示できます。