Layouts
Layout は、Input System が Input Device と Input Control を理解するメカニズムの中核を成すものです。各Layout は、Input Control の具体的な構造を表します。Input System では、Device の説明をLayout と照合することで、適切な Device タイプが作成され、入力データが正しく解釈されるようになります。
ノート: Layout は、Input System の高度な機能であり、大部分は内部で使用されます。Layout システムの知識が主に役立つのは、カスタムの Device をサポートする必要がある場合や、既存の Device の動作を変更する場合です。
Layout は、入力のメモリ形式と、そのメモリのデータを読み書きするためにビルドする Input Control の説明です。
Input System には、一般的な Control タイプと一般的な Device に対応する幅広いLayout のセットが付属しています。その他の Device タイプについては、Device のインターフェースから報告される Device の説明に基づいて、Input System で自動的に Layout が生成されます。
現在、認識されている Layout のセットは、Input Debugger から参照できます。
Layout には、主に以下の 2 つの機能があります。
- 入力データを格納する特定のメモリレイアウトを記述する。
- データを操作する Control に名前、構造体、意味を割り当てる。
Layout の対象は、Device 上の Control (Stick
など)、または Device 自体 (InputDevice
に基づくもの) です。
Input System は、必要時にのみ (通常は、新しい Device の作成時に) Layout をロードします。手動でLayout をロードする場合は、InputSystem.LoadLayout
を使用できます。これによって返される InputControlLayout
インスタンスには、すべてがまとめられた最終的な Layout 構造体が含まれます (つまり、基本 Layout から継承された情報や、Layout のオーバーライドを反映した情報がすべて含まれます)。
新しいLayout を登録するには、InputSystem.RegisterLayout
を使用します。
Layout 形式
新しい Layout を加えるには、以下の 3 つの方法のいずれかを使用できます。
- C# の構造体とクラスで表現する。
- JSON 形式で記述する。
- Layout ビルダーを使用してランタイムに動的にビルドする。
型によるLayout の定義
最も基本的な形として、以下のクラスから派生する C# クラスによって Layout を表現できます。
- Control レイアウトの場合は
InputControl
。 - Device レイアウトの場合は
InputDevice
。
// ここでは、InputControlLayout 属性は厳密には必要ありません。
// ただし、これを使用すると、追加プロパティ (Layout のカスタム
// 表示名など) を設定できます。
[InputControlLayout]
public class MyDevice : InputDevice
{
public AxisControl axis { get; private set; }
public ButtonControl button { get; private set; }
protected override void FinishSetup(InputDeviceBuilder builder)
{
base.FinishSetup(builder);
axis = builder.GetControl<AxisControl>("axis");
button = builder.GetControl<ButtonControl>("button");
}
}
その後、InputSystem.RegisterLayout
を使用してLayout を登録することができます。これは、Control レイアウトと Device レイアウトの両方で同様に機能します。
// ノート: これは通常、InitializeOnLoad/RuntimeInitializeOnLoad
// コードから実行する必要があります。
InputSystem.RegisterLayout<MyDevice>();
Layout がインスタンス化されるときは、型に直接定義されているフィールドとプロパティがそれぞれ調べられ、必要に応じて 1 つ以上の Control 要素 に変換されます。
- フィールドまたはプロパティに
InputControlAttribute
で注釈が付けられている場合は、属性のプロパティが Control 要素に適用されます。この場合、いくつかの特別なデフォルト値が適用されることがあります。 - フィールドまたはプロパティが
IInputStateTypeInfo
を実装する構造体である場合、そのフィールドは埋め込みの 状態構造体 と見なされ、フィールドまたはプロパティが再帰的に処理されて Control が収集されます。 - それ以外の場合、フィールドまたはプロパティの型が
InputControl
に基づいている場合は、Control 要素 が加えられます。これは、ケース 1 で説明した、メンバーがInputControlAttribute
で注釈が付けられている場合と同様です。
状態 (state) 構造体の使用
新しい Input Device のサポートを実装するときには、通常、Input System がその Device の入力として受け取るデータ形式が既に決まっています。データ形式のサポートを加えるには、InputControlAttribute
で注釈を付けた C# 構造体として記述することが、最も簡単な方法です。
public struct MyDeviceState : IInputStateTypeInfo
{
public FourCC format => new FourCC('M', 'D', 'E', 'V');
[InputControl(name = "button1", layout = "Button", bit = 0)]
[InputControl(name = "button2", layout = "Button", bit = 1)]
[InputControl(name = "dpad", layout = "Dpad", bit = 2, sizeInBits = 4)]
[InputControl(name = "dpad/up", bit = 2)]
[InputControl(name = "dpad/down", bit = 3)]
[InputControl(name = "dpad/left", bit = 4)]
[InputControl(name = "dpad/right", bit = 5)]
public int buttons;
[InputControl(layout = "Stick")]
public Vector2 stick;
[InputControl(layout = "Axis")] // byte から float に自動変換されます。
public byte trigger;
}
// 作成した state 構造体を使用するように Device に指示する必要があります。
[InputControlLayout(stateType = typeof(MyDeviceState)]
public class MyDevice : InputDevice
{
}
JSON によるLayout の定義
同じ情報を含む JSON 文字列からLayout を作成することもできます。これは通常、コードとは別にLayout 情報を保存し、転送することができるようにする場合に役立ちます。例えば、アプリケーションの新しいビルドを作成することなく、新しい Device のサポートを動的に加えることが必要な場合です。Layout と JSON 形式の間の変換は、InputControlLayout.ToJson()
および InputControlLayout.FromJson()
を使用して行うことができます。
上記と同じLayout を JSON 形式で記述すると、以下のようになります。
{
"name": "MyDevice",
"format": "MDEV",
"controls": [
{
"name": "button1",
"layout": "Button",
"offset": 0,
"bit": 0,
},
{
"name": "button2",
"layout": "Button",
"offset": 0,
"bit": 1,
},
{
"name": "dpad",
"layout": "Dpad",
"offset": 0,
"bit": 2,
"sizeInBits": 4,
},
{
"name": "dpad/up",
"offset": -1,
"bit": 2,
},
{
"name": "dpad/down",
"offset": -1,
"bit": 3,
},
{
"name": "dpad/left",
"offset": -1,
"bit": 4,
},
{
"name": "dpad/right",
"offset": -1,
"bit": 5,
},
{
"name": "stick",
"layout": "Stick",
"offset": 4,
"format": "VEC2",
},
{
"name": "trigger",
"layout": "Axis",
"offset": 12,
"format": "BYTE",
}
]
}
Layout の生成
最後に、Input System では、コード内で動的にLayout をビルドすることもできます。これは、各 Device の記述情報を提供する HID のような Device インターフェースを使用する場合に便利です。
コードで動的にLayout をビルドするには、InputControlLayout.Builder
API を使用します。
前の例と同じLayout をプログラムで構築すると、以下のようになります。
var builder = new InputControlLayout.Builder()
.WithName("MyDevice")
.WithFormat("MDEV");
builder.AddControl("button1")
.WithLayout("Button")
.WithByteOffset(0)
.WithBitOffset(0);
builder.AddControl("button2")
.WithLayout("Button")
.WithByteOffset(0)
.WithBitOffset(1);
builder.AddControl("dpad")
.WithLayout("Dpad")
.WithByteOffset(0)
.WithBitOffset(2)
.WithSizeInBits(4);
builder.AddControl("dpad/up")
.WithByteOffset(-1)
.WithBitOffset(2);
builder.AddControl("dpad/down")
.WithByteOffset(-1)
.WithBitOffset(3);
builder.AddControl("dpad/left")
.WithByteOffset(-1)
.WithBitOffset(4);
builder.AddControl("dpad/right")
.WithByteOffset(-1)
.WithBitOffset(5);
builder.AddControl("stick")
.WithLayout("Stick")
.WithByteOffset(4)
.WithFormat("VEC2");
builder.AddControl("trigger")
.WithLayout("Axis")
.WithByteOffset(12)
.WithFormat("BYTE");
var layout = builder.Build();
Layout の継承
既存の Layout から別の Layout を派生させることができます。このプロセスでは、基本 Layout に含まれている情報を基盤として、派生する Layout の情報をその上に結合します。
- 型として定義される Layout では、基本型の Layout (存在する場合) が基本 Layout になります。
- JSON で定義される Layout では、ルートノードの
extends
プロパティで基本 Layout を指定できます。 - コードで作成される Layout では、
InputControlLayout.Builder
のInputControlLayout.Builder.Extend()
を使用して基本 Layout を指定できます。
Control 要素
各 Layout は、0 個以上の Control 要素で構成されます。これらはそれぞれ、新しい Control を記述するか、または既存の Control のプロパティを変更します。後者の場合は、下位の階層で、別の要素によって暗示的に追加された子 Control のプロパティを変更することもできます。
// D パッド Control を加えます。
[InputControl(layout = "Dpad")]
// 上記の "Dpad" Layout で加えられた "up" Control のプロパティを
// 変更します。
[InputControl(name = "dpad/up", displayName = "DPADUP")]
public int buttons;
以下の表は、Control 要素に指定できるプロパティの詳細を示しています。これらは、InputControlAttribute
のプロパティとして設定するか、JSON で Control のプロパティとして設定するか、または InputControlLayout.Builder.ControlBuilder
のメソッドを使用して設定することができます。
プロパティ | 説明 |
---|---|
name |
Control の名前。 デフォルトでは、 InputControlAttribute の適用先のフィールド/プロパティの名前です。 |
displayName |
Control の表示名 (UI 文字列で使用されます)。 |
shortDisplayName |
Control の短い表示名 (UI 文字列で使用されます)。 |
layout |
Control に使用するLayout 。 |
variants |
Control のバリアント。 |
aliases |
Control の別名 (エイリアス)。これらは、Control を参照するために使用できる代替名です。 |
usages |
Control の 使用法。 |
offset |
Control の状態が格納されている位置のバイトオフセット。 |
bit |
Control の状態が格納されているバイト内のビットオフセット。 |
sizeInBits |
Control の状態の合計サイズ (ビット数)。 |
arraySize |
0 以外の値に設定すると、このサイズの Control の配列がシステムによって作成されます。 |
parameters |
Control に渡すパラメーター。AxisControl.scaleFactor など、Control タイプに含まれている任意のフィールドに、システムによってこれらが適用されます。 |
processors |
Control に適用する Processor。 |
noisy |
Control が 値変動あり (noisy) と見なされるかどうか。 |
synthetic |
Control が シンセティック と見なされるかどうか。 |
defaultState |
Control の状態 メモリ のデフォルトの初期値。 |
useStateFrom |
シンセティック Control で、Control の状態を合成するために使用されます。 |
minValue |
Control から報告される可能性のある最小値。Control の作動量 を評価するために使用されます。 |
maxValue |
Control から報告される可能性のある最大値。Control の作動量 を評価するために使用されます。 |
dontReset |
デバイスの "ソフト" リセット の実行時に、このコントロールの状態をリセットしないように指定します。これは、リセット時に (0,0) に設定されるのが望ましくないポインター位置などのコントロールで役立ちます。"ハード" リセットの実行時には、このコントロールもデフォルトの状態にリセットされます。 |
Layout のオーバーライド
Layout のオーバーライドを使用すると、既存のLayout のさまざまな部分を非破壊的に変更できます。Layout を 基本 Layout のオーバーライドとして登録するには、InputSystem.RegisterLayoutOverride
を呼び出します。オーバーライドに含まれているすべてのプロパティが、システムによって基本 Layout または既存のプロパティに追加されます。
// "Mouse" Layout に Control を追加
const string json = @"
{
""name"" : ""Overrides"",
""extend"" : ""Mouse"",
""controls"" : [
{ ""name"" : ""extraControl"", ""layout"" : ""Button"" }
]
}
";
InputSystem.RegisterLayoutOverride(json);
プリコンパイルLayout
ランタイムに InputControlLayout
からデバイスをビルドするプロセスには時間がかかります。最終的な InputDevice
インスタンスを作り上げるには、Layout 自体のインスタンスをビルド (リフレクションを伴う場合があります) してから、Layout を解釈する必要があります。このプロセスには通常、複数の InputControlLayout
インスタンスをロードする処理が含まれます。その各インスタンスは、Layout で 継承 または オーバーライド が使用されている場合、複数の Layout を 1 つにまとめた結果である可能性があります。
このプロセスは、最終的な形の Layout を "プリコンパイルされた Layout " として "ベイクする" ことで高速化できます。プリコンパイルされた Layout は、生成済みの C# コードであり、実行されると、InputControlLayout
のロードと解釈を必要とせずに、対応するデバイスをビルドします。この結果、より高速で実行されるだけでなく、生成されるガベージが大幅に減少します。C# リフレクションが使用されることもありません (リフレクションは一般に、C# ランタイムが内部に保持するオブジェクト数を増加させるため、ランタイムオーバーヘッドが発生する原因となります)。
ノート: プリコンパイルされた Layout は、デバイスLayout であることが必要です。
InputControl
の Layout はプリコンパイルできません。
プリコンパイルされた Layout の作成
プリコンパイルされた Layout を設定する最初のステップは、プリコンパイルされた Layout を生成することです。そのためには、Input Debugger を開き、Layouts ブランチ内で、プリコンパイルする Layout に移動してから右クリックし、Generate Precompiled Layout を選択します。
Unity により、生成されたコードの保存場所をたずねる画面が表示されます。プロジェクト内のディレクトリを選択し、ファイル名を入力して、Save をクリックします。
Layout が生成されたら、InputSystem.RegisterPrecompiledLayout
を使用して、そのプリコンパイルされた Layout を Input System に登録することができます。このメソッドでは、プリコンパイルされた Layout のメタデータを含む文字列引数が想定されています。この文字列は、生成されたクラス内に自動的に const
として加えられます。
InputSystem.RegisterPrecompiledLayout<MyPrecompiledDevice>(MyPrecompiledDevice.metadata);
重要: このメソッドを呼び出すときは、関連するすべての Layout 登録が、その Layout をプリコンパイルした時点と同じ状態を保っていることが非常に重要です。内部では、プリコンパイルされた Layout で、非プリコンパイルバージョンと同一の結果が生成されるかどうかは確認されません。
登録されたプリコンパイルされた Layout は、その基になった Layout がインスタンス化されるたびに自動的に使用されます。
// カスタムデバイスクラスを定義します。
public class MyDevice : InputDevice
{
// コントロールゲッターのセッターは、プリコンパイルバージョンから使用できるように、少なくとも `protected`
// または `internal` アクセスにする必要があります。
[InputControl]
public ButtonControl button { get; protected set; }
// このメソッドはプリコンパイルバージョンでは *呼び出されません*。代わりに、ここで
// 実行される参照はすべて、生成される C# コードにハードコーディングされます。
protected override void FinishSetup()
{
base.FinishSetup();
button = GetChildControl<ButtonControl>("button1");
}
}
// 起動時の処理のどこかで、デバイスをLayout として登録します。
InputSystem.RegisterLayout<MyDevice>();
// 次に、プリコンパイルバージョンも登録します。
InputSystem.RegisterPrecompiledLayout<PrecompiledMyDevice>(PrecompiledMyDevice.metadata);
// その後、以下のコードでは、プリコンパイルバージョンが暗示的に使用されます。
InputSystem.AddDevice<MyDevice>();
以下の場合は、プリコンパイルされた Layout の登録が自動的に解除されます。
- プリコンパイルされた Device で使用されるLayout のいずれかに、Layout のオーバーライド が適用された場合。Device によって使用される コントロール についても同様です。
- プリコンパイルされた Device で使用される Layout のいずれかと同じ名前の Layout が登録された場合 (これにより、既にその名前で登録されていた Layout が置き換えられます)。
- プリコンパイルされた Device で使用される Processor を置き換える Processor が登録された場合。
これらの状況が発生すると、Input System が、プリコンパイルされていないバージョンの Layout にフォールバックされます。プリコンパイルされた Layout は、基になる Layout から 派生 した Layout には使用されないことにも注意してください。上記の例で、MyDevice
から新しい Layout を派生させた場合は、プリコンパイルされたバージョンに影響はありませんが (登録は解除されません)、新しく作成されたタイプのデバイスにそのバージョンが使用されることもありません。
// 前の例を引き続き使用して、だれかが後からビルトインボタンを
// 拡張バージョンに置き換えたとします。
InputSystem.RegisterLayout<ExtendedButtonControl>("Button");
// PrecompiledMyDevice で使用される ButtonControl が ExtendedButtonControl に置き換えられたため、
// PrecompiledMyDevice は暗示的に削除されます。
必要な場合は、生成されるコードに #if
による制御を加えることができます。コードジェネレーターは、既存のファイルの先頭をスキャンして #if
で始まる行を探します。見つかった場合は、新しく生成されるコードでもその行を保持し、対応する #endif
をファイルの末尾に生成します。同様に、生成されるクラスを public
から internal
に変更すると、クラスの再生成時にそのモディファイアが保持されます。さらに、生成されるファイル内の名前空間を変更することもでき、その場合も変更が保持されます。
生成されるクラスは partial
としてマークされるため、同等の partial
クラスを定義することで、オーバーロードやその他のコードを追加できます。
// 次の行は、プリコンパイルされた Layout の再生成時に保持されます。対応する
// #endif はファイルの末尾に出力されます。
# if UNITY_EDITOR || UNITY_STANDALONE
// 名前空間を変更した場合は、プリコンパイルされた Layout の再生成時に
// 名前空間の名前が保持されます。
namepace MyNamespace
{
// `public` を `internal` に変更すると、プリコンパイルLayout の
// 再生成時に変更が保持されます。
public partial class PrecompiledMyDevice : MyDevice
{
//...
生成される Layout の名前空間は、