Controls.md 20 KB

Controls

An Input Control represents a source of values. These values can be of any structured or primitive type. The only requirement is that the type is blittable.

Note: Controls are for input only. Output and configuration items on Input Devices are not represented as Controls.

Each Control is identified by a name (InputControl.name) and can optionally have a display name (InputControl.displayName) that differs from the Control name. For example, the right-hand face button closest to the touchpad on a PlayStation DualShock 4 controller has the control name "buttonWest" and the display name "Square".

Additionally, a Control might have one or more aliases which provide alternative names for the Control. You can access the aliases for a specific Control through its InputControl.aliases property.

Finally, a Control might also have a short display name which can be accessed through the InputControl.shortDisplayName property. For example, the short display name for the left mouse button is "LMB".

Control hierarchies

Controls can form hierarchies. The root of a Control hierarchy is always a Device.

The setup of hierarchies is exclusively controlled through layouts.

You can access the parent of a Control using InputControl.parent, and its children using InputControl.children. To access the flattened hierarchy of all Controls on a Device, use InputDevice.allControls.

Control types

All controls are based on the InputControl base class. Most concrete implementations are based on InputControl<TValue>.

The Input System provides the following types of controls out of the box:

Control Type Description Example
AxisControl A 1D floating-point axis. Gamepad.leftStick.x
ButtonControl A button expressed as a floating-point value. Whether the button can have a value other than 0 or 1 depends on the underlying representation. For example, gamepad trigger buttons can have values other than 0 and 1, but gamepad face buttons generally can't. Mouse.leftButton
KeyControl A specialized button that represents a key on a Keyboard. Keys have an associated keyCode and, unlike other types of Controls, change their display name in accordance to the currently active system-wide keyboard layout. See the Keyboard documentation for details. Keyboard.aKey
Vector2Control A 2D floating-point vector. Pointer.position
Vector3Control A 3D floating-point vector. Accelerometer.acceleration
QuaternionControl A 3D rotation. AttitudeSensor.attitude
IntegerControl An integer value. Touchscreen.primaryTouch.touchId
StickControl A 2D stick control like the thumbsticks on gamepads or the stick control of a joystick. Gamepad.rightStick
DpadControl A 4-way button control like the D-pad on gamepads or hatswitches on joysticks. Gamepad.dpad
TouchControl A control that represents all the properties of a touch on a touch screen. Touchscreen.primaryTouch

You can browse the set of all registered control layouts in the input debugger.

Control usages

A Control can have one or more associated usages. A usage is a string that denotes the Control's intended use. An example of a Control usage is Submit, which labels a Control that is commonly used to confirm a selection in the UI. On a gamepad, this usage is commonly found on the buttonSouth Control.

You can access a Control's usages using the InputControl.usages property.

Usages can be arbitrary strings. However, a certain set of usages is very commonly used and comes predefined in the API in the form of the CommonUsages static class. Check out the CommonUsages scripting API page for an overview.

Control paths

Example: <Gamepad>/leftStick/x means "X Control on left stick of gamepad".

The Input System can look up Controls using textual paths. Bindings on Input Actions rely on this feature to identify the Control(s) they read input from. However, you can also use them for lookup directly on Controls and Devices, or to let the Input System search for Controls among all devices using InputSystem.FindControls.

var gamepad = Gamepad.all[0];
var leftStickX = gamepad["leftStick/x"];
var submitButton = gamepad["{Submit}"];
var allSubmitButtons = InputSystem.FindControls("*/{Submit}");

Control paths resemble file system paths. Each path consists of one or more components separated by a forward slash:

component/component...

Each component uses a similar syntax made up of multiple fields. Each field is optional, but at least one field must be present. All fields are case-insensitive.

<layoutName>{usageName}controlName#(displayName)

The following table explains the use of each field:

Field Description Example
<layoutName> Requires the Control at the current level to be based on the given layout. The actual layout of the Control may be the same or a layout based on the given layout. <Gamepad>/buttonSouth
{usageName} Works differently for Controls and Devices.

When used on a Device (the first component of a path), it requires the device to have the given usage. See Device usages for more details.

For looking up a Control, the usage field is currently restricted to the path component immediately following the Device (the second component in the path). It finds the Control on the Device that has the given usage. The Control can be anywhere in the Control hierarchy of the Device.
Device:

<XRController>{LeftHand}/trigger

Control:

<Gamepad>/{Submit}
controlName Requires the Control at the current level to have the given name. Takes both "proper" names (InputControl.name) and aliases (InputControl.aliases) into account.

This field can also be a wildcard (*) to match any name.
MyGamepad/buttonSouth

*/{PrimaryAction} (match PrimaryAction usage on Devices with any name)
#(displayName) Requires the Control at the current level to have the given display name (i.e. InputControl.displayName). The display name may contain whitespace and symbols. <Keyboard>/#(a) (matches the key that generates the "a" character, if any, according to the current keyboard layout).

<Gamepad>/#(Cross)

You can access the literal path of a given control via its InputControl.path property.

Control state

Each Control is connected to a block of memory that is considered the Control's "state". You can query the size, format, and location of this block of memory from a Control through the InputControl.stateBlock property.

The state of Controls is stored in unmanaged memory that the Input System handles internally. All Devices added to the system share one block of unmanaged memory that contains the state of all the Controls on the Devices.

A Control's state might not be stored in the natural format for that Control. For example, the system often represents buttons as bitfields, and axis controls as 8-bit or 16-bit integer values. This format is determined by the combination of platform, hardware, and drivers. Each Control knows the format of its storage and how to translate the values as needed. The Input System uses layouts to understand this representation.

You can access the current state of a Control through its ReadValue method.

Gamepad.current.leftStick.x.ReadValue();

Each type of Control has a specific type of values that it returns, regardless of how many different types of formats it supports for its state. You can access this value type through the InputControl.valueType property.

Reading a value from a Control might apply one or more value Processors. See documentation on Processors for more information.

Recording state history

You might want to access the history of value changes on a Control (for example, in order to compute exit velocity on a touch release).

To record state changes over time, you can use InputStateHistory or InputStateHistory<TValue>. The latter restricts Controls to those of a specific value type, which in turn simplifies some of the API.

// Create history that records Vector2 control value changes.
// NOTE: You can also pass controls directly or use paths that match multiple
//       controls (e.g. "<Gamepad>/<Button>").
// NOTE: The unconstrained InputStateHistory class can record changes on controls
//        of different value types.
var history = new InputStateHistory<Vector2>("<Touchscreen>/primaryTouch/position");

// To start recording state changes of the controls to which the history
// is attached, call StartRecording.
history.StartRecording();

// To stop recording state changes, call StopRecording.
history.StopRecording();

// Recorded history can be accessed like an array.
for (var i = 0; i < history.Count; ++i)
{
    // Each recorded value provides information about which control changed
    // value (in cases state from multiple controls is recorded concurrently
    // by the same InputStateHistory) and when it did so.

    var time = history[i].time;
    var control = history[i].control;
    var value = history[i].ReadValue();
}

// Recorded history can also be iterated over.
foreach (var record in history)
    Debug.Log(record.ReadValue());
Debug.Log(string.Join(",\n", history));

// You can also record state changes manually, which allows
// storing arbitrary histories in InputStateHistory.
// NOTE: This records a value change that didn't actually happen on the control.
history.RecordStateChange(Touchscreen.current.primaryTouch.position,
    new Vector2(0.123f, 0.234f));

// State histories allocate unmanaged memory and need to be disposed.
history.Dispose();

For example, if you want to have the last 100 samples of the left stick on the gamepad available, you can use this code:

var history = new InputStateHistory<Vector2>(Gamepad.current.leftStick);
history.historyDepth = 100;
history.StartRecording();

Control actuation

A Control is considered actuated when it has moved away from its default state in such a way that it affects the actual value of the Control. You can query whether a Control is currently actuated using IsActuated.

// Check if leftStick is currently actuated.
if (Gamepad.current.leftStick.IsActuated())
    Debug.Log("Left Stick is actuated");

It can be useful to determine not just whether a Control is actuated at all, but also the amount by which it is actuated (that is, its magnitude). For example, for a Vector2Control this would be the length of the vector, whereas for a button it is the raw, absolute floating-point value.

In general, the current magnitude of a Control is always >= 0. However, a Control might not have a meaningful magnitude, in which case it returns -1. Any negative value should be considered an invalid magnitude.

You can query the current amount of actuation using EvaluateMagnitude.

// Check if left stick is actuated more than a quarter of its motion range.
if (Gamepad.current.leftStick.EvaluateMagnitude() > 0.25f)
    Debug.Log("Left Stick actuated past 25%");

There are two mechanisms that most notably make use of Control actuation:

  • Interactive rebinding (InputActionRebindingExceptions.RebindOperation) uses it to select between multiple suitable Controls to find the one that is actuated the most.
  • Disambiguation between multiple Controls that are bound to the same action uses it to decide which Control gets to drive the action.

Noisy Controls

The Input System can label a Control as "noisy". You can query this using the InputControl.noisy property.

Noisy Controls are those that can change value without any actual or intentional user interaction required. A good example of this is a gravity sensor in a cellphone. Even if the cellphone is perfectly still, there are usually fluctuations in gravity readings. Another example are orientation readings from an HMD.

If a Control is marked as noisy, it means that:

  1. The Control is not considered for interactive rebinding. InputActionRebindingExceptions.RebindingOperation ignores the Control by default (you can bypass this using WithoutIgnoringNoisyControls).
  2. If enabled in the Project Settings, the system performs additional event filtering, then calls InputDevice.MakeCurrent. If an input event for a Device contains no state change on a Control that is not marked noisy, then the Device will not be made current based on the event. This avoids, for example, a plugged in PS4 controller constantly making itself the current gamepad (Gamepad.current) due to its sensors constantly feeding data into the system.
  3. When the application loses focus and Devices are reset as a result, the state of noisy Controls will be preserved as is. This ensures that sensor readinds will remain at their last value rather than being reset to default values.

Note: If any Control on a Device is noisy, the Device itself is flagged as noisy.

Parallel to the input state and the default state that the Input System keeps for all Devices currently present, it also maintains a noise mask in which only bits for state that is not noise are set. This can be used to very efficiently mask out noise in input.

Synthetic Controls

A synthetic Control is a Control that doesn't correspond to an actual physical control on a device (for example the left, right, up, and down child Controls on a StickControl). These Controls synthesize input from other, actual physical Controls and present it in a different way (in this example, they allow you to treat the individual directions of a stick as buttons).

Whether a given Control is synthetic is indicated by its InputControl.synthetic property.

The system considers synthetic Controls for interactive rebinding but always favors non-synthetic Controls. If both a synthetic and a non-synthetic Control that are a potential match exist, the non-synthetic Control wins by default. This makes it possible to interactively bind to <Gamepad>/leftStick/left, for example, but also makes it possible to bind to <Gamepad>/leftStickPress without getting interference from the synthetic buttons on the stick.