Interactions
An interaction represents a specific input pattern. For example, a "hold" is an interaction that requires a control to be held for at least a minimum amount of time.
Interactions drive responses on actions. They are placed on individual bindings but can also be placed on an action as a whole, in which case they are applied to every binding on the action. At runtime, when a particular interaction is completed, it triggers the action.
Operation
An interaction has a set of dictinct phases it can go through in response to receiving input.
Phase | Description |
---|---|
Waiting |
The interaction is waiting for input. |
Started |
The interaction has been started (i.e. some input has been received) but has not been completed yet. |
Performed |
The interaction has been completed. |
Canceled |
The interaction has been interrupted and aborted. For example, this can happen with a "Hold" if a button is released before a full "Hold" is achieved. |
Note that not every interaction supports every phase and that the pattern in which the phases are triggered from a specific interaction depends on the interaction.
While Performed
will generally be the phase that triggers the actual response to an interaction, Started
and Canceled
can be very useful for providing UI feedback while the interation is in progress. For example, when a "Hold" is Started
, a radial progress bar can be shown that fills up until the hold time has been reached. If, however, the "Hold" is Canceled
before it is complete, the progress bar can be reset to the beginning.
The following example demonstrates this kind of setup with a fire action that can be tapped to fire immediately and held to charge.
var fireAction = new InputAction("fire");
fireAction.AddBinding("<Gamepad>/buttonSouth")
// Tap fires, slow tap charges. Both act on release.
.WithInteractions("tap;slowTap");
fireAction.started +=
context =>
{
if (context.interaction is SlowTapInteraction)
ShowChargingUI();
};
fireAction.performed +=
context =>
{
if (context.interaction is SlowTapInteraction)
ChargedFire();
else
Fire();
};
fireAction.canceled +=
_ => HideChargingUI();
Multiple Controls on an Action
If you have multiple controls bound to a binding or an action which has an interaction then the input system will first apply the control disambiguation logic to get a single value for the action, which is then fed to the interaction logic. Any of the bound controls can perform the interaction.
Multiple Interactions on a Binding
If multiple interactions are present on a single binding or action, then the interactions will be checked in the order they are present on the binding. We have such a case in the code example above - the binding on the fireAction
action has two interactions: WithInteractions("tap;slowTap")
. Now, the Tap interaction gets a first chance at interpreting the input from the action. If the button is pressed, the action will call the started
callback on the Tap interaction. Now, if we keep holding the button, the Tap interaction will time out, and the action will call the canceled
callback for the Tap interaction, and then start processing the SlowTap interaction (which will get a started
callback now).
Using Interactions
Interactions can be installed on bindings or actions.
Interactions on Bindings
When you create bindings for your actions, you can choose to add interactions to the bindings.
If you are using Input Action Assets, you can simply add any interaction to your bindings in the input action editor. Once you created some bindings, just select the binding you want to add interactions to (so that the right pane of the window shows the properties for that binding). Now, click on the "Plus" icon on the "Interactions" foldout, which will show a popup of all available interactions. Choose an interaction type to add an interaction instance of that type. The interaction will now be shown under the interactions foldout. If the interaction has any parameters you can now edit them here as well:
You can click the "Minus" button next to a interaction to remove it. You can click the "up" and "down" arrows to change the order of interactions.
If you create your bindings in code, you can add interaction like this:
var action = new InputAction();
action.AddBinding("<Gamepad>/leftStick")
.WithInteractions("tap(duration=0.8)");
Interactions on Actions
Interactions on actions work very similar to interactions on bindings, but they affect all controls bound to an action, not just the ones coming from a specific binding. If there are interactions on both the binding and the action, the ones from the binding will be processes first.
You can add and edit interactions on actions in the Input Action Assets the same way as you would do for bindings - just add them in the right window pane when you have selected an action to edit.
If you create your actions in code, you can add interactions like this:
var action = new InputAction(interactions: "tap(duration=0.8)");
Predefined Interactions
The input system package comes with a set of useful interactions you can use. If no interaction is set an an action, the system will use the default interaction.
Note that the built-in interaction operate on control actuation, not on control values directly. This means that the pressPoint
parameters will be evaluated against the magnitude of the control actuation. This means that it is possible to use these interactions on any control which has a magnitude (like sticks) and not just on buttons.
Default Interaction
If no interaction has specifically been added to a binding or its action, then the default interaction applies to the binding. It is designed to represent a "generic" interaction with an input control.
For Value
or Button
type actions, the behavior is as follows:
- As soon as a bound control becomes actuated, the action goes from
Waiting
toStarted
and then immediately toPerformed
and back toStarted
(i.e. you will see on callback onInputAction.started
followed by one callback onInputAction.performed
). - For as long as the bound control remains actuated, the action stays in
Started
and will triggerPerformed
whenever the value of the control changes (i.e. you will see one call to [InputAction.performed
])(../api/UnityEngine.InputSystem.InputAction.html#UnityEngine_InputSystem_InputAction_performed). - When the bound control stops being actuated, the action goes to
Canceled
and then back toWaiting
(i.e. you will see one call toInputAction.canceled
).
For PassThrough
type actions, the behavior is simpler. The input system will not try to track any interaction state (which would be meaningless if tracking several controls separately), but simply trigger a Performed
callback for each value change.
Callbacks/InputAction.type |
Value or Button |
PassThrough |
---|---|---|
started |
Control is actuated | not used |
performed |
Controls changes actuation (also first time, i.e. when started is triggered, too) |
Control changes value |
canceled |
Control is no longer actuated | not used |
Press
A PressInteraction
can be used to explicitly force button-like interaction. The behavior
parameter let's you select if the interaction should trigger on button press, release or both.
Parameters | Type | Default value |
---|---|---|
pressPoint |
float |
InputSettings.defaultButtonPressPoint |
behavior |
PressBehavior |
PressOnly |
Callbacks/behavior |
PressOnly |
ReleaseOnly |
PressAndRelease |
---|---|---|---|
started |
Control magnitude crosses pressPoint |
Control magnitude crosses pressPoint |
Control magnitude crosses pressPoint |
performed |
Control magnitude crosses pressPoint |
Control magnitude goes back below pressPoint |
* Control magnitude crosses pressPoint or * Control magnitude goes back below pressPoint |
canceled |
not used | not used | not used |
Hold
A HoldInteraction
requires a control to be held for duration
seconds before the action is triggered.
Parameters | Type | Default value |
---|---|---|
duration |
float |
InputSettings.defaultHoldTime |
pressPoint |
float |
InputSettings.defaultButtonPressPoint |
Callbacks | |
---|---|
started |
Control magnitude crosses pressPoint |
performed |
Control magnitude held above pressPoint for >= duration |
canceled |
Control magnitude goes back below pressPoint before duration (i.e. button was not held long enough) |
Tap
A TapInteraction
requires a control to be pressed and released within duration
seconds to trigger the action.
Parameters | Type | Default value |
---|---|---|
duration |
float |
InputSettings.defaultTapTime |
pressPoint |
float |
InputSettings.defaultButtonPressPoint |
Callbacks | |
---|---|
started |
Control magnitude crosses pressPoint |
performed |
Control magnitude goes back below pressPoint before duration |
canceled |
Control magnitude held above pressPoint for >= duration (i.e. tap was too slow) |
SlowTap
A SlowTapInteraction
requires a control to be pressed and, held for a minimum duration of duration
seconds, and then released to trigger the action.
Parameters | Type | Default value |
---|---|---|
duration |
float |
InputSettings.defaultSlowTapTime |
pressPoint |
float |
InputSettings.defaultButtonPressPoint |
Callbacks | |
---|---|
started |
Control magnitude crosses pressPoint |
performed |
Control magnitude goes back below pressPoint after duration |
canceled |
Control magnitude goes back below pressPoint before duration (i.e. tap was too fast) |
MultiTap
A MultiTapInteraction
requires a control to be pressed and released within tapTime
seconds tapCount
times, with no more then tapDelay
seconds passing between taps for the interaction to trigger. This can be used to detect double-click or multi-click gestures for instance.
Parameters | Type | Default value |
---|---|---|
tapTime |
float |
InputSettings.defaultTapTime |
tapDelay |
float |
2 * tapTime |
tapCount |
int |
2 |
pressPoint |
float |
InputSettings.defaultButtonPressPoint |
Callbacks | |
---|---|
started |
Control magnitude crosses pressPoint |
performed |
Control magnitude went back below pressPoint and back up above it repeatedly for tapCount times |
canceled |
* After going back below pressPoint , control magnitude did not go back above pressPoint within tapDelay time (i.e. taps were spaced out too far apart)or * After going back above pressPoint , control magnitude did not go back below pressPoint within tapTime time (i.e. taps were too long) |
Writing Custom Interactions
You can also write a custom interaction to use in your project. Newly added interactions are usable in the UI and data the same way that built-in interactions are. Simply add a class implementing the IInputInteraction
, interface Process
and Reset
methods:
// In the spirit of classic joystick-destroying C64 sport simulation games
// like "Winter Games", here's an interaction which performs when you move an
// axis all the way from end to end fast enough.
public class MyWiggleInteraction : IInputInteraction
{
public float duration = 0.2;
void Process(ref InputInteractionContext context)
{
if (context.timerHasExpired)
{
context.Canceled();
return;
}
switch (context.phase)
{
case InputActionPhase.Waiting:
if (context.control.ReadValue<float>() == 1)
{
context.Started();
context.SetTimeout(duration);
}
break;
case InputActionPhase.Started:
if (context.control.ReadValue<float>() == -1)
context.Performed();
break;
}
}
// Unlike processors, interations can be stateful, meaning that it is permissible
// to keep local state that mutates over time as input is received. The system may
// ask interactions to reset such state at certain points by invoking the `Reset()`
// method.
void Reset()
{
}
}
Now, you need to tell the Input System about your interaction. So, somewhere in your initialization code, you should call:
InputSystem.RegisterInteraction<MyWiggleInteraction>();
Now, your new interaction will become available up in the Input Action Asset Editor window, and you can also add it in code like this:
var action = new InputAction(interactions: "MyWiggle(duration=0.5)");