Unityがサポートしているすべてのプラットフォームで、同じ API が使用できるように努力しています。ですが、いくつかのプラットフォームでは、固有の制限があります。これらの制限を理解して、クロスプラットフォームのコードが書けるようになるために、以下に各プラットフォームに適用される制限とスクリプティングバックエンドについて説明します。
プラットフォーム | スクリプティングバックエンド | 制限 |
---|---|---|
Standalone | Mono | None |
WebGL | IL2CPP | 事前コンパイル、スレッド不可 |
iOS | IL2CPP | 事前コンパイル |
Android | Mono | None |
Android | IL2CPP | 事前コンパイル |
Samsung TV | Mono | 事前コンパイル |
Tizen | Mono | 事前コンパイル |
XBox One | Mono | 事前コンパイル |
XBox One | IL2CPP | 事前コンパイル |
WiiU | Mono | 事前コンパイル |
PS Vita | Mono | 事前コンパイル |
PS Vita | IL2CPP | 事前コンパイル |
PS4 | Mono | 事前コンパイル |
PS4 | IL2CPP | 事前コンパイル |
Windows Store | .NET | .NET Core クラスライブラリサブセットを使用 |
Windows Store | IL2CPP | 事前コンパイル |
プラットフォームの中には、実行時のコード生成ができないものもあります。そのため、そのようなデバイスで実行時 (Just-In-Time、JIT) コンパイルをおこなうと失敗します。代わりに、すべてのコードを事前 (Ahead-Of-Time、AOT) にコンパイルする必要があります。しばしば、この差異はあまり重要でないこともあります。しかし、ある特定の場合では、AOT コンパイルを必要とするプラットフォームには、追加の配慮が必要なことがあります。
事前 (AOT) コンパイルが必要なプラットフォームには、System.Reflection.Emit 名前空間のメソッドを実装することができません。それ以外の System.Reflection は、使用可能なことに気を付けてください。ただし、リフレクションを通して使用されるコードが実行時に必要であることをコンパイラーが検知する必要があります。
事前 (AOT) コンパイルが必要なプラットフォームでは、リフレクションの使用が原因でシリアライズと非シリアライズで問題が発生することがあります。シリアライズと非シリアライズの一部として、型とメソッドをリフレクション経由でのみ使用できる場合、事前コンパイラーは型とメソッドのためにコードが生成される必要があることを検知することができません。
ジェネリックメソッドでは、開発者が書いたコードを拡張して、デバイスで実際に実行できるようなコードにするコンパイラーによる追加作業が必要です。例えば、int と doubleをもつListでは、異なるコードが必要です。仮想メソッドが存在する場合、ビヘイビアはコンパイル時よりもはむしろ実行時に決定されます。そのため、ソースコードから明白にされないコードがある場合、コンパイラーによって、簡単に適所で実行時コード生成が要求されます。
以下のようなコードがあるとします。実行時 (JIT) コンパイルのプラットフォームでは、意図した通りに動きます (「Message value: Zero」がコンソールに 1 回出力されます)。
using UnityEngine;
using System;
public class AOTProblemExample : MonoBehaviour, IReceiver {
public enum AnyEnum {
Zero,
One,
}
void Start() {
// 微細なトリガ: AOT の問題をトリガするには
//マネージャータイプは Manager でなく IManager でなくてはならない。
IManager manager = new Manager();
manager.SendMessage(this, AnyEnum.Zero);
}
public void OnMessage<T>(T value) {
Debug.LogFormat("Message value: {0}", value);
}
}
public class Manager : IManager {
public void SendMessage<T>(IReceiver target, T value) {
target.OnMessage(value);
}
}
public interface IReceiver {
void OnMessage<T>(T value);
}
public interface IManager {
void SendMessage<T>(IReceiver target, T value);
}
このコードを IL2CPP スクリプティングバックエンドの事前 (AOT) コンパイルのプラットフォームで実行すると、以下の例外が発生します。
ExecutionEngineException: Attempting to call method 'AOTProblemExample::OnMessage<AOTProblemExample+AnyEnum>' for which no ahead of time (AOT) code was generated.
at Manager.SendMessage[T] (IReceiver target, .T value) [0x00000] in <filename unknown>:0
at AOTProblemExample.Start () [0x00000] in <filename unknown>:0
同様に、Mono スクリプティングバックエンドでは、以下のような類似の例外が発生します。
ExecutionEngineException: Attempting to JIT compile method 'Manager:SendMessage<AOTProblemExample/AnyEnum> (IReceiver,AOTProblemExample/AnyEnum)' while running with --aot-only.
at AOTProblemExample.Start () [0x00000] in <filename unknown>:0
事前 (AOT) コンパイラーは、AnyEnum の T を持つジェネリックメソッド OnMessage のコードを生成する必要があることを察知しません。そのため、このメソッドを飛ばしてそのまま継続します。そのメソッドが呼び出されると、ランタイムコンパイラーは実行できる適当なコードを見つけることができず、このエラーメッセージを出力して失敗します。
このような AOT 問題を避けるために、しばしばコンパイラーを強制して適当なコードを生成させることができます。そのためには、以下のようなコードを AOTProblemExample クラスに加えます。
public void UsedOnlyForAOTCodeGeneration() {
// IL2CPP には以下のラインのみ必要です
OnMessage(AnyEnum.Zero);
// Mono はこのラインも必要です。 IManager インターフェースでなく、
// 直接 Manager で呼び出していることに注意してください。
new Manager().SendMessage(null, AnyEnum.Zero);
// 例外も加えます。そうすれば、このメソッドが呼び出されれば、確実に検知できます。
throw new InvalidOperationException("This method is used for AOT code generation only. Do not call it at runtime.");
}
コンパイラーが AnyEnum の T を持つジェネリックメソッド OnMessage を明白に呼び出すと、ランタイムに適切なコードが生成され実行されます。UsedOnlyForAOTCodeGeneration メソッドは呼び出される必要はありません。ただ、コンパイラーが見るように存在しさえすればよいのです。
プラットフォームの中には、スレッドの使用をサポートしないものもあります。そのような場合、System.Threading 名前空間を使用するすべてのコードは、実行時に失敗します。また、.NET クラスのライブラリには、暗にスレッドに依存しているものもあります。よく使われる例は System.Timers.Timer クラスで、スレッドのサポートに依存しています。