Initial commit
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2069418e852748dbaa154cf71b8f033a
|
||||
folderAsset: yes
|
||||
timeCreated: 1506735447
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6adbf65a53ff9b04990c2116b3e4d5b7
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,248 @@
|
||||
using System.ComponentModel;
|
||||
using UnityEngine.InputSystem.Layouts;
|
||||
using UnityEngine.InputSystem.Processors;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine.InputSystem.Editor;
|
||||
using UnityEngine.UIElements;
|
||||
#endif
|
||||
|
||||
namespace UnityEngine.InputSystem.Composites
|
||||
{
|
||||
/// <summary>
|
||||
/// A single axis value computed from one axis that pulls in the <see cref="negative"/> direction (<see cref="minValue"/>) and one
|
||||
/// axis that pulls in the <see cref="positive"/> direction (<see cref="maxValue"/>).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The limits of the axis are determined by <see cref="minValue"/> and <see cref="maxValue"/>.
|
||||
/// By default, they are set to <c>[-1..1]</c>. The values can be set as parameters.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var action = new InputAction();
|
||||
/// action.AddCompositeBinding("Axis(minValue=0,maxValue=2)")
|
||||
/// .With("Negative", "<Keyboard>/a")
|
||||
/// .With("Positive", "<Keyboard>/d");
|
||||
/// </code>
|
||||
/// </example>
|
||||
///
|
||||
/// If both axes are actuated at the same time, the behavior depends on <see cref="whichSideWins"/>.
|
||||
/// By default, neither side will win (<see cref="WhichSideWins.Neither"/>) and the result
|
||||
/// will be 0 (or, more precisely, the midpoint between <see cref="minValue"/> and <see cref="maxValue"/>).
|
||||
/// This can be customized to make the positive side win (<see cref="WhichSideWins.Positive"/>)
|
||||
/// or the negative one (<see cref="WhichSideWins.Negative"/>).
|
||||
///
|
||||
/// This is useful, for example, in a driving game where break should cancel out accelerate.
|
||||
/// By binding <see cref="negative"/> to the break control(s) and <see cref="positive"/> to the
|
||||
/// acceleration control(s), and setting <see cref="whichSideWins"/> to <see cref="WhichSideWins.Negative"/>,
|
||||
/// if the break button is pressed, it will always cause the acceleration button to be ignored.
|
||||
///
|
||||
/// The actual <em>absolute</em> values of <see cref="negative"/> and <see cref="positive"/> are used
|
||||
/// to scale <see cref="minValue"/> and <see cref="maxValue"/> respectively. So if, for example, <see cref="positive"/>
|
||||
/// is bound to <see cref="Gamepad.rightTrigger"/> and the trigger is at a value of 0.5, then the resulting
|
||||
/// value is <c>maxValue * 0.5</c> (the actual formula is <c>midPoint + (maxValue - midPoint) * positive</c>).
|
||||
/// </remarks>
|
||||
[DisplayStringFormat("{negative}/{positive}")]
|
||||
[DisplayName("Positive/Negative Binding")]
|
||||
public class AxisComposite : InputBindingComposite<float>
|
||||
{
|
||||
/// <summary>
|
||||
/// Binding for the axis input that controls the negative [<see cref="minValue"/>..0] direction of the
|
||||
/// combined axis.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property is automatically assigned by the input system.
|
||||
/// </remarks>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Global
|
||||
[InputControl(layout = "Axis")] public int negative = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Binding for the axis input that controls the positive [0..<see cref="maxValue"/>] direction of the
|
||||
/// combined axis.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property is automatically assigned by the input system.
|
||||
/// </remarks>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Global
|
||||
[InputControl(layout = "Axis")] public int positive = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The lower bound that the axis is limited to. -1 by default.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This value corresponds to the full actuation of the control(s) bound to <see cref="negative"/>.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var action = new InputAction();
|
||||
/// action.AddCompositeBinding("Axis(minValue=0,maxValue=2)")
|
||||
/// .With("Negative", "<Keyboard>/a")
|
||||
/// .With("Positive", "<Keyboard>/d");
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="maxValue"/>
|
||||
/// <seealso cref="negative"/>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Global
|
||||
[Tooltip("Value to return when the negative side is fully actuated.")]
|
||||
public float minValue = -1;
|
||||
|
||||
/// <summary>
|
||||
/// The upper bound that the axis is limited to. 1 by default.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This value corresponds to the full actuation of the control(s) bound to <see cref="positive"/>.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var action = new InputAction();
|
||||
/// action.AddCompositeBinding("Axis(minValue=0,maxValue=2)")
|
||||
/// .With("Negative", "<Keyboard>/a")
|
||||
/// .With("Positive", "<Keyboard>/d");
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="minValue"/>
|
||||
/// <seealso cref="positive"/>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Global
|
||||
[Tooltip("Value to return when the positive side is fully actuated.")]
|
||||
public float maxValue = 1;
|
||||
|
||||
/// <summary>
|
||||
/// If both the <see cref="positive"/> and <see cref="negative"/> button are actuated, this
|
||||
/// determines which value is returned from the composite.
|
||||
/// </summary>
|
||||
[Tooltip("If both the positive and negative side are actuated, decides what value to return. 'Neither' (default) means that " +
|
||||
"the resulting value is the midpoint between min and max. 'Positive' means that max will be returned. 'Negative' means that " +
|
||||
"min will be returned.")]
|
||||
public WhichSideWins whichSideWins = WhichSideWins.Neither;
|
||||
|
||||
/// <summary>
|
||||
/// The value that is returned if the composite is in a neutral position, that is, if
|
||||
/// neither <see cref="positive"/> nor <see cref="negative"/> are actuated or if
|
||||
/// <see cref="whichSideWins"/> is set to <see cref="WhichSideWins.Neither"/> and
|
||||
/// both <see cref="positive"/> and <see cref="negative"/> are actuated.
|
||||
/// </summary>
|
||||
public float midPoint => (maxValue + minValue) / 2;
|
||||
|
||||
////TODO: add parameters to control ramp up&down
|
||||
|
||||
/// <inheritdoc />
|
||||
public override float ReadValue(ref InputBindingCompositeContext context)
|
||||
{
|
||||
var negativeValue = Mathf.Abs(context.ReadValue<float>(negative));
|
||||
var positiveValue = Mathf.Abs(context.ReadValue<float>(positive));
|
||||
|
||||
var negativeIsActuated = negativeValue > Mathf.Epsilon;
|
||||
var positiveIsActuated = positiveValue > Mathf.Epsilon;
|
||||
|
||||
if (negativeIsActuated == positiveIsActuated)
|
||||
{
|
||||
switch (whichSideWins)
|
||||
{
|
||||
case WhichSideWins.Negative:
|
||||
positiveIsActuated = false;
|
||||
break;
|
||||
|
||||
case WhichSideWins.Positive:
|
||||
negativeIsActuated = false;
|
||||
break;
|
||||
|
||||
case WhichSideWins.Neither:
|
||||
return midPoint;
|
||||
}
|
||||
}
|
||||
|
||||
var mid = midPoint;
|
||||
|
||||
if (negativeIsActuated)
|
||||
return mid - (mid - minValue) * negativeValue;
|
||||
|
||||
return mid + (maxValue - mid) * positiveValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override float EvaluateMagnitude(ref InputBindingCompositeContext context)
|
||||
{
|
||||
var value = ReadValue(ref context);
|
||||
if (value < midPoint)
|
||||
{
|
||||
value = Mathf.Abs(value - midPoint);
|
||||
return NormalizeProcessor.Normalize(value, 0, Mathf.Abs(minValue), 0);
|
||||
}
|
||||
|
||||
value = Mathf.Abs(value - midPoint);
|
||||
return NormalizeProcessor.Normalize(value, 0, Mathf.Abs(maxValue), 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// What happens to the value of an <see cref="AxisComposite"/> if both <see cref="positive"/>
|
||||
/// and <see cref="negative"/> are actuated at the same time.
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1717:OnlyFlagsEnumsShouldHavePluralNames", Justification = "False positive: `Wins` is not a plural form.")]
|
||||
public enum WhichSideWins
|
||||
{
|
||||
/// <summary>
|
||||
/// If both <see cref="positive"/> and <see cref="negative"/> are actuated, the sides cancel
|
||||
/// each other out and the result is 0.
|
||||
/// </summary>
|
||||
Neither = 0,
|
||||
|
||||
/// <summary>
|
||||
/// If both <see cref="positive"/> and <see cref="negative"/> are actuated, the value of
|
||||
/// <see cref="positive"/> wins and <see cref="negative"/> is ignored.
|
||||
/// </summary>
|
||||
Positive = 1,
|
||||
|
||||
/// <summary>
|
||||
/// If both <see cref="positive"/> and <see cref="negative"/> are actuated, the value of
|
||||
/// <see cref="negative"/> wins and <see cref="positive"/> is ignored.
|
||||
/// </summary>
|
||||
Negative = 2,
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
internal class AxisCompositeEditor : InputParameterEditor<AxisComposite>
|
||||
{
|
||||
private GUIContent m_WhichAxisWinsLabel = new GUIContent("Which Side Wins",
|
||||
"Determine which axis 'wins' if both are actuated at the same time. "
|
||||
+ "If 'Neither' is selected, the result is 0 (or, more precisely, "
|
||||
+ "the midpoint between minValue and maxValue).");
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
|
||||
if (!InputSystem.settings.useIMGUIEditorForAssets) return;
|
||||
#endif
|
||||
target.whichSideWins = (AxisComposite.WhichSideWins)EditorGUILayout.EnumPopup(m_WhichAxisWinsLabel, target.whichSideWins);
|
||||
}
|
||||
|
||||
#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
|
||||
public override void OnDrawVisualElements(VisualElement root, Action onChangedCallback)
|
||||
{
|
||||
var modeField = new EnumField(m_WhichAxisWinsLabel.text, target.whichSideWins)
|
||||
{
|
||||
tooltip = m_WhichAxisWinsLabel.tooltip
|
||||
};
|
||||
|
||||
modeField.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
target.whichSideWins = (AxisComposite.WhichSideWins)evt.newValue;
|
||||
onChangedCallback();
|
||||
});
|
||||
|
||||
root.Add(modeField);
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 61fdc882d66f0f34d90450c001c0078e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,203 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using UnityEngine.InputSystem.Layouts;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
using UnityEngine.Scripting;
|
||||
|
||||
////TODO: remove this once we can break the API
|
||||
|
||||
namespace UnityEngine.InputSystem.Composites
|
||||
{
|
||||
/// <summary>
|
||||
/// A button with an additional modifier. The button only triggers when
|
||||
/// the modifier is pressed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This composite can be used to require another button to be held while
|
||||
/// pressing the button that triggers the action. This is most commonly used
|
||||
/// on keyboards to require one of the modifier keys (shift, ctrl, or alt)
|
||||
/// to be held in combination with another key, e.g. "CTRL+1".
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Create a button action that triggers when CTRL+1
|
||||
/// // is pressed on the keyboard.
|
||||
/// var action = new InputAction(type: InputActionType.Button);
|
||||
/// action.AddCompositeBinding("ButtonWithOneModifier")
|
||||
/// .With("Modifier", "<Keyboard>/leftCtrl")
|
||||
/// .With("Modifier", "<Keyboard>/rightControl")
|
||||
/// .With("Button", "<Keyboard>/1")
|
||||
/// </code>
|
||||
/// </example>
|
||||
///
|
||||
/// Note that this is not restricted to the keyboard and will preserve
|
||||
/// the full value of the button.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Create a button action that requires the A button on the
|
||||
/// // gamepad to be held and will then trigger from the gamepad's
|
||||
/// // left trigger button.
|
||||
/// var action = new InputAction(type: InputActionType.Button);
|
||||
/// action.AddCompositeBinding("ButtonWithOneModifier")
|
||||
/// .With("Modifier", "<Gamepad>/buttonSouth")
|
||||
/// .With("Button", "<Gamepad>/leftTrigger");
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="ButtonWithTwoModifiers"/>
|
||||
[DesignTimeVisible(false)] // Obsoleted by OneModifierComposite
|
||||
[DisplayStringFormat("{modifier}+{button}")]
|
||||
public class ButtonWithOneModifier : InputBindingComposite<float>
|
||||
{
|
||||
/// <summary>
|
||||
/// Binding for the button that acts as a modifier, e.g. <c><Keyboard/leftCtrl</c>.
|
||||
/// </summary>
|
||||
/// <value>Part index to use with <see cref="InputBindingCompositeContext.ReadValue{T}(int)"/>.</value>
|
||||
/// <remarks>
|
||||
/// This property is automatically assigned by the input system.
|
||||
/// </remarks>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Global
|
||||
// ReSharper disable once UnassignedField.Global
|
||||
[InputControl(layout = "Button")] public int modifier;
|
||||
|
||||
/// <summary>
|
||||
/// Binding for the button that is gated by the modifier. The composite will assume the value
|
||||
/// of this button while the modifier is pressed.
|
||||
/// </summary>
|
||||
/// <value>Part index to use with <see cref="InputBindingCompositeContext.ReadValue{T}(int)"/>.</value>
|
||||
/// <remarks>
|
||||
/// This property is automatically assigned by the input system.
|
||||
/// </remarks>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Global
|
||||
// ReSharper disable once UnassignedField.Global
|
||||
[InputControl(layout = "Button")] public int button;
|
||||
|
||||
/// <summary>
|
||||
/// If set to <c>true</c>, <see cref="modifier"/> can be pressed after <see cref="button"/> and the composite will
|
||||
/// still trigger. Default is false.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default, if the setting <see cref="InputSettings.shortcutKeysConsumeInput"/> is enabled,
|
||||
/// <see cref="modifier"/> is required to be in pressed state before or at the same time that <see cref="button"/>
|
||||
/// goes into pressed state for the composite as a whole to trigger. This means that binding to, for example, <c>Shift+B</c>,
|
||||
/// the <c>shift</c> key has to be pressed before pressing the <c>B</c> key. This is the behavior usually expected with
|
||||
/// keyboard shortcuts.
|
||||
///
|
||||
/// This parameter can be used to bypass this behavior and allow any timing between <see cref="modifier"/> and <see cref="button"/>.
|
||||
/// The only requirement is for them both to concurrently be in pressed state.
|
||||
///
|
||||
/// To don't depends on the setting please consider using <see cref="modifiersOrder"/> instead.
|
||||
/// </remarks>
|
||||
[Tooltip("Obsolete please use modifiers Order. If enabled, this will override the Input Consumption setting, allowing the modifier keys to be pressed after the button and the composite will still trigger.")]
|
||||
[Obsolete("Use ModifiersOrder.Unordered with 'modifiersOrder' instead")]
|
||||
public bool overrideModifiersNeedToBePressedFirst;
|
||||
|
||||
/// <summary>
|
||||
/// Determines how a <c>modifiers</c> keys need to be pressed in order or not.
|
||||
/// </summary>
|
||||
public enum ModifiersOrder
|
||||
{
|
||||
/// <summary>
|
||||
/// By default, if the setting <see cref="InputSettings.shortcutKeysConsumeInput"/> is enabled,
|
||||
/// <see cref="modifier"/> is required to be in pressed state before or at the same time that <see cref="button"/>
|
||||
/// goes into pressed state for the composite as a whole to trigger. This means that binding to, for example, <c>Shift+B</c>,
|
||||
/// the <c>shift</c> key has to be pressed before pressing the <c>B</c> key. This is the behavior usually expected with
|
||||
/// keyboard shortcuts.
|
||||
///
|
||||
/// If the setting <see cref="InputSettings.shortcutKeysConsumeInput"/> is disabled,
|
||||
/// modifiers can be pressed after the button and the composite will still trigger.
|
||||
/// </summary>
|
||||
Default = 0,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="modifier"/> is required to be in pressed state before or at the same
|
||||
/// time that <see cref="button"/> goes into pressed state for the composite as a whole to trigger. This means that binding to,
|
||||
/// for example, <c>Ctrl+B</c>, the <c>ctrl</c> key have to be pressed before pressing the <c>B</c> key.
|
||||
/// This is the behavior usually expected with keyboard shortcuts.
|
||||
/// </summary>
|
||||
Ordered = 1,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="modifier"/> can be pressed after <see cref="button"/>
|
||||
/// and the composite will still trigger. The only requirement is for all of them to concurrently be in pressed state.
|
||||
/// </summary>
|
||||
Unordered = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If set to <c>Ordered</c> or <c>Unordered</c>, the built-in logic to determine if modifiers need to be pressed first is overridden.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default, if the setting <see cref="InputSettings.shortcutKeysConsumeInput"/> is enabled,
|
||||
/// <see cref="modifier"/> is required to be in pressed state before or at the same time that <see cref="button"/>
|
||||
/// goes into pressed state for the composite as a whole to trigger. This means that binding to, for example, <c>Shift+B</c>,
|
||||
/// the <c>shift</c> key has to be pressed before pressing the <c>B</c> key. This is the behavior usually expected with
|
||||
/// keyboard shortcuts.
|
||||
///
|
||||
/// If the setting <see cref="InputSettings.shortcutKeysConsumeInput"/> is disabled,
|
||||
/// modifiers can be pressed after the button and the composite will still trigger.
|
||||
///
|
||||
/// This parameter can be used to bypass this behavior and enforce the timing order or allow any timing between <see cref="modifier"/> and <see cref="button"/>.
|
||||
/// The only requirement is for them both to concurrently be in pressed state.
|
||||
/// </remarks>
|
||||
[Tooltip("By default it follows the Input Consumption setting to determine if the modifers keys need to be pressed first.")]
|
||||
public ModifiersOrder modifiersOrder = ModifiersOrder.Default;
|
||||
|
||||
/// <summary>
|
||||
/// Return the value of the <see cref="button"/> part if <see cref="modifier"/> is pressed. Otherwise
|
||||
/// return 0.
|
||||
/// </summary>
|
||||
/// <param name="context">Evaluation context passed in from the input system.</param>
|
||||
/// <returns>The current value of the composite.</returns>
|
||||
public override float ReadValue(ref InputBindingCompositeContext context)
|
||||
{
|
||||
if (ModifierIsPressed(ref context))
|
||||
return context.ReadValue<float>(button);
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private bool ModifierIsPressed(ref InputBindingCompositeContext context)
|
||||
{
|
||||
var modifierDown = context.ReadValueAsButton(modifier);
|
||||
|
||||
if (modifierDown && modifiersOrder == ModifiersOrder.Ordered)
|
||||
{
|
||||
var timestamp = context.GetPressTime(button);
|
||||
var timestamp1 = context.GetPressTime(modifier);
|
||||
|
||||
return timestamp1 <= timestamp;
|
||||
}
|
||||
|
||||
return modifierDown;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Same as <see cref="ReadValue"/> in this case.
|
||||
/// </summary>
|
||||
/// <param name="context">Evaluation context passed in from the input system.</param>
|
||||
/// <returns>A >0 value if the composite is currently actuated.</returns>
|
||||
public override float EvaluateMagnitude(ref InputBindingCompositeContext context)
|
||||
{
|
||||
return ReadValue(ref context);
|
||||
}
|
||||
|
||||
protected override void FinishSetup(ref InputBindingCompositeContext context)
|
||||
{
|
||||
if (modifiersOrder == ModifiersOrder.Default)
|
||||
{
|
||||
// Legacy. We need to reference the obsolete member here so temporarily
|
||||
// turn off the warning.
|
||||
#pragma warning disable CS0618
|
||||
if (overrideModifiersNeedToBePressedFirst)
|
||||
#pragma warning restore CS0618
|
||||
modifiersOrder = ModifiersOrder.Unordered;
|
||||
else
|
||||
modifiersOrder = InputSystem.settings.shortcutKeysConsumeInput ? ModifiersOrder.Ordered : ModifiersOrder.Unordered;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 15164829aab964eedaee6bac785c2c05
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,218 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using UnityEngine.InputSystem.Layouts;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
using UnityEngine.Scripting;
|
||||
|
||||
////TODO: remove this once we can break the API
|
||||
|
||||
namespace UnityEngine.InputSystem.Composites
|
||||
{
|
||||
/// <summary>
|
||||
/// A button with two additional modifiers. The button only triggers when
|
||||
/// both modifiers are pressed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This composite can be used to require two other buttons to be held while
|
||||
/// using the control that triggers the action. This is most commonly used
|
||||
/// on keyboards to require two of the modifier keys (shift, ctrl, or alt)
|
||||
/// to be held in combination with another key, e.g. "CTRL+SHIFT+1".
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Create a button action that triggers when CTRL+SHIFT+1
|
||||
/// // is pressed on the keyboard.
|
||||
/// var action = new InputAction(type: InputActionType.Button);
|
||||
/// action.AddCompositeBinding("TwoModifiers")
|
||||
/// .With("Modifier1", "<Keyboard>/leftCtrl")
|
||||
/// .With("Modifier1", "<Keyboard>/rightCtrl")
|
||||
/// .With("Modifier2", "<Keyboard>/leftShift")
|
||||
/// .With("Modifier2", "<Keyboard>/rightShift")
|
||||
/// .With("Button", "<Keyboard>/1")
|
||||
/// </code>
|
||||
/// </example>
|
||||
///
|
||||
/// Note that this is not restricted to the keyboard and will preserve
|
||||
/// the full value of the button.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Create a button action that requires the A and X button on the
|
||||
/// // gamepad to be held and will then trigger from the gamepad's
|
||||
/// // left trigger button.
|
||||
/// var action = new InputAction(type: InputActionType.Button);
|
||||
/// action.AddCompositeBinding("ButtonWithTwoModifiers")
|
||||
/// .With("Modifier1", "<Gamepad>/buttonSouth")
|
||||
/// .With("Modifier2", "<Gamepad>/buttonWest")
|
||||
/// .With("Button", "<Gamepad>/leftTrigger");
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="ButtonWithOneModifier"/>
|
||||
[DesignTimeVisible(false)] // Obsoleted by TwoModifiersComposite
|
||||
[DisplayStringFormat("{modifier1}+{modifier2}+{button}")]
|
||||
public class ButtonWithTwoModifiers : InputBindingComposite<float>
|
||||
{
|
||||
/// <summary>
|
||||
/// Binding for the first button that acts as a modifier, e.g. <c><Keyboard/leftCtrl</c>.
|
||||
/// </summary>
|
||||
/// <value>Part index to use with <see cref="InputBindingCompositeContext.ReadValue{T}(int)"/>.</value>
|
||||
/// <remarks>
|
||||
/// This property is automatically assigned by the input system.
|
||||
/// </remarks>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Global
|
||||
// ReSharper disable once UnassignedField.Global
|
||||
[InputControl(layout = "Button")] public int modifier1;
|
||||
|
||||
/// <summary>
|
||||
/// Binding for the second button that acts as a modifier, e.g. <c><Keyboard/leftCtrl</c>.
|
||||
/// </summary>
|
||||
/// <value>Part index to use with <see cref="InputBindingCompositeContext.ReadValue{T}(int)"/>.</value>
|
||||
/// <remarks>
|
||||
/// This property is automatically assigned by the input system.
|
||||
/// </remarks>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Global
|
||||
// ReSharper disable once UnassignedField.Global
|
||||
[InputControl(layout = "Button")] public int modifier2;
|
||||
|
||||
/// <summary>
|
||||
/// Binding for the button that is gated by <see cref="modifier1"/> and <see cref="modifier2"/>.
|
||||
/// The composite will assume the value of this button while both of the modifiers are pressed.
|
||||
/// </summary>
|
||||
/// <value>Part index to use with <see cref="InputBindingCompositeContext.ReadValue{T}(int)"/>.</value>
|
||||
/// <remarks>
|
||||
/// This property is automatically assigned by the input system.
|
||||
/// </remarks>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Global
|
||||
// ReSharper disable once UnassignedField.Global
|
||||
[InputControl(layout = "Button")] public int button;
|
||||
|
||||
/// <summary>
|
||||
/// If set to <c>true</c>, <see cref="modifier1"/> and/or <see cref="modifier2"/> can be pressed after <see cref="button"/>
|
||||
/// and the composite will still trigger. Default is false.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default, if the setting <see cref="InputSettings.shortcutKeysConsumeInput"/> is enabled,
|
||||
/// <see cref="modifier1"/> and <see cref="modifier2"/> are required to be in pressed state before or at the same
|
||||
/// time that <see cref="button"/> goes into pressed state for the composite as a whole to trigger. This means that binding to,
|
||||
/// for example, <c>Ctrl+Shift+B</c>, the <c>ctrl</c> and <c>shift</c> keys have to be pressed, in any order, before pressing the <c>B</c> key.
|
||||
/// This is the behavior usually expected with keyboard shortcuts.
|
||||
///
|
||||
/// This parameter can be used to bypass this behavior and allow any timing between <see cref="modifier1"/>, <see cref="modifier2"/>,
|
||||
/// and <see cref="button"/>. The only requirement is for all of them to concurrently be in pressed state.
|
||||
///
|
||||
/// To don't depends on the setting please consider using <see cref="modifiersOrder"/> instead.
|
||||
/// </remarks>
|
||||
[Tooltip("Obsolete please use modifiers Order. If enabled, this will override the Input Consumption setting, allowing the modifier keys to be pressed after the button and the composite will still trigger.")]
|
||||
[Obsolete("Use ModifiersOrder.Unordered with 'modifiersOrder' instead")]
|
||||
public bool overrideModifiersNeedToBePressedFirst;
|
||||
|
||||
/// <summary>
|
||||
/// Determines how a <c>modifiers</c> keys need to be pressed in order or not.
|
||||
/// </summary>
|
||||
public enum ModifiersOrder
|
||||
{
|
||||
/// <summary>
|
||||
/// By default, if the setting <see cref="InputSettings.shortcutKeysConsumeInput"/> is enabled,
|
||||
/// <see cref="modifier1"/> and <see cref="modifier2"/> are required to be in pressed state before or at the same
|
||||
/// time that <see cref="button"/> goes into pressed state for the composite as a whole to trigger. This means that binding to,
|
||||
/// for example, <c>Ctrl+Shift+B</c>, the <c>ctrl</c> and <c>shift</c> keys have to be pressed, in any order, before pressing the <c>B</c> key.
|
||||
/// This is the behavior usually expected with keyboard shortcuts.
|
||||
///
|
||||
/// If the setting <see cref="InputSettings.shortcutKeysConsumeInput"/> is disabled,
|
||||
/// modifiers can be pressed after the button and the composite will still trigger.
|
||||
/// </summary>
|
||||
Default = 0,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="modifier1"/> and <see cref="modifier2"/> are required to be in pressed state before or at the same
|
||||
/// time that <see cref="button"/> goes into pressed state for the composite as a whole to trigger. This means that binding to,
|
||||
/// for example, <c>Ctrl+Shift+B</c>, the <c>ctrl</c> and <c>shift</c> keys have to be pressed, in any order, before pressing the <c>B</c> key.
|
||||
/// This is the behavior usually expected with keyboard shortcuts.
|
||||
/// </summary>
|
||||
Ordered = 1,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="modifier1"/> and/or <see cref="modifier2"/> can be pressed after <see cref="button"/>
|
||||
/// and the composite will still trigger. The only requirement is for all of them to concurrently be in pressed state.
|
||||
/// </summary>
|
||||
Unordered = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If set to <c>Ordered</c> or <c>Unordered</c>, the built-in logic to determine if modifiers need to be pressed first is overridden.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default, if the setting <see cref="InputSettings.shortcutKeysConsumeInput"/> is enabled,
|
||||
/// <see cref="modifier1"/> and <see cref="modifier2"/> are required to be in pressed state before or at the same
|
||||
/// time that <see cref="button"/> goes into pressed state for the composite as a whole to trigger. This means that binding to,
|
||||
/// for example, <c>Ctrl+Shift+B</c>, the <c>ctrl</c> and <c>shift</c> keys have to be pressed, in any order, before pressing the <c>B</c> key.
|
||||
/// This is the behavior usually expected with keyboard shortcuts.
|
||||
///
|
||||
/// If the setting <see cref="InputSettings.shortcutKeysConsumeInput"/> is disabled,
|
||||
/// modifiers can be pressed after the button and the composite will still trigger.
|
||||
///
|
||||
/// This field allows you to explicitly override this default inference.
|
||||
/// </remarks>
|
||||
[Tooltip("By default it follows the Input Consumption setting to determine if the modifers keys need to be pressed first.")]
|
||||
public ModifiersOrder modifiersOrder = ModifiersOrder.Default;
|
||||
|
||||
/// <summary>
|
||||
/// Return the value of the <see cref="button"/> part while both <see cref="modifier1"/> and <see cref="modifier2"/>
|
||||
/// are pressed. Otherwise return 0.
|
||||
/// </summary>
|
||||
/// <param name="context">Evaluation context passed in from the input system.</param>
|
||||
/// <returns>The current value of the composite.</returns>
|
||||
public override float ReadValue(ref InputBindingCompositeContext context)
|
||||
{
|
||||
if (ModifiersArePressed(ref context))
|
||||
return context.ReadValue<float>(button);
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private bool ModifiersArePressed(ref InputBindingCompositeContext context)
|
||||
{
|
||||
var modifiersDown = context.ReadValueAsButton(modifier1) && context.ReadValueAsButton(modifier2);
|
||||
|
||||
if (modifiersDown && modifiersOrder == ModifiersOrder.Ordered)
|
||||
{
|
||||
var timestamp = context.GetPressTime(button);
|
||||
var timestamp1 = context.GetPressTime(modifier1);
|
||||
var timestamp2 = context.GetPressTime(modifier2);
|
||||
|
||||
return timestamp1 <= timestamp && timestamp2 <= timestamp;
|
||||
}
|
||||
|
||||
return modifiersDown;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Same as <see cref="ReadValue"/> in this case.
|
||||
/// </summary>
|
||||
/// <param name="context">Evaluation context passed in from the input system.</param>
|
||||
/// <returns>A >0 value if the composite is currently actuated.</returns>
|
||||
public override float EvaluateMagnitude(ref InputBindingCompositeContext context)
|
||||
{
|
||||
return ReadValue(ref context);
|
||||
}
|
||||
|
||||
protected override void FinishSetup(ref InputBindingCompositeContext context)
|
||||
{
|
||||
if (modifiersOrder == ModifiersOrder.Default)
|
||||
{
|
||||
// Legacy. We need to reference the obsolete member here so temporarily
|
||||
// turn off the warning.
|
||||
#pragma warning disable CS0618
|
||||
if (overrideModifiersNeedToBePressedFirst)
|
||||
#pragma warning restore CS0618
|
||||
modifiersOrder = ModifiersOrder.Unordered;
|
||||
else
|
||||
modifiersOrder = InputSystem.settings.shortcutKeysConsumeInput ? ModifiersOrder.Ordered : ModifiersOrder.Unordered;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 62dcc18c42c2246bdaed7fe210b77118
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,254 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine.InputSystem.Layouts;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
using UnityEngine.Scripting;
|
||||
|
||||
////TODO: allow making modifier optional; maybe alter the value (e.g. 0=unpressed, 0.5=pressed without modifier, 1=pressed with modifier)
|
||||
|
||||
namespace UnityEngine.InputSystem.Composites
|
||||
{
|
||||
/// <summary>
|
||||
/// A binding with an additional modifier. The bound controls only trigger when
|
||||
/// the modifier is pressed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This composite can be used to require a button to be held in order to "activate"
|
||||
/// another binding. This is most commonly used on keyboards to require one of the
|
||||
/// modifier keys (shift, ctrl, or alt) to be held in combination with another control,
|
||||
/// e.g. "CTRL+1".
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Create a button action that triggers when CTRL+1
|
||||
/// // is pressed on the keyboard.
|
||||
/// var action = new InputAction(type: InputActionType.Button);
|
||||
/// action.AddCompositeBinding("OneModifier")
|
||||
/// .With("Modifier", "<Keyboard>/ctrl")
|
||||
/// .With("Binding", "<Keyboard>/1")
|
||||
/// </code>
|
||||
/// </example>
|
||||
///
|
||||
/// However, this can also be used to "gate" other types of controls. For example, a "look"
|
||||
/// action could be bound to mouse <see cref="Pointer.delta"/> such that the <see cref="Keyboard.altKey"/> on the
|
||||
/// keyboard has to be pressed in order for the player to be able to look around.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// lookAction.AddCompositeBinding("OneModifier")
|
||||
/// .With("Modifier", "<Keyboard>/alt")
|
||||
/// .With("Binding", "<Mouse>/delta")
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="TwoModifiersComposite"/>
|
||||
[DisplayStringFormat("{modifier}+{binding}")]
|
||||
[DisplayName("Binding With One Modifier")]
|
||||
public class OneModifierComposite : InputBindingComposite
|
||||
{
|
||||
/// <summary>
|
||||
/// Binding for the button that acts as a modifier, e.g. <c><Keyboard/ctrl</c>.
|
||||
/// </summary>
|
||||
/// <value>Part index to use with <see cref="InputBindingCompositeContext.ReadValue{T}(int)"/>.</value>
|
||||
/// <remarks>
|
||||
/// This property is automatically assigned by the input system.
|
||||
/// </remarks>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Global
|
||||
// ReSharper disable once UnassignedField.Global
|
||||
[InputControl(layout = "Button")] public int modifier;
|
||||
|
||||
/// <summary>
|
||||
/// Binding for the control that is gated by the modifier. The composite will assume the value
|
||||
/// of this control while the modifier is considered pressed (that is, has a magnitude equal to or
|
||||
/// greater than the button press point).
|
||||
/// </summary>
|
||||
/// <value>Part index to use with <see cref="InputBindingCompositeContext.ReadValue{T}(int)"/>.</value>
|
||||
/// <remarks>
|
||||
/// This property is automatically assigned by the input system.
|
||||
/// </remarks>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Global
|
||||
// ReSharper disable once UnassignedField.Global
|
||||
[InputControl] public int binding;
|
||||
|
||||
/// <summary>
|
||||
/// Type of values read from controls bound to <see cref="binding"/>.
|
||||
/// </summary>
|
||||
public override Type valueType => m_ValueType;
|
||||
|
||||
/// <summary>
|
||||
/// Size of the largest value that may be read from the controls bound to <see cref="binding"/>.
|
||||
/// </summary>
|
||||
public override int valueSizeInBytes => m_ValueSizeInBytes;
|
||||
|
||||
/// <summary>
|
||||
/// If set to <c>true</c>, the built-in logic to determine if modifiers need to be pressed first is overridden.
|
||||
/// Default value is <c>false</c>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default, if the setting <see cref="InputSettings.shortcutKeysConsumeInput"/> is enabled,
|
||||
/// if <see cref="binding"/> is bound to only <see cref="Controls.ButtonControl"/>s, then the composite requires
|
||||
/// <see cref="modifier"/> to be pressed <em>before</em> pressing <see cref="binding"/>. This means that binding to, for example,
|
||||
/// <c>Ctrl+B</c>, the <c>ctrl</c> keys have to be pressed before pressing the <c>B</c> key. This is the behavior usually expected
|
||||
/// with keyboard shortcuts.
|
||||
///
|
||||
/// However, when binding, for example, <c>Ctrl+MouseDelta</c>, it should be possible to press <c>ctrl</c> at any time. The default
|
||||
/// logic will automatically detect the difference between this binding and the button binding in the example above and behave
|
||||
/// accordingly.
|
||||
///
|
||||
/// This field allows you to explicitly override this default inference and make it so that regardless of what <see cref="binding"/>
|
||||
/// is bound to, any press sequence is acceptable. For the example binding to <c>Ctrl+B</c>, it would mean that pressing <c>B</c> and
|
||||
/// only then pressing <c>Ctrl</c> will still trigger the binding.
|
||||
///
|
||||
/// To don't depends on the setting please consider using <see cref="modifiersOrder"/> instead.
|
||||
/// </remarks>
|
||||
[Tooltip("Obsolete please use modifiers Order. If enabled, this will override the Input Consumption setting, allowing the modifier keys to be pressed after the button and the composite will still trigger.")]
|
||||
[Obsolete("Use ModifiersOrder.Unordered with 'modifiersOrder' instead")]
|
||||
public bool overrideModifiersNeedToBePressedFirst;
|
||||
|
||||
/// <summary>
|
||||
/// Determines how a <c>modifiers</c> keys need to be pressed in order or not.
|
||||
/// </summary>
|
||||
public enum ModifiersOrder
|
||||
{
|
||||
/// <summary>
|
||||
/// By default, if the setting <see cref="InputSettings.shortcutKeysConsumeInput"/> is enabled,
|
||||
/// if <see cref="binding"/> is bound to only <see cref="Controls.ButtonControl"/>s, then the composite requires
|
||||
/// <see cref="modifier"/> to be pressed <em>before</em> pressing <see cref="binding"/>. This means that binding to, for example,
|
||||
/// <c>Ctrl+B</c>, the <c>ctrl</c> keys have to be pressed before pressing the <c>B</c> key. This is the behavior usually expected
|
||||
/// with keyboard shortcuts.
|
||||
///
|
||||
/// If the setting <see cref="InputSettings.shortcutKeysConsumeInput"/> is disabled,
|
||||
/// modifiers can be pressed after the button and the composite will still trigger.
|
||||
/// </summary>
|
||||
Default = 0,
|
||||
|
||||
/// <summary>
|
||||
/// if <see cref="binding"/> is bound to only <see cref="Controls.ButtonControl"/>s, then the composite requires
|
||||
/// <see cref="modifier"/> to be pressed <em>before</em> pressing <see cref="binding"/>. This means that binding to, for example,
|
||||
/// <c>Ctrl+B</c>, the <c>ctrl</c> key have to be pressed before pressing the <c>B</c> key. This is the behavior usually expected
|
||||
/// with keyboard shortcuts.
|
||||
/// </summary>
|
||||
Ordered = 1,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="modifier"/> can be pressed after <see cref="binding"/>
|
||||
/// and the composite will still trigger. The only requirement is for all of them to concurrently be in pressed state.
|
||||
/// </summary>
|
||||
Unordered = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If set to <c>Ordered</c> or <c>Unordered</c>, the built-in logic to determine if modifiers need to be pressed first is overridden.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default, if the setting <see cref="InputSettings.shortcutKeysConsumeInput"/> is enabled,
|
||||
/// if <see cref="binding"/> is bound to only <see cref="Controls.ButtonControl"/>s, then the composite requires
|
||||
/// <see cref="modifier"/> to be pressed <em>before</em> pressing <see cref="binding"/>. This means that binding to, for example,
|
||||
/// <c>Ctrl+B</c>, the <c>ctrl</c> keys have to be pressed before pressing the <c>B</c> key. This is the behavior usually expected
|
||||
/// with keyboard shortcuts.
|
||||
///
|
||||
/// If the setting <see cref="InputSettings.shortcutKeysConsumeInput"/> is disabled,
|
||||
/// modifiers can be pressed after the button and the composite will still trigger.
|
||||
///
|
||||
/// However, when binding, for example, <c>Ctrl+MouseDelta</c>, it should be possible to press <c>ctrl</c> at any time. The default
|
||||
/// logic will automatically detect the difference between this binding and the button binding in the example above and behave
|
||||
/// accordingly.
|
||||
///
|
||||
/// This field allows you to explicitly override this default inference and make the order mandatory or make it so that regardless of what <see cref="binding"/>
|
||||
/// is bound to, any press sequence is acceptable. For the example binding to <c>Ctrl+B</c>, it would mean that pressing <c>B</c> and
|
||||
/// only then pressing <c>Ctrl</c> will still trigger the binding.
|
||||
/// </remarks>
|
||||
[Tooltip("By default it follows the Input Consumption setting to determine if the modifers keys need to be pressed first.")]
|
||||
public ModifiersOrder modifiersOrder = ModifiersOrder.Default;
|
||||
|
||||
private int m_ValueSizeInBytes;
|
||||
private Type m_ValueType;
|
||||
private bool m_BindingIsButton;
|
||||
|
||||
public override float EvaluateMagnitude(ref InputBindingCompositeContext context)
|
||||
{
|
||||
if (ModifierIsPressed(ref context))
|
||||
return context.EvaluateMagnitude(binding);
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override unsafe void ReadValue(ref InputBindingCompositeContext context, void* buffer, int bufferSize)
|
||||
{
|
||||
if (ModifierIsPressed(ref context))
|
||||
context.ReadValue(binding, buffer, bufferSize);
|
||||
else
|
||||
UnsafeUtility.MemClear(buffer, m_ValueSizeInBytes);
|
||||
}
|
||||
|
||||
private bool ModifierIsPressed(ref InputBindingCompositeContext context)
|
||||
{
|
||||
var modifierDown = context.ReadValueAsButton(modifier);
|
||||
|
||||
// When the modifiers are gating a button, we require the modifiers to be pressed *first*.
|
||||
if (modifierDown && m_BindingIsButton && modifiersOrder == ModifiersOrder.Ordered)
|
||||
{
|
||||
var timestamp = context.GetPressTime(binding);
|
||||
var timestamp1 = context.GetPressTime(modifier);
|
||||
|
||||
return timestamp1 <= timestamp;
|
||||
}
|
||||
|
||||
return modifierDown;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void FinishSetup(ref InputBindingCompositeContext context)
|
||||
{
|
||||
DetermineValueTypeAndSize(ref context, binding, out m_ValueType, out m_ValueSizeInBytes, out m_BindingIsButton);
|
||||
|
||||
if (modifiersOrder == ModifiersOrder.Default)
|
||||
{
|
||||
// Legacy. We need to reference the obsolete member here so temporarily
|
||||
// turn off the warning.
|
||||
#pragma warning disable CS0618
|
||||
if (overrideModifiersNeedToBePressedFirst)
|
||||
#pragma warning restore CS0618
|
||||
modifiersOrder = ModifiersOrder.Unordered;
|
||||
else
|
||||
modifiersOrder = InputSystem.settings.shortcutKeysConsumeInput ? ModifiersOrder.Ordered : ModifiersOrder.Unordered;
|
||||
}
|
||||
}
|
||||
|
||||
public override object ReadValueAsObject(ref InputBindingCompositeContext context)
|
||||
{
|
||||
if (context.ReadValueAsButton(modifier))
|
||||
return context.ReadValueAsObject(binding);
|
||||
return null;
|
||||
}
|
||||
|
||||
internal static void DetermineValueTypeAndSize(ref InputBindingCompositeContext context, int part, out Type valueType, out int valueSizeInBytes, out bool isButton)
|
||||
{
|
||||
valueSizeInBytes = 0;
|
||||
isButton = true;
|
||||
|
||||
Type type = null;
|
||||
foreach (var control in context.controls)
|
||||
{
|
||||
if (control.part != part)
|
||||
continue;
|
||||
|
||||
var controlType = control.control.valueType;
|
||||
if (type == null || controlType.IsAssignableFrom(type))
|
||||
type = controlType;
|
||||
else if (!type.IsAssignableFrom(controlType))
|
||||
type = typeof(Object);
|
||||
|
||||
valueSizeInBytes = Math.Max(control.control.valueSizeInBytes, valueSizeInBytes);
|
||||
|
||||
// *All* bound controls need to be buttons for us to classify this part as a "Button" part.
|
||||
isButton &= control.control.isButton;
|
||||
}
|
||||
|
||||
valueType = type;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a0a9c8a3d9c4893ab5389e009563314
|
||||
timeCreated: 1588685554
|
||||
@@ -0,0 +1,238 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine.InputSystem.Layouts;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
|
||||
namespace UnityEngine.InputSystem.Composites
|
||||
{
|
||||
/// <summary>
|
||||
/// A binding with two additional modifiers modifier. The bound controls only trigger when
|
||||
/// both modifiers are pressed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This composite can be used to require two buttons to be held in order to "activate"
|
||||
/// another binding. This is most commonly used on keyboards to require two of the
|
||||
/// modifier keys (shift, ctrl, or alt) to be held in combination with another control,
|
||||
/// e.g. "SHIFT+CTRL+1".
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Create a button action that triggers when SHIFT+CTRL+1
|
||||
/// // is pressed on the keyboard.
|
||||
/// var action = new InputAction(type: InputActionType.Button);
|
||||
/// action.AddCompositeBinding("TwoModifiers")
|
||||
/// .With("Modifier", "<Keyboard>/ctrl")
|
||||
/// .With("Modifier", "<Keyboard>/shift")
|
||||
/// .With("Binding", "<Keyboard>/1")
|
||||
/// </code>
|
||||
/// </example>
|
||||
///
|
||||
/// However, this can also be used to "gate" other types of controls. For example, a "look"
|
||||
/// action could be bound to mouse <see cref="Pointer.delta"/> such that the <see cref="Keyboard.ctrlKey"/> and
|
||||
/// <see cref="Keyboard.shiftKey"/> on the keyboard have to be pressed in order for the player to be able to
|
||||
/// look around.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var action = new InputAction();
|
||||
/// action.AddCompositeBinding("TwoModifiers")
|
||||
/// .With("Modifier1", "<Keyboard>/ctrl")
|
||||
/// .With("Modifier2", "<Keyboard>/shift")
|
||||
/// .With("Binding", "<Mouse>/delta");
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="OneModifierComposite"/>
|
||||
[DisplayStringFormat("{modifier1}+{modifier2}+{binding}")]
|
||||
[DisplayName("Binding With Two Modifiers")]
|
||||
public class TwoModifiersComposite : InputBindingComposite
|
||||
{
|
||||
/// <summary>
|
||||
/// Binding for the first button that acts as a modifier, e.g. <c><Keyboard/leftCtrl</c>.
|
||||
/// </summary>
|
||||
/// <value>Part index to use with <see cref="InputBindingCompositeContext.ReadValue{T}(int)"/>.</value>
|
||||
/// <remarks>
|
||||
/// This property is automatically assigned by the input system.
|
||||
/// </remarks>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Global
|
||||
// ReSharper disable once UnassignedField.Global
|
||||
[InputControl(layout = "Button")] public int modifier1;
|
||||
|
||||
/// <summary>
|
||||
/// Binding for the second button that acts as a modifier, e.g. <c><Keyboard/leftCtrl</c>.
|
||||
/// </summary>
|
||||
/// <value>Part index to use with <see cref="InputBindingCompositeContext.ReadValue{T}(int)"/>.</value>
|
||||
/// <remarks>
|
||||
/// This property is automatically assigned by the input system.
|
||||
/// </remarks>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Global
|
||||
// ReSharper disable once UnassignedField.Global
|
||||
[InputControl(layout = "Button")] public int modifier2;
|
||||
|
||||
/// <summary>
|
||||
/// Binding for the control that is gated by <see cref="modifier1"/> and <see cref="modifier2"/>.
|
||||
/// The composite will assume the value of this button while both of the modifiers are pressed.
|
||||
/// </summary>
|
||||
/// <value>Part index to use with <see cref="InputBindingCompositeContext.ReadValue{T}(int)"/>.</value>
|
||||
/// <remarks>
|
||||
/// This property is automatically assigned by the input system.
|
||||
/// </remarks>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Global
|
||||
// ReSharper disable once UnassignedField.Global
|
||||
[InputControl] public int binding;
|
||||
|
||||
/// <summary>
|
||||
/// If set to <c>true</c>, the built-in logic to determine if modifiers need to be pressed first is overridden.
|
||||
/// Default value is <c>false</c>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default, if the setting <see cref="InputSettings.shortcutKeysConsumeInput"/> is enabled,
|
||||
/// if <see cref="binding"/> is bound to only <see cref="Controls.ButtonControl"/>s, then the composite requires
|
||||
/// both <see cref="modifier1"/> and <see cref="modifier2"/> to be pressed <em>before</em> pressing <see cref="binding"/>.
|
||||
/// This means that binding to, for example, <c>Ctrl+Shift+B</c>, the <c>ctrl</c> and <c>shift</c> keys have to be pressed, in any order,
|
||||
/// before pressing the <c>B</c> key. This is the behavior usually expected with keyboard shortcuts.
|
||||
///
|
||||
/// However, when binding, for example, <c>Ctrl+Shift+MouseDelta</c>, it should be possible to press <c>ctrl</c> and <c>shift</c>
|
||||
/// at any time and in any order. The default logic will automatically detect the difference between this binding and the button
|
||||
/// binding in the example above and behave accordingly.
|
||||
///
|
||||
/// This field allows you to explicitly override this default inference and make it so that regardless of what <see cref="binding"/>
|
||||
/// is bound to, any press sequence is acceptable. For the example binding to <c>Ctrl+Shift+B</c>, it would mean that pressing
|
||||
/// <c>B</c> and only then pressing <c>Ctrl</c> and <c>Shift</c> will still trigger the binding.
|
||||
///
|
||||
/// To don't depends on the setting please consider using <see cref="modifiersOrder"/> instead.
|
||||
/// </remarks>
|
||||
[Tooltip("Obsolete please use modifiers Order. If enabled, this will override the Input Consumption setting, allowing the modifier keys to be pressed after the button and the composite will still trigger.")]
|
||||
[Obsolete("Use ModifiersOrder.Unordered with 'modifiersOrder' instead")]
|
||||
public bool overrideModifiersNeedToBePressedFirst;
|
||||
|
||||
/// <summary>
|
||||
/// Determines how a <c>modifiers</c> keys need to be pressed in order or not.
|
||||
/// </summary>
|
||||
public enum ModifiersOrder
|
||||
{
|
||||
/// <summary>
|
||||
/// By default, if the setting <see cref="InputSettings.shortcutKeysConsumeInput"/> is enabled,
|
||||
/// if <see cref="binding"/> is bound to only <see cref="Controls.ButtonControl"/>s, then the composite requires
|
||||
/// both <see cref="modifier1"/> and <see cref="modifier2"/> to be pressed <em>before</em> pressing <see cref="binding"/>.
|
||||
/// This means that binding to, for example, <c>Ctrl+Shift+B</c>, the <c>ctrl</c> and <c>shift</c> keys have to be pressed, in any order,
|
||||
/// before pressing the <c>B</c> key. This is the behavior usually expected with keyboard shortcuts.
|
||||
///
|
||||
/// If the setting <see cref="InputSettings.shortcutKeysConsumeInput"/> is disabled,
|
||||
/// modifiers can be pressed after the button and the composite will still trigger.
|
||||
/// </summary>
|
||||
Default = 0,
|
||||
|
||||
/// <summary>
|
||||
/// if <see cref = "binding" /> is bound to only <see cref = "Controls.ButtonControl" /> s, then the composite requires
|
||||
/// both <see cref="modifier1"/> and <see cref="modifier2"/> to be pressed <em>before</em> pressing <see cref="binding"/>.
|
||||
/// This means that binding to, for example, <c>Ctrl+Shift+B</c>, the <c>ctrl</c> and <c>shift</c> keys have to be pressed, in any order,
|
||||
/// before pressing the <c>B</c> key. This is the behavior usually expected with keyboard shortcuts.
|
||||
/// </summary>
|
||||
Ordered = 1,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="modifier1"/> and/or <see cref="modifier2"/> can be pressed after <see cref="binding"/>
|
||||
/// and the composite will still trigger. The only requirement is for all of them to concurrently be in pressed state.
|
||||
/// </summary>
|
||||
Unordered = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If set to <c>Ordered</c> or <c>Unordered</c>, the built-in logic to determine if modifiers need to be pressed first is overridden.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default, if the setting <see cref="InputSettings.shortcutKeysConsumeInput"/> is enabled,
|
||||
/// if <see cref="binding"/> is bound to only <see cref="Controls.ButtonControl"/>s, then the composite requires
|
||||
/// both <see cref="modifier1"/> and <see cref="modifier2"/> to be pressed <em>before</em> pressing <see cref="binding"/>.
|
||||
/// This means that binding to, for example, <c>Ctrl+Shift+B</c>, the <c>ctrl</c> and <c>shift</c> keys have to be pressed, in any order,
|
||||
/// before pressing the <c>B</c> key. This is the behavior usually expected with keyboard shortcuts.
|
||||
///
|
||||
/// If the setting <see cref="InputSettings.shortcutKeysConsumeInput"/> is disabled,
|
||||
/// modifiers can be pressed after the button and the composite will still trigger.
|
||||
///
|
||||
/// This field allows you to explicitly override this default inference and make the order mandatory or make it so that regardless of what <see cref="binding"/>
|
||||
/// is bound to, any press sequence is acceptable. For the example binding to <c>Ctrl+Shift+B</c>, it would mean that pressing
|
||||
/// <c>B</c> and only then pressing <c>Ctrl</c> and <c>Shift</c> will still trigger the binding.
|
||||
///
|
||||
/// </remarks>
|
||||
[Tooltip("By default it follows the Input Consumption setting to determine if the modifers keys need to be pressed first.")]
|
||||
public ModifiersOrder modifiersOrder = ModifiersOrder.Default;
|
||||
|
||||
/// <summary>
|
||||
/// Type of values read from controls bound to <see cref="binding"/>.
|
||||
/// </summary>
|
||||
public override Type valueType => m_ValueType;
|
||||
|
||||
/// <summary>
|
||||
/// Size of the largest value that may be read from the controls bound to <see cref="binding"/>.
|
||||
/// </summary>
|
||||
public override int valueSizeInBytes => m_ValueSizeInBytes;
|
||||
|
||||
private int m_ValueSizeInBytes;
|
||||
private Type m_ValueType;
|
||||
private bool m_BindingIsButton;
|
||||
|
||||
public override float EvaluateMagnitude(ref InputBindingCompositeContext context)
|
||||
{
|
||||
if (ModifiersArePressed(ref context))
|
||||
return context.EvaluateMagnitude(binding);
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override unsafe void ReadValue(ref InputBindingCompositeContext context, void* buffer, int bufferSize)
|
||||
{
|
||||
if (ModifiersArePressed(ref context))
|
||||
context.ReadValue(binding, buffer, bufferSize);
|
||||
else
|
||||
UnsafeUtility.MemClear(buffer, m_ValueSizeInBytes);
|
||||
}
|
||||
|
||||
private bool ModifiersArePressed(ref InputBindingCompositeContext context)
|
||||
{
|
||||
var modifiersDown = context.ReadValueAsButton(modifier1) && context.ReadValueAsButton(modifier2);
|
||||
|
||||
// When the modifiers are gating a button, we require the modifiers to be pressed *first*.
|
||||
if (modifiersDown && m_BindingIsButton && modifiersOrder == ModifiersOrder.Ordered)
|
||||
{
|
||||
var timestamp = context.GetPressTime(binding);
|
||||
var timestamp1 = context.GetPressTime(modifier1);
|
||||
var timestamp2 = context.GetPressTime(modifier2);
|
||||
|
||||
return timestamp1 <= timestamp && timestamp2 <= timestamp;
|
||||
}
|
||||
|
||||
return modifiersDown;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void FinishSetup(ref InputBindingCompositeContext context)
|
||||
{
|
||||
OneModifierComposite.DetermineValueTypeAndSize(ref context, binding, out m_ValueType, out m_ValueSizeInBytes, out m_BindingIsButton);
|
||||
|
||||
if (modifiersOrder == ModifiersOrder.Default)
|
||||
{
|
||||
// Legacy. We need to reference the obsolete member here so temporarily
|
||||
// turn off the warning.
|
||||
#pragma warning disable CS0618
|
||||
if (overrideModifiersNeedToBePressedFirst)
|
||||
#pragma warning restore CS0618
|
||||
modifiersOrder = ModifiersOrder.Unordered;
|
||||
else
|
||||
modifiersOrder = InputSystem.settings.shortcutKeysConsumeInput ? ModifiersOrder.Ordered : ModifiersOrder.Unordered;
|
||||
}
|
||||
}
|
||||
|
||||
public override object ReadValueAsObject(ref InputBindingCompositeContext context)
|
||||
{
|
||||
if (context.ReadValueAsButton(modifier1) && context.ReadValueAsButton(modifier2))
|
||||
return context.ReadValueAsObject(binding);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: be272fdf97df4e8daf1393dabdfa4cb8
|
||||
timeCreated: 1588685577
|
||||
@@ -0,0 +1,228 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using UnityEngine.InputSystem.Controls;
|
||||
using UnityEngine.InputSystem.Layouts;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEngine.InputSystem.Editor;
|
||||
using UnityEngine.UIElements;
|
||||
#endif
|
||||
|
||||
////TODO: add support for ramp up/down
|
||||
|
||||
namespace UnityEngine.InputSystem.Composites
|
||||
{
|
||||
/// <summary>
|
||||
/// A 2D planar motion vector computed from an up+down button pair and a left+right
|
||||
/// button pair.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This composite allows to grab arbitrary buttons from a device and arrange them in
|
||||
/// a D-Pad like configuration. Based on button presses, the composite will return a
|
||||
/// normalized direction vector (normalization can be turned off via <see cref="mode"/>).
|
||||
///
|
||||
/// Opposing motions cancel each other out. This means that if, for example, both the left
|
||||
/// and right horizontal button are pressed, the resulting horizontal movement value will
|
||||
/// be zero.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Set up WASD style keyboard controls.
|
||||
/// action.AddCompositeBinding("2DVector")
|
||||
/// .With("Up", "<Keyboard>/w")
|
||||
/// .With("Left", "<Keyboard>/a")
|
||||
/// .With("Down", "<Keyboard>/s")
|
||||
/// .With("Right", "<Keyboard>/d");
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="Vector3Composite"/>
|
||||
[DisplayStringFormat("{up}/{left}/{down}/{right}")] // This results in WASD.
|
||||
[DisplayName("Up/Down/Left/Right Composite")]
|
||||
public class Vector2Composite : InputBindingComposite<Vector2>
|
||||
{
|
||||
/// <summary>
|
||||
/// Binding for the button that represents the up (that is, <c>(0,1)</c>) direction of the vector.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property is automatically assigned by the input system.
|
||||
/// </remarks>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Global
|
||||
[InputControl(layout = "Axis")] public int up;
|
||||
|
||||
/// <summary>
|
||||
/// Binding for the button represents the down (that is, <c>(0,-1)</c>) direction of the vector.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property is automatically assigned by the input system.
|
||||
/// </remarks>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Global
|
||||
[InputControl(layout = "Axis")] public int down;
|
||||
|
||||
/// <summary>
|
||||
/// Binding for the button represents the left (that is, <c>(-1,0)</c>) direction of the vector.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property is automatically assigned by the input system.
|
||||
/// </remarks>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Global
|
||||
[InputControl(layout = "Axis")] public int left;
|
||||
|
||||
/// <summary>
|
||||
/// Binding for the button that represents the right (that is, <c>(1,0)</c>) direction of the vector.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property is automatically assigned by the input system.
|
||||
/// </remarks>
|
||||
[InputControl(layout = "Axis")] public int right;
|
||||
|
||||
[Obsolete("Use Mode.DigitalNormalized with 'mode' instead")]
|
||||
public bool normalize = true;
|
||||
|
||||
/// <summary>
|
||||
/// How to synthesize a <c>Vector2</c> from the values read from <see cref="up"/>, <see cref="down"/>,
|
||||
/// <see cref="left"/>, and <see cref="right"/>.
|
||||
/// </summary>
|
||||
/// <value>Determines how X and Y of the resulting <c>Vector2</c> are formed from input values.</value>
|
||||
/// <remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var action = new InputAction();
|
||||
///
|
||||
/// // DigitalNormalized composite (the default). Turns gamepad left stick into
|
||||
/// // control equivalent to the D-Pad.
|
||||
/// action.AddCompositeBinding("2DVector(mode=0)")
|
||||
/// .With("up", "<Gamepad>/leftStick/up")
|
||||
/// .With("down", "<Gamepad>/leftStick/down")
|
||||
/// .With("left", "<Gamepad>/leftStick/left")
|
||||
/// .With("right", "<Gamepad>/leftStick/right");
|
||||
///
|
||||
/// // Digital composite. Turns gamepad left stick into control equivalent
|
||||
/// // to the D-Pad except that diagonals will not be normalized.
|
||||
/// action.AddCompositeBinding("2DVector(mode=1)")
|
||||
/// .With("up", "<Gamepad>/leftStick/up")
|
||||
/// .With("down", "<Gamepad>/leftStick/down")
|
||||
/// .With("left", "<Gamepad>/leftStick/left")
|
||||
/// .With("right", "<Gamepad>/leftStick/right");
|
||||
///
|
||||
/// // Analog composite. In this case results in setup that behaves exactly
|
||||
/// // the same as leftStick already does. But you could use it, for example,
|
||||
/// // to swap directions by binding "up" to leftStick/down and "down" to
|
||||
/// // leftStick/up.
|
||||
/// action.AddCompositeBinding("2DVector(mode=2)")
|
||||
/// .With("up", "<Gamepad>/leftStick/up")
|
||||
/// .With("down", "<Gamepad>/leftStick/down")
|
||||
/// .With("left", "<Gamepad>/leftStick/left")
|
||||
/// .With("right", "<Gamepad>/leftStick/right");
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
public Mode mode;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Vector2 ReadValue(ref InputBindingCompositeContext context)
|
||||
{
|
||||
var mode = this.mode;
|
||||
|
||||
if (mode == Mode.Analog)
|
||||
{
|
||||
var upValue = context.ReadValue<float>(up);
|
||||
var downValue = context.ReadValue<float>(down);
|
||||
var leftValue = context.ReadValue<float>(left);
|
||||
var rightValue = context.ReadValue<float>(right);
|
||||
|
||||
return DpadControl.MakeDpadVector(upValue, downValue, leftValue, rightValue);
|
||||
}
|
||||
|
||||
var upIsPressed = context.ReadValueAsButton(up);
|
||||
var downIsPressed = context.ReadValueAsButton(down);
|
||||
var leftIsPressed = context.ReadValueAsButton(left);
|
||||
var rightIsPressed = context.ReadValueAsButton(right);
|
||||
|
||||
// Legacy. We need to reference the obsolete member here so temporarily
|
||||
// turn off the warning.
|
||||
#pragma warning disable CS0618
|
||||
if (!normalize) // Was on by default.
|
||||
mode = Mode.Digital;
|
||||
#pragma warning restore CS0618
|
||||
|
||||
return DpadControl.MakeDpadVector(upIsPressed, downIsPressed, leftIsPressed, rightIsPressed, mode == Mode.DigitalNormalized);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override float EvaluateMagnitude(ref InputBindingCompositeContext context)
|
||||
{
|
||||
var value = ReadValue(ref context);
|
||||
return value.magnitude;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines how a <c>Vector2</c> is synthesized from part controls.
|
||||
/// </summary>
|
||||
public enum Mode
|
||||
{
|
||||
/// <summary>
|
||||
/// Part controls are treated as analog meaning that the floating-point values read from controls
|
||||
/// will come through as is (minus the fact that the down and left direction values are negated).
|
||||
/// </summary>
|
||||
Analog = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Part controls are treated as buttons (on/off) and the resulting vector is normalized. This means
|
||||
/// that if, for example, both left and up are pressed, instead of returning a vector (-1,1), a vector
|
||||
/// of roughly (-0.7,0.7) (that is, corresponding to <c>new Vector2(-1,1).normalized</c>) is returned instead.
|
||||
/// The resulting 2D area is diamond-shaped.
|
||||
/// </summary>
|
||||
DigitalNormalized = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Part controls are treated as buttons (on/off) and the resulting vector is not normalized. This means
|
||||
/// that if, for example, both left and up are pressed, the resulting vector is (-1,1) and has a length
|
||||
/// greater than 1. The resulting 2D area is box-shaped.
|
||||
/// </summary>
|
||||
Digital = 1
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
internal class Vector2CompositeEditor : InputParameterEditor<Vector2Composite>
|
||||
{
|
||||
private GUIContent m_ModeLabel = new GUIContent("Mode",
|
||||
"How to synthesize a Vector2 from the inputs. Digital "
|
||||
+ "treats part bindings as buttons (on/off) whereas Analog preserves "
|
||||
+ "floating-point magnitudes as read from controls.");
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
|
||||
if (!InputSystem.settings.useIMGUIEditorForAssets) return;
|
||||
#endif
|
||||
target.mode = (Vector2Composite.Mode)EditorGUILayout.EnumPopup(m_ModeLabel, target.mode);
|
||||
}
|
||||
|
||||
#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
|
||||
public override void OnDrawVisualElements(VisualElement root, Action onChangedCallback)
|
||||
{
|
||||
var modeField = new EnumField(m_ModeLabel.text, target.mode)
|
||||
{
|
||||
tooltip = m_ModeLabel.tooltip
|
||||
};
|
||||
|
||||
modeField.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
target.mode = (Vector2Composite.Mode)evt.newValue;
|
||||
onChangedCallback();
|
||||
});
|
||||
|
||||
root.Add(modeField);
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4e9c0b68df9234a0bad68ad7e0391330
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,208 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using UnityEngine.InputSystem.Layouts;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEngine.InputSystem.Editor;
|
||||
using UnityEngine.UIElements;
|
||||
#endif
|
||||
|
||||
namespace UnityEngine.InputSystem.Composites
|
||||
{
|
||||
/// <summary>
|
||||
/// A 3D vector formed from six floating-point inputs.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Depending on the setting of <see cref="mode"/>, the vector is either in the [-1..1]
|
||||
/// range on each axis (normalized or not depending on <see cref="mode"/>) or is in the
|
||||
/// full value range of the input controls.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// action.AddCompositeBinding("3DVector")
|
||||
/// .With("Forward", "<Keyboard>/w")
|
||||
/// .With("Backward", "<Keyboard>/s")
|
||||
/// .With("Left", "<Keyboard>/a")
|
||||
/// .With("Right", "<Keyboard>/d")
|
||||
/// .With("Up", "<Keyboard>/q")
|
||||
/// .With("Down", "<Keyboard>/e");
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="Vector2Composite"/>
|
||||
[DisplayStringFormat("{up}+{down}/{left}+{right}/{forward}+{backward}")]
|
||||
[DisplayName("Up/Down/Left/Right/Forward/Backward Composite")]
|
||||
public class Vector3Composite : InputBindingComposite<Vector3>
|
||||
{
|
||||
/// <summary>
|
||||
/// Binding for the button that represents the up (that is, <c>(0,1,0)</c>) direction of the vector.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property is automatically assigned by the input system.
|
||||
/// </remarks>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Global
|
||||
[InputControl(layout = "Axis")] public int up;
|
||||
|
||||
/// <summary>
|
||||
/// Binding for the button that represents the down (that is, <c>(0,-1,0)</c>) direction of the vector.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property is automatically assigned by the input system.
|
||||
/// </remarks>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Global
|
||||
[InputControl(layout = "Axis")] public int down;
|
||||
|
||||
/// <summary>
|
||||
/// Binding for the button that represents the left (that is, <c>(-1,0,0)</c>) direction of the vector.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property is automatically assigned by the input system.
|
||||
/// </remarks>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Global
|
||||
[InputControl(layout = "Axis")] public int left;
|
||||
|
||||
/// <summary>
|
||||
/// Binding for the button that represents the right (that is, <c>(1,0,0)</c>) direction of the vector.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property is automatically assigned by the input system.
|
||||
/// </remarks>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Global
|
||||
[InputControl(layout = "Axis")] public int right;
|
||||
|
||||
/// <summary>
|
||||
/// Binding for the button that represents the right (that is, <c>(0,0,1)</c>) direction of the vector.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property is automatically assigned by the input system.
|
||||
/// </remarks>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Global
|
||||
[InputControl(layout = "Axis")] public int forward;
|
||||
|
||||
/// <summary>
|
||||
/// Binding for the button that represents the right (that is, <c>(0,0,-1)</c>) direction of the vector.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property is automatically assigned by the input system.
|
||||
/// </remarks>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Global
|
||||
[InputControl(layout = "Axis")] public int backward;
|
||||
|
||||
/// <summary>
|
||||
/// How to synthesize a <c>Vector3</c> from the values read from <see cref="up"/>, <see cref="down"/>,
|
||||
/// <see cref="left"/>, <see cref="right"/>, <see cref="forward"/>, and <see cref="backward"/>.
|
||||
/// </summary>
|
||||
/// <value>Determines how X, Y, and Z of the resulting <c>Vector3</c> are formed from input values.</value>
|
||||
public Mode mode = Mode.Analog;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Vector3 ReadValue(ref InputBindingCompositeContext context)
|
||||
{
|
||||
if (mode == Mode.Analog)
|
||||
{
|
||||
var upValue = context.ReadValue<float>(up);
|
||||
var downValue = context.ReadValue<float>(down);
|
||||
var leftValue = context.ReadValue<float>(left);
|
||||
var rightValue = context.ReadValue<float>(right);
|
||||
var forwardValue = context.ReadValue<float>(forward);
|
||||
var backwardValue = context.ReadValue<float>(backward);
|
||||
|
||||
return new Vector3(rightValue - leftValue, upValue - downValue, forwardValue - backwardValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
var upValue = context.ReadValueAsButton(up) ? 1f : 0f;
|
||||
var downValue = context.ReadValueAsButton(down) ? -1f : 0f;
|
||||
var leftValue = context.ReadValueAsButton(left) ? -1f : 0f;
|
||||
var rightValue = context.ReadValueAsButton(right) ? 1f : 0f;
|
||||
var forwardValue = context.ReadValueAsButton(forward) ? 1f : 0f;
|
||||
var backwardValue = context.ReadValueAsButton(backward) ? -1f : 0f;
|
||||
|
||||
var vector = new Vector3(leftValue + rightValue, upValue + downValue, forwardValue + backwardValue);
|
||||
|
||||
if (mode == Mode.DigitalNormalized)
|
||||
vector = vector.normalized;
|
||||
|
||||
return vector;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override float EvaluateMagnitude(ref InputBindingCompositeContext context)
|
||||
{
|
||||
var value = ReadValue(ref context);
|
||||
return value.magnitude;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines how a <c>Vector3</c> is synthesized from part controls.
|
||||
/// </summary>
|
||||
public enum Mode
|
||||
{
|
||||
/// <summary>
|
||||
/// Part controls are treated as analog meaning that the floating-point values read from controls
|
||||
/// will come through as is (minus the fact that the down and left direction values are negated).
|
||||
/// </summary>
|
||||
Analog,
|
||||
|
||||
/// <summary>
|
||||
/// Part controls are treated as buttons (on/off) and the resulting vector is normalized. This means
|
||||
/// that if, for example, both left and up are pressed, instead of returning a vector (-1,1,0), a vector
|
||||
/// of roughly (-0.7,0.7,0) (that is, corresponding to <c>new Vector3(-1,1,0).normalized</c>) is returned instead.
|
||||
/// </summary>
|
||||
DigitalNormalized,
|
||||
|
||||
/// <summary>
|
||||
/// Part controls are treated as buttons (on/off) and the resulting vector is not normalized. This means
|
||||
/// that if both left and up are pressed, for example, the resulting vector is (-1,1,0) and has a length
|
||||
/// greater than 1.
|
||||
/// </summary>
|
||||
Digital,
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
internal class Vector3CompositeEditor : InputParameterEditor<Vector3Composite>
|
||||
{
|
||||
private GUIContent m_ModeLabel = new GUIContent("Mode",
|
||||
"How to synthesize a Vector3 from the inputs. Digital "
|
||||
+ "treats part bindings as buttons (on/off) whereas Analog preserves "
|
||||
+ "floating-point magnitudes as read from controls.");
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
|
||||
if (!InputSystem.settings.useIMGUIEditorForAssets) return;
|
||||
#endif
|
||||
target.mode = (Vector3Composite.Mode)EditorGUILayout.EnumPopup(m_ModeLabel, target.mode);
|
||||
}
|
||||
|
||||
#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
|
||||
public override void OnDrawVisualElements(VisualElement root, Action onChangedCallback)
|
||||
{
|
||||
var modeField = new EnumField(m_ModeLabel.text, target.mode)
|
||||
{
|
||||
tooltip = m_ModeLabel.tooltip
|
||||
};
|
||||
|
||||
modeField.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
target.mode = (Vector3Composite.Mode)evt.newValue;
|
||||
onChangedCallback();
|
||||
});
|
||||
|
||||
root.Add(modeField);
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cda1b45d3bda468a95dca208b99174da
|
||||
timeCreated: 1597842391
|
||||
@@ -0,0 +1,144 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
|
||||
////TODO: move indexer up here
|
||||
|
||||
namespace UnityEngine.InputSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of input actions (see <see cref="InputAction"/>).
|
||||
/// </summary>
|
||||
/// <seealso cref="InputActionMap"/>
|
||||
/// <seealso cref="InputActionAsset"/>
|
||||
public interface IInputActionCollection : IEnumerable<InputAction>
|
||||
{
|
||||
/// <summary>
|
||||
/// Optional mask applied to all bindings in the collection.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this is not null, only bindings that match the mask will be used.
|
||||
///
|
||||
/// Modifying this property while any of the actions in the collection are enabled will
|
||||
/// lead to the actions getting disabled temporarily and then re-enabled.
|
||||
/// </remarks>
|
||||
InputBinding? bindingMask { get; set; }
|
||||
|
||||
////REVIEW: should this allow restricting to a set of controls instead of confining it to just devices?
|
||||
/// <summary>
|
||||
/// Devices to use with the actions in this collection.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this is set, actions in the collection will exclusively bind to devices
|
||||
/// in the given list. For example, if two gamepads are present in the system yet
|
||||
/// only one gamepad is listed here, then a "<Gamepad>/leftStick" binding will
|
||||
/// only bind to the gamepad in the list and not to the one that is only available
|
||||
/// globally.
|
||||
///
|
||||
/// Modifying this property after bindings in the collection have already been resolved,
|
||||
/// will lead to <see cref="InputAction.controls"/> getting refreshed. If any of the actions
|
||||
/// in the collection are currently in progress (see <see cref="InputAction.phase"/>),
|
||||
/// the actions will remain unaffected and in progress except if the controls currently
|
||||
/// driving them (see <see cref="InputAction.activeControl"/>) are no longer part of any
|
||||
/// of the selected devices. In that case, the action is <see cref="InputAction.canceled"/>.
|
||||
/// </remarks>
|
||||
ReadOnlyArray<InputDevice>? devices { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of control schemes defined for the set of actions.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Control schemes are optional and the list may be empty.
|
||||
/// </remarks>
|
||||
ReadOnlyArray<InputControlScheme> controlSchemes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Check whether the given action is contained in this collection.
|
||||
/// </summary>
|
||||
/// <param name="action">An arbitrary input action.</param>
|
||||
/// <returns>True if the given action is contained in the collection, false if not.</returns>
|
||||
/// <remarks>
|
||||
/// Calling this method will not allocate GC memory (unlike when iterating generically
|
||||
/// over the collection). Also, a collection may have a faster containment check rather than
|
||||
/// having to search through all its actions.
|
||||
/// </remarks>
|
||||
bool Contains(InputAction action);
|
||||
|
||||
/// <summary>
|
||||
/// Enable all actions in the collection.
|
||||
/// </summary>
|
||||
/// <seealso cref="InputAction.Enable"/>
|
||||
/// <seealso cref="InputAction.enabled"/>
|
||||
void Enable();
|
||||
|
||||
/// <summary>
|
||||
/// Disable all actions in the collection.
|
||||
/// </summary>
|
||||
/// <seealso cref="InputAction.Disable"/>
|
||||
/// <seealso cref="InputAction.enabled"/>
|
||||
void Disable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An extended version of <see cref="IInputActionCollection"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This interface will be merged into <see cref="IInputActionCollection"/> in a future (major) version.
|
||||
/// </remarks>
|
||||
public interface IInputActionCollection2 : IInputActionCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// Iterate over all bindings in the collection of actions.
|
||||
/// </summary>
|
||||
/// <seealso cref="InputActionMap.bindings"/>
|
||||
/// <seealso cref="InputAction.bindings"/>
|
||||
/// <seealso cref="InputActionAsset.bindings"/>
|
||||
IEnumerable<InputBinding> bindings { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Find an <see cref="InputAction"/> in the collection by its <see cref="InputAction.name"/> or
|
||||
/// by its <see cref="InputAction.id"/> (in string form).
|
||||
/// </summary>
|
||||
/// <param name="actionNameOrId">Name of the action as either a "map/action" combination (e.g. "gameplay/fire") or
|
||||
/// a simple name. In the former case, the name is split at the '/' slash and the first part is used to find
|
||||
/// a map with that name and the second part is used to find an action with that name inside the map. In the
|
||||
/// latter case, all maps are searched in order and the first action that has the given name in any of the maps
|
||||
/// is returned. Note that name comparisons are case-insensitive.
|
||||
///
|
||||
/// Alternatively, the given string can be a GUID as given by <see cref="InputAction.id"/>.</param>
|
||||
/// <param name="throwIfNotFound">If <c>true</c>, instead of returning <c>null</c> when the action
|
||||
/// cannot be found, throw <c>ArgumentException</c>.</param>
|
||||
/// <returns>The action with the corresponding name or <c>null</c> if no matching action could be found.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="actionNameOrId"/> is <c>null</c>.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="throwIfNotFound"/> is true and the
|
||||
/// action could not be found. -Or- If <paramref name="actionNameOrId"/> contains a slash but is missing
|
||||
/// either the action or the map name.</exception>
|
||||
InputAction FindAction(string actionNameOrId, bool throwIfNotFound = false);
|
||||
|
||||
/// <summary>
|
||||
/// Find the index of the first binding that matches the given mask.
|
||||
/// </summary>
|
||||
/// <param name="mask">A binding. See <see cref="InputBinding.Matches"/> for details.</param>
|
||||
/// <param name="action">Receives the action on which the binding was found. If none was found,
|
||||
/// will be set to <c>null</c>.</param>
|
||||
/// <returns>Index into <see cref="InputAction.bindings"/> of <paramref name="action"/> of the binding
|
||||
/// that matches <paramref name="mask"/>. If no binding matches, will return -1.</returns>
|
||||
/// <remarks>
|
||||
/// For details about matching bindings by a mask, see <see cref="InputBinding.Matches"/>.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var index = playerInput.actions.FindBinding(
|
||||
/// new InputBinding { path = "<Gamepad>/buttonSouth" },
|
||||
/// out var action);
|
||||
///
|
||||
/// if (index != -1)
|
||||
/// Debug.Log($"The A button is bound to {action}");
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputBinding.Matches"/>
|
||||
/// <seealso cref="bindings"/>
|
||||
int FindBinding(InputBinding mask, out InputAction action);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0e297b4219a224ba5bcb4f9293e26ea9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,337 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
using UnityEngine.Scripting;
|
||||
|
||||
// [GESTURES]
|
||||
// Idea for v2 of the input system:
|
||||
// Separate interaction *recognition* from interaction *representation*
|
||||
// This will likely also "solve" gestures
|
||||
//
|
||||
// ATM, an interaction is a prebuilt thing that rolls recognition and representation of an interaction into
|
||||
// one single thing. That limits how powerful this can be. There's only ever one interaction coming from each interaction
|
||||
// added to a setup.
|
||||
//
|
||||
// A much more powerful way would be to have the interactions configured on actions and bindings add *recognizers*
|
||||
// which then *generate* interactions. This way, a single recognizer could spawn arbitrary many interactions. What the
|
||||
// recognizer is attached to (the bindings) would simply act as triggers. Beyond that, the recognizer would have
|
||||
// plenty freedom to start, perform, and stop interactions happening in response to input.
|
||||
//
|
||||
// It'll likely be a breaking change as far as user-implemented interactions go but at least the data as it looks today
|
||||
// should work with this just fine.
|
||||
|
||||
////TODO: allow interactions to be constrained to a specific InputActionType
|
||||
|
||||
////TODO: add way for parameters on interactions and processors to be driven from global value source that is NOT InputSettings
|
||||
//// (ATM it's very hard to e.g. have a scale value on gamepad stick bindings which is determined dynamically from player
|
||||
//// settings in the game)
|
||||
|
||||
////REVIEW: what about putting an instance of one of these on every resolved control instead of sharing it between all controls resolved from a binding?
|
||||
|
||||
////REVIEW: can we have multiple interactions work together on the same binding? E.g. a 'Press' causing a start and a 'Release' interaction causing a performed
|
||||
|
||||
////REVIEW: have a default interaction so that there *always* is an interaction object when processing triggers?
|
||||
|
||||
namespace UnityEngine.InputSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for interaction patterns that drive actions.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Actions have a built-in interaction pattern that to some extent depends on their type (<see
|
||||
/// cref="InputActionType"/>, <see cref="InputAction.type"/>). What this means is that when controls
|
||||
/// bound to an action are actuated, the action will initiate an interaction that in turn determines
|
||||
/// when <see cref="InputAction.started"/>, <see cref="InputAction.performed"/>, and <see cref="InputAction.canceled"/>
|
||||
/// are called.
|
||||
///
|
||||
/// The default interaction (that is, when no interaction has been added to a binding or the
|
||||
/// action that the binding targets) will generally start and perform an action as soon as a control
|
||||
/// is actuated, then perform the action whenever the value of the control changes except if the value
|
||||
/// changes back to the default in which case the action is cancelled.
|
||||
///
|
||||
/// By writing custom interactions, it is possible to implement different interactions. For example,
|
||||
/// <see cref="Interactions.HoldInteraction"/> will only start when a control is being actuated but
|
||||
/// will only perform the action if the control is held for a minimum amount of time.
|
||||
///
|
||||
/// Interactions can be stateful and mutate state over time. In fact, interactions will usually
|
||||
/// represent miniature state machines driven directly by input.
|
||||
///
|
||||
/// Multiple interactions can be applied to the same binding. The interactions will be processed in
|
||||
/// sequence. However, the first interaction that starts the action will get to drive the state of
|
||||
/// the action. If it performs the action, all interactions are reset. If it cancels, the first
|
||||
/// interaction in the list that is in started state will get to take over and drive the action.
|
||||
///
|
||||
/// This makes it possible to have several interaction patterns on the same action. For example,
|
||||
/// to have a "fire" action that allows for charging, one can have a "Hold" and a "Press" interaction
|
||||
/// in sequence on the action.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Create a fire action with two interactions:
|
||||
/// // 1. Hold. Triggers charged firing. Has to come first as otherwise "Press" will immediately perform the action.
|
||||
/// // 2. Press. Triggers instant firing.
|
||||
/// // NOTE: An alternative is to use "Tap;Hold", that is, a "Tap" first and then a "Hold". The difference
|
||||
/// // is relatively minor. In this setup, the "Tap" turns into a "Hold" if the button is held for
|
||||
/// // longer than the tap time whereas in the setup below, the "Hold" turns into a "Press" if the
|
||||
/// // button is released before the hold time has been reached.
|
||||
/// var fireAction = new InputAction(type: InputActionType.Button, interactions: "Hold;Press");
|
||||
/// fireAction.AddBinding("<Gamepad>/buttonSouth");
|
||||
/// </code>
|
||||
/// </example>
|
||||
///
|
||||
/// Custom interactions are automatically registered by reflection but it can also be manually registered using <see cref="InputSystem.RegisterInteraction"/>. This can be
|
||||
/// done at any point during or after startup but has to be done before actions that reference the interaction
|
||||
/// are enabled or have their controls queried. A good point is usually to do it during loading like so:
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// #if UNITY_EDITOR
|
||||
/// [InitializeOnLoad]
|
||||
/// #endif
|
||||
/// public class MyInteraction : IInputInteraction
|
||||
/// {
|
||||
/// public void Process(ref InputInteractionContext context)
|
||||
/// {
|
||||
/// // ...
|
||||
/// }
|
||||
///
|
||||
/// public void Reset()
|
||||
/// {
|
||||
/// }
|
||||
///
|
||||
/// static MyInteraction()
|
||||
/// {
|
||||
/// InputSystem.RegisterInteraction<MyInteraction>();
|
||||
/// }
|
||||
///
|
||||
/// [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||
/// private static void Initialize()
|
||||
/// {
|
||||
/// // Will execute the static constructor as a side effect.
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
///
|
||||
/// If your interaction will only work with a specific type of value (e.g. <c>float</c>), it is better
|
||||
/// to base the implementation on <see cref="IInputInteraction{TValue}"/> instead. While the interface is the
|
||||
/// same, the type parameter communicates to the input system that only controls that have compatible value
|
||||
/// types should be used with your interaction.
|
||||
///
|
||||
/// Interactions, like processors (<see cref="InputProcessor"/>) and binding composites (<see cref="InputBindingComposite"/>)
|
||||
/// may define their own parameters which can then be configured through the editor UI or set programmatically in
|
||||
/// code. To define a parameter, add a public field to your class that has either a <c>bool</c>, an <c>int</c>,
|
||||
/// a <c>float</c>, or an <c>enum</c> type. To set defaults for the parameters, assign default values
|
||||
/// to the fields.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// public class MyInteraction : IInputInteraction
|
||||
/// {
|
||||
/// public bool boolParameter;
|
||||
/// public int intParameter;
|
||||
/// public float floatParameter;
|
||||
/// public MyEnum enumParameter = MyEnum.C; // Custom default.
|
||||
///
|
||||
/// public enum MyEnum
|
||||
/// {
|
||||
/// A,
|
||||
/// B,
|
||||
/// C
|
||||
/// }
|
||||
///
|
||||
/// public void Process(ref InputInteractionContext context)
|
||||
/// {
|
||||
/// // ...
|
||||
/// }
|
||||
///
|
||||
/// public void Reset()
|
||||
/// {
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // The parameters can be configured graphically in the editor or set programmatically in code.
|
||||
/// // NOTE: Enum parameters are represented by their integer values. However, when setting enum parameters
|
||||
/// // graphically in the UI, they will be presented as a dropdown using the available enum values.
|
||||
/// var action = new InputAction(interactions: "MyInteraction(boolParameter=true,intParameter=1,floatParameter=1.2,enumParameter=1);
|
||||
/// </code>
|
||||
/// </example>
|
||||
///
|
||||
/// A default UI will be presented in the editor UI to configure the parameters of your interaction.
|
||||
/// You can customize this by replacing the default UI with a custom implementation using <see cref="Editor.InputParameterEditor"/>.
|
||||
/// This mechanism is the same as for processors and binding composites.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// #if UNITY_EDITOR
|
||||
/// public class MyCustomInteractionEditor : InputParameterEditor<MyCustomInteraction>
|
||||
/// {
|
||||
/// protected override void OnEnable()
|
||||
/// {
|
||||
/// // Do any setup work you need.
|
||||
/// }
|
||||
///
|
||||
/// protected override void OnGUI()
|
||||
/// {
|
||||
/// // Use standard Unity UI calls do create your own parameter editor UI.
|
||||
/// }
|
||||
/// }
|
||||
/// #endif
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputSystem.RegisterInteraction"/>
|
||||
/// <seealso cref="InputBinding.interactions"/>
|
||||
/// <seealso cref="InputAction.interactions"/>
|
||||
/// <seealso cref="Editor.InputParameterEditor"/>
|
||||
/// <seealso cref="InputActionRebindingExtensions.GetParameterValue(InputAction,string,InputBinding)"/>
|
||||
/// <seealso cref="InputActionRebindingExtensions.ApplyParameterOverride(InputActionMap,string,PrimitiveValue,InputBinding)"/>
|
||||
public interface IInputInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// Perform processing of the interaction in response to input.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <remarks>
|
||||
/// This method is called whenever a control referenced in the binding that the interaction sits on
|
||||
/// changes value. The interaction is expected to process the value change and, if applicable, call
|
||||
/// <see cref="InputInteractionContext.Started"/> and/or its related methods to initiate a state change.
|
||||
///
|
||||
/// Note that if "control disambiguation" (i.e. the process where if multiple controls are bound to
|
||||
/// the same action, the system decides which control gets to drive the action at any one point) is
|
||||
/// in effect -- i.e. when either <see cref="InputActionType.Button"/> or <see cref="InputActionType.Value"/>
|
||||
/// are used but not if <see cref="InputActionType.PassThrough"/> is used -- inputs that the disambiguation
|
||||
/// chooses to ignore will cause this method to not be called.
|
||||
///
|
||||
/// Note that this method is called on the interaction even when there are multiple interactions
|
||||
/// and the interaction is not the one currently in control of the action (because another interaction
|
||||
/// that comes before it in the list had already started the action). Each interaction will get
|
||||
/// processed independently and the action will decide when to use which interaction to drive the
|
||||
/// action as a whole.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Processing for an interaction that will perform the action only if a control
|
||||
/// // is held at least at 3/4 actuation for at least 1 second.
|
||||
/// public void Process(ref InputInteractionContext context)
|
||||
/// {
|
||||
/// var control = context.control;
|
||||
///
|
||||
/// // See if we're currently tracking a control.
|
||||
/// if (m_Control != null)
|
||||
/// {
|
||||
/// // Ignore any input on a control we're not currently tracking.
|
||||
/// if (m_Control != control)
|
||||
/// return;
|
||||
///
|
||||
/// // Check if the control is currently actuated past our 3/4 threshold.
|
||||
/// var isStillActuated = context.ControlIsActuated(0.75f);
|
||||
///
|
||||
/// // See for how long the control has been held.
|
||||
/// var actuationTime = context.time - context.startTime;
|
||||
///
|
||||
/// if (!isStillActuated)
|
||||
/// {
|
||||
/// // Control is no longer actuated above 3/4 threshold. If it was held
|
||||
/// // for at least a second, perform the action. Otherwise cancel it.
|
||||
///
|
||||
/// if (actuationTime >= 1)
|
||||
/// context.Performed();
|
||||
/// else
|
||||
/// context.Cancelled();
|
||||
/// }
|
||||
///
|
||||
/// // Control changed value somewhere above 3/4 of its actuation. Doesn't
|
||||
/// // matter to us so no change.
|
||||
/// }
|
||||
/// else
|
||||
/// {
|
||||
/// // We're not already tracking a control. See if the control that just triggered
|
||||
/// // is actuated at least 3/4th of its way. If so, start tracking it.
|
||||
///
|
||||
/// var isActuated = context.ControlIsActuated(0.75f);
|
||||
/// if (isActuated)
|
||||
/// {
|
||||
/// m_Control = context.control;
|
||||
/// context.Started();
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// InputControl m_Control;
|
||||
///
|
||||
/// public void Reset()
|
||||
/// {
|
||||
/// m_Control = null;
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
void Process(ref InputInteractionContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Reset state that the interaction may hold. This should put the interaction back in its original
|
||||
/// state equivalent to no input yet having been received.
|
||||
/// </summary>
|
||||
void Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identical to <see cref="IInputInteraction"/> except that it allows an interaction to explicitly
|
||||
/// advertise the value it expects.
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">Type of values expected by the interaction</typeparam>
|
||||
/// <remarks>
|
||||
/// Advertising the value type will an interaction type to be filtered out in the UI if the value type
|
||||
/// it has is not compatible with the value type expected by the action.
|
||||
///
|
||||
/// In all other ways, this interface is identical to <see cref="IInputInteraction"/>.
|
||||
/// </remarks>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1040:AvoidEmptyInterfaces", Justification = "This interface is used to mark implementing classes to advertise the value it expects. This seems more elegant then the suggestion to use an attribute.")]
|
||||
public interface IInputInteraction<TValue> : IInputInteraction
|
||||
where TValue : struct
|
||||
{
|
||||
}
|
||||
|
||||
internal static class InputInteraction
|
||||
{
|
||||
public static TypeTable s_Interactions;
|
||||
|
||||
public static Type GetValueType(Type interactionType)
|
||||
{
|
||||
if (interactionType == null)
|
||||
throw new ArgumentNullException(nameof(interactionType));
|
||||
|
||||
return TypeHelpers.GetGenericTypeArgumentFromHierarchy(interactionType, typeof(IInputInteraction<>), 0);
|
||||
}
|
||||
|
||||
public static string GetDisplayName(string interaction)
|
||||
{
|
||||
if (string.IsNullOrEmpty(interaction))
|
||||
throw new ArgumentNullException(nameof(interaction));
|
||||
|
||||
var interactionType = s_Interactions.LookupTypeRegistration(interaction);
|
||||
if (interactionType == null)
|
||||
return interaction;
|
||||
|
||||
return GetDisplayName(interactionType);
|
||||
}
|
||||
|
||||
public static string GetDisplayName(Type interactionType)
|
||||
{
|
||||
if (interactionType == null)
|
||||
throw new ArgumentNullException(nameof(interactionType));
|
||||
|
||||
var displayNameAttribute = interactionType.GetCustomAttribute<DisplayNameAttribute>();
|
||||
if (displayNameAttribute == null)
|
||||
{
|
||||
if (interactionType.Name.EndsWith("Interaction"))
|
||||
return interactionType.Name.Substring(0, interactionType.Name.Length - "Interaction".Length);
|
||||
return interactionType.Name;
|
||||
}
|
||||
|
||||
return displayNameAttribute.DisplayName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e37175fb5b321444aa88a861f93360a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cfe12f5319f74d9e8b0875e965ac280b
|
||||
timeCreated: 1506842940
|
||||
@@ -0,0 +1,980 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
|
||||
////TODO: make the FindAction logic available on any IEnumerable<InputAction> and IInputActionCollection via extension methods
|
||||
|
||||
////TODO: control schemes, like actions and maps, should have stable IDs so that they can be renamed
|
||||
|
||||
////REVIEW: have some way of expressing 'contracts' on action maps? I.e. something like
|
||||
//// "I expect a 'look' and a 'move' action in here"
|
||||
|
||||
////REVIEW: rename this from "InputActionAsset" to something else that emphasizes the asset aspect less
|
||||
//// and instead emphasizes the map collection aspect more?
|
||||
|
||||
namespace UnityEngine.InputSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// An asset that contains action maps and control schemes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// InputActionAssets can be created in code but are usually stored in JSON format on
|
||||
/// disk with the ".inputactions" extension. Unity imports them with a custom
|
||||
/// importer.
|
||||
///
|
||||
/// To create an InputActionAsset in code, use the <c>Singleton</c> API and populate the
|
||||
/// asset with the methods found in <see cref="InputActionSetupExtensions"/>. Alternatively,
|
||||
/// you can use <see cref="FromJson"/> to load an InputActionAsset directly from a string in JSON format.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Create and configure an asset in code.
|
||||
/// var asset1 = ScriptableObject.CreateInstance<InputActionAsset>();
|
||||
/// var actionMap1 = asset1.AddActionMap("map1");
|
||||
/// action1Map.AddAction("action1", binding: "<Keyboard>/space");
|
||||
/// </code>
|
||||
/// </example>
|
||||
///
|
||||
/// If you use the API to modify an InputActionAsset while in Play mode,
|
||||
/// it does not survive the transition back to Edit Mode. Unity tracks and reloads modified assets
|
||||
/// from disk when exiting Play mode. This is done so that you can realistically test the input
|
||||
/// related functionality of your application i.e. control rebinding etc, without inadvertently changing
|
||||
/// the input asset.
|
||||
///
|
||||
/// Each asset can contain arbitrary many action maps that you can enable and disable individually
|
||||
/// (see <see cref="InputActionMap.Enable"/> and <see cref="InputActionMap.Disable"/>) or in bulk
|
||||
/// (see <see cref="Enable"/> and <see cref="Disable"/>). The name of each action map must be unique.
|
||||
/// The list of action maps can be queried from <see cref="actionMaps"/>.
|
||||
///
|
||||
/// InputActionAssets can only define <see cref="InputControlScheme"/>s. They can be added to
|
||||
/// an asset with <see cref="InputActionSetupExtensions.AddControlScheme(InputActionAsset,string)"/>
|
||||
/// and can be queried from <see cref="controlSchemes"/>.
|
||||
///
|
||||
/// Be aware that input action assets do not separate between static (configuration) data and dynamic
|
||||
/// (instance) data. For audio, for example, <c>AudioClip</c> represents the static,
|
||||
/// shared data portion of audio playback whereas <c>AudioSource"</c> represents the
|
||||
/// dynamic, per-instance audio playback portion (referencing the clip through <c>AudioSource.clip</c>).
|
||||
///
|
||||
/// For input, such a split is less beneficial as the same input is generally not exercised
|
||||
/// multiple times in parallel. Keeping both static and dynamic data together simplifies
|
||||
/// using the system.
|
||||
///
|
||||
/// However, there are scenarios where you indeed want to take the same input action and
|
||||
/// exercise it multiple times in parallel. A prominent example of such a use case is
|
||||
/// local multiplayer where each player gets the same set of actions but is controlling
|
||||
/// them with a different device (or devices) each. This is easily achieved by simply
|
||||
/// using <c>UnityEngine.Object.Instantiate</c> to instantiate the input action
|
||||
/// asset multiple times. <see cref="PlayerInput"/> will automatically do so in its
|
||||
/// internals.
|
||||
///
|
||||
/// Note also that all action maps in an asset share binding state. This means that if
|
||||
/// one map in an asset has to resolve its bindings, all maps in the asset have to.
|
||||
/// </remarks>
|
||||
public class InputActionAsset : ScriptableObject, IInputActionCollection2
|
||||
{
|
||||
/// <summary>
|
||||
/// File extension (without the dot) for InputActionAssets in JSON format.
|
||||
/// </summary>
|
||||
/// <value>File extension for InputActionAsset source files.</value>
|
||||
/// <remarks>
|
||||
/// Files with this extension will automatically be imported by Unity as
|
||||
/// InputActionAssets.
|
||||
/// </remarks>
|
||||
public const string Extension = "inputactions";
|
||||
////REVIEW: actually pre-populate with some stuff?
|
||||
internal const string kDefaultAssetLayoutJson = "{}";
|
||||
|
||||
/// <summary>
|
||||
/// True if any action in the asset is currently enabled.
|
||||
/// </summary>
|
||||
/// <seealso cref="InputAction.enabled"/>
|
||||
/// <seealso cref="InputActionMap.enabled"/>
|
||||
/// <seealso cref="InputAction.Enable"/>
|
||||
/// <seealso cref="InputActionMap.Enable"/>
|
||||
/// <seealso cref="Enable"/>
|
||||
public bool enabled
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var actionMap in actionMaps)
|
||||
if (actionMap.enabled)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of action maps defined in the asset.
|
||||
/// </summary>
|
||||
/// <value>Action maps contained in the asset.</value>
|
||||
/// <seealso cref="InputActionSetupExtensions.AddActionMap(InputActionAsset,string)"/>
|
||||
/// <seealso cref="InputActionSetupExtensions.RemoveActionMap(InputActionAsset,InputActionMap)"/>
|
||||
/// <seealso cref="FindActionMap(string,bool)"/>
|
||||
public ReadOnlyArray<InputActionMap> actionMaps => new ReadOnlyArray<InputActionMap>(m_ActionMaps);
|
||||
|
||||
/// <summary>
|
||||
/// List of control schemes defined in the asset.
|
||||
/// </summary>
|
||||
/// <value>Control schemes defined for the asset.</value>
|
||||
/// <seealso cref="InputActionSetupExtensions.AddControlScheme(InputActionAsset,string)"/>
|
||||
/// <seealso cref="InputActionSetupExtensions.RemoveControlScheme"/>
|
||||
public ReadOnlyArray<InputControlScheme> controlSchemes => new ReadOnlyArray<InputControlScheme>(m_ControlSchemes);
|
||||
|
||||
/// <summary>
|
||||
/// Iterate over all bindings in the asset.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This iterates over all action maps in <see cref="actionMaps"/> and, within each
|
||||
/// map, over the set of <see cref="InputActionMap.bindings"/>.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputActionMap.bindings"/>
|
||||
public IEnumerable<InputBinding> bindings
|
||||
{
|
||||
get
|
||||
{
|
||||
var numActionMaps = m_ActionMaps.LengthSafe();
|
||||
if (numActionMaps == 0)
|
||||
yield break;
|
||||
|
||||
for (var i = 0; i < numActionMaps; ++i)
|
||||
{
|
||||
var actionMap = m_ActionMaps[i];
|
||||
var bindings = actionMap.m_Bindings;
|
||||
var numBindings = bindings.LengthSafe();
|
||||
|
||||
for (var n = 0; n < numBindings; ++n)
|
||||
yield return bindings[n];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binding mask to apply to all action maps and actions in the asset.
|
||||
/// </summary>
|
||||
/// <value>Optional mask that determines which bindings in the asset to enable.</value>
|
||||
/// <remarks>
|
||||
/// Binding masks can be applied at three different levels: for an entire asset through
|
||||
/// this property, for a specific map through <see cref="InputActionMap.bindingMask"/>,
|
||||
/// and for single actions through <see cref="InputAction.bindingMask"/>. By default,
|
||||
/// none of the masks will be set (i.e. they will be <c>null</c>).
|
||||
///
|
||||
/// When an action is enabled, all the binding masks that apply to it are taken into
|
||||
/// account. Specifically, this means that any given binding on the action will be
|
||||
/// enabled only if it matches the mask applied to the asset, the mask applied
|
||||
/// to the map that contains the action, and the mask applied to the action itself.
|
||||
/// All the masks are individually optional.
|
||||
///
|
||||
/// Masks are matched against bindings using <see cref="InputBinding.Matches"/>.
|
||||
///
|
||||
/// Note that if you modify the masks applicable to an action while it is
|
||||
/// enabled, the action's <see cref="InputAction.controls"/> will get updated immediately to
|
||||
/// respect the mask. To avoid repeated binding resolution, it is most efficient
|
||||
/// to apply binding masks before enabling actions.
|
||||
///
|
||||
/// Binding masks are non-destructive. All the bindings on the action are left
|
||||
/// in place. Setting a mask will not affect the value of the <see cref="InputAction.bindings"/>
|
||||
/// and <see cref="InputActionMap.bindings"/> properties.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputBinding.MaskByGroup"/>
|
||||
/// <seealso cref="InputAction.bindingMask"/>
|
||||
/// <seealso cref="InputActionMap.bindingMask"/>
|
||||
public InputBinding? bindingMask
|
||||
{
|
||||
get => m_BindingMask;
|
||||
set
|
||||
{
|
||||
if (m_BindingMask == value)
|
||||
return;
|
||||
|
||||
m_BindingMask = value;
|
||||
|
||||
ReResolveIfNecessary(fullResolve: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set of devices that bindings in the asset can bind to.
|
||||
/// </summary>
|
||||
/// <value>Optional set of devices to use by bindings in the asset.</value>
|
||||
/// <remarks>
|
||||
/// By default (with this property being <c>null</c>), bindings will bind to any of the
|
||||
/// controls available through <see cref="InputSystem.devices"/>, i.e. controls from all
|
||||
/// devices in the system will be used.
|
||||
///
|
||||
/// By setting this property, binding resolution can instead be restricted to just specific
|
||||
/// devices. This restriction can either be applied to an entire asset using this property
|
||||
/// or to specific action maps by using <see cref="InputActionMap.devices"/>. Note that if
|
||||
/// both this property and <see cref="InputActionMap.devices"/> is set for a specific action
|
||||
/// map, the list of devices on the action map will take precedence and the list on the
|
||||
/// asset will be ignored for bindings in that action map.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Create an asset with a single action map and a single action with a
|
||||
/// // gamepad binding.
|
||||
/// var asset = ScriptableObject.CreateInstance<InputActionAsset>();
|
||||
/// var actionMap = new InputActionMap();
|
||||
/// var fireAction = actionMap.AddAction("Fire", binding: "<Gamepad>/buttonSouth");
|
||||
/// asset.AddActionMap(actionMap);
|
||||
///
|
||||
/// // Let's assume we have two gamepads connected. If we enable the
|
||||
/// // action map now, the 'Fire' action will bind to both.
|
||||
/// actionMap.Enable();
|
||||
///
|
||||
/// // This will print two controls.
|
||||
/// Debug.Log(string.Join("\n", fireAction.controls));
|
||||
///
|
||||
/// // To restrict the setup to just the first gamepad, we can assign
|
||||
/// // to the 'devices' property (in this case, we could do so on either
|
||||
/// // the action map or on the asset; we choose the latter here).
|
||||
/// asset.devices = new InputDevice[] { Gamepad.all[0] };
|
||||
///
|
||||
/// // Now this will print only one control.
|
||||
/// Debug.Log(string.Join("\n", fireAction.controls));
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputActionMap.devices"/>
|
||||
public ReadOnlyArray<InputDevice>? devices
|
||||
{
|
||||
get => m_Devices.Get();
|
||||
set
|
||||
{
|
||||
if (m_Devices.Set(value))
|
||||
ReResolveIfNecessary(fullResolve: false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Look up an action by name or ID.
|
||||
/// </summary>
|
||||
/// <param name="actionNameOrId">Name of the action as either a "map/action" combination (e.g. "gameplay/fire") or
|
||||
/// a simple name. In the former case, the name is split at the '/' slash and the first part is used to find
|
||||
/// a map with that name and the second part is used to find an action with that name inside the map. In the
|
||||
/// latter case, all maps are searched in order and the first action that has the given name in any of the maps
|
||||
/// is returned. Note that name comparisons are case-insensitive.
|
||||
///
|
||||
/// Alternatively, the given string can be a GUID as given by <see cref="InputAction.id"/>.</param>
|
||||
/// <returns>The action with the corresponding name or null if no matching action could be found.</returns>
|
||||
/// <remarks>
|
||||
/// This method is equivalent to <see cref="FindAction(string,bool)"/> except that it throws
|
||||
/// <see cref="KeyNotFoundException"/> if no action with the given name or ID
|
||||
/// could be found.
|
||||
/// </remarks>
|
||||
/// <exception cref="KeyNotFoundException">No action was found matching <paramref name="actionNameOrId"/>.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="actionNameOrId"/> is <c>null</c> or empty.</exception>
|
||||
/// <seealso cref="FindAction(string,bool)"/>
|
||||
public InputAction this[string actionNameOrId]
|
||||
{
|
||||
get
|
||||
{
|
||||
var action = FindAction(actionNameOrId);
|
||||
if (action == null)
|
||||
throw new KeyNotFoundException($"Cannot find action '{actionNameOrId}' in '{this}'");
|
||||
return action;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a JSON representation of the asset.
|
||||
/// </summary>
|
||||
/// <returns>A string in JSON format that represents the static/configuration data present
|
||||
/// in the asset.</returns>
|
||||
/// <remarks>
|
||||
/// This will not save dynamic execution state such as callbacks installed on
|
||||
/// <see cref="InputAction">actions</see> or enabled/disabled states of individual
|
||||
/// maps and actions.
|
||||
///
|
||||
/// Use <see cref="LoadFromJson"/> to deserialize the JSON data back into an InputActionAsset.
|
||||
///
|
||||
/// Be aware that the format used by this method is <em>different</em> than what you
|
||||
/// get if you call <c>JsonUtility.ToJson</c> on an InputActionAsset instance. In other
|
||||
/// words, the JSON format is not identical to the Unity serialized object representation
|
||||
/// of the asset.
|
||||
/// </remarks>
|
||||
/// <seealso cref="FromJson"/>
|
||||
public string ToJson()
|
||||
{
|
||||
return JsonUtility.ToJson(new WriteFileJson
|
||||
{
|
||||
name = name,
|
||||
maps = InputActionMap.WriteFileJson.FromMaps(m_ActionMaps).maps,
|
||||
controlSchemes = InputControlScheme.SchemeJson.ToJson(m_ControlSchemes),
|
||||
}, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace the contents of the asset with the data in the given JSON string.
|
||||
/// </summary>
|
||||
/// <param name="json">JSON contents of an <c>.inputactions</c> asset.</param>
|
||||
/// <remarks>
|
||||
/// <c>.inputactions</c> assets are stored in JSON format. This method allows reading
|
||||
/// the JSON source text of such an asset into an existing <c>InputActionMap</c> instance.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var asset = ScriptableObject.CreateInstance<InputActionAsset>();
|
||||
/// asset.LoadFromJson(@"
|
||||
/// {
|
||||
/// ""maps"" : [
|
||||
/// {
|
||||
/// ""name"" : ""gameplay"",
|
||||
/// ""actions"" : [
|
||||
/// { ""name"" : ""fire"", ""type"" : ""button"" },
|
||||
/// { ""name"" : ""look"", ""type"" : ""value"" },
|
||||
/// { ""name"" : ""move"", ""type"" : ""value"" }
|
||||
/// ],
|
||||
/// ""bindings"" : [
|
||||
/// { ""path"" : ""<Gamepad>/buttonSouth"", ""action"" : ""fire"", ""groups"" : ""Gamepad"" },
|
||||
/// { ""path"" : ""<Gamepad>/leftTrigger"", ""action"" : ""fire"", ""groups"" : ""Gamepad"" },
|
||||
/// { ""path"" : ""<Gamepad>/leftStick"", ""action"" : ""move"", ""groups"" : ""Gamepad"" },
|
||||
/// { ""path"" : ""<Gamepad>/rightStick"", ""action"" : ""look"", ""groups"" : ""Gamepad"" },
|
||||
/// { ""path"" : ""dpad"", ""action"" : ""move"", ""groups"" : ""Gamepad"", ""isComposite"" : true },
|
||||
/// { ""path"" : ""<Keyboard>/a"", ""name"" : ""left"", ""action"" : ""move"", ""groups"" : ""Keyboard&Mouse"", ""isPartOfComposite"" : true },
|
||||
/// { ""path"" : ""<Keyboard>/d"", ""name"" : ""right"", ""action"" : ""move"", ""groups"" : ""Keyboard&Mouse"", ""isPartOfComposite"" : true },
|
||||
/// { ""path"" : ""<Keyboard>/w"", ""name"" : ""up"", ""action"" : ""move"", ""groups"" : ""Keyboard&Mouse"", ""isPartOfComposite"" : true },
|
||||
/// { ""path"" : ""<Keyboard>/s"", ""name"" : ""down"", ""action"" : ""move"", ""groups"" : ""Keyboard&Mouse"", ""isPartOfComposite"" : true },
|
||||
/// { ""path"" : ""<Mouse>/delta"", ""action"" : ""look"", ""groups"" : ""Keyboard&Mouse"" },
|
||||
/// { ""path"" : ""<Mouse>/leftButton"", ""action"" : ""fire"", ""groups"" : ""Keyboard&Mouse"" }
|
||||
/// ]
|
||||
/// },
|
||||
/// {
|
||||
/// ""name"" : ""ui"",
|
||||
/// ""actions"" : [
|
||||
/// { ""name"" : ""navigate"" }
|
||||
/// ],
|
||||
/// ""bindings"" : [
|
||||
/// { ""path"" : ""<Gamepad>/dpad"", ""action"" : ""navigate"", ""groups"" : ""Gamepad"" }
|
||||
/// ]
|
||||
/// }
|
||||
/// ],
|
||||
/// ""controlSchemes"" : [
|
||||
/// {
|
||||
/// ""name"" : ""Gamepad"",
|
||||
/// ""bindingGroup"" : ""Gamepad"",
|
||||
/// ""devices"" : [
|
||||
/// { ""devicePath"" : ""<Gamepad>"" }
|
||||
/// ]
|
||||
/// },
|
||||
/// {
|
||||
/// ""name"" : ""Keyboard&Mouse"",
|
||||
/// ""bindingGroup"" : ""Keyboard&Mouse"",
|
||||
/// ""devices"" : [
|
||||
/// { ""devicePath"" : ""<Keyboard>"" },
|
||||
/// { ""devicePath"" : ""<Mouse>"" }
|
||||
/// ]
|
||||
/// }
|
||||
/// ]
|
||||
/// }");
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="json"/> is <c>null</c> or empty.</exception>
|
||||
/// <seealso cref="FromJson"/>
|
||||
/// <seealso cref="ToJson"/>
|
||||
public void LoadFromJson(string json)
|
||||
{
|
||||
if (string.IsNullOrEmpty(json))
|
||||
throw new ArgumentNullException(nameof(json));
|
||||
|
||||
var parsedJson = JsonUtility.FromJson<ReadFileJson>(json);
|
||||
parsedJson.ToAsset(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace the contents of the asset with the data in the given JSON string.
|
||||
/// </summary>
|
||||
/// <param name="json">JSON contents of an <c>.inputactions</c> asset.</param>
|
||||
/// <returns>The InputActionAsset instance created from the given JSON string.</returns>
|
||||
/// <remarks>
|
||||
/// <c>.inputactions</c> assets are stored in JSON format. This method allows turning
|
||||
/// the JSON source text of such an asset into a new <c>InputActionMap</c> instance.
|
||||
///
|
||||
/// Be aware that the format used by this method is <em>different</em> than what you
|
||||
/// get if you call <c>JsonUtility.ToJson</c> on an InputActionAsset instance. In other
|
||||
/// words, the JSON format is not identical to the Unity serialized object representation
|
||||
/// of the asset.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var asset = InputActionAsset.FromJson(@"
|
||||
/// {
|
||||
/// ""maps"" : [
|
||||
/// {
|
||||
/// ""name"" : ""gameplay"",
|
||||
/// ""actions"" : [
|
||||
/// { ""name"" : ""fire"", ""type"" : ""button"" },
|
||||
/// { ""name"" : ""look"", ""type"" : ""value"" },
|
||||
/// { ""name"" : ""move"", ""type"" : ""value"" }
|
||||
/// ],
|
||||
/// ""bindings"" : [
|
||||
/// { ""path"" : ""<Gamepad>/buttonSouth"", ""action"" : ""fire"", ""groups"" : ""Gamepad"" },
|
||||
/// { ""path"" : ""<Gamepad>/leftTrigger"", ""action"" : ""fire"", ""groups"" : ""Gamepad"" },
|
||||
/// { ""path"" : ""<Gamepad>/leftStick"", ""action"" : ""move"", ""groups"" : ""Gamepad"" },
|
||||
/// { ""path"" : ""<Gamepad>/rightStick"", ""action"" : ""look"", ""groups"" : ""Gamepad"" },
|
||||
/// { ""path"" : ""dpad"", ""action"" : ""move"", ""groups"" : ""Gamepad"", ""isComposite"" : true },
|
||||
/// { ""path"" : ""<Keyboard>/a"", ""name"" : ""left"", ""action"" : ""move"", ""groups"" : ""Keyboard&Mouse"", ""isPartOfComposite"" : true },
|
||||
/// { ""path"" : ""<Keyboard>/d"", ""name"" : ""right"", ""action"" : ""move"", ""groups"" : ""Keyboard&Mouse"", ""isPartOfComposite"" : true },
|
||||
/// { ""path"" : ""<Keyboard>/w"", ""name"" : ""up"", ""action"" : ""move"", ""groups"" : ""Keyboard&Mouse"", ""isPartOfComposite"" : true },
|
||||
/// { ""path"" : ""<Keyboard>/s"", ""name"" : ""down"", ""action"" : ""move"", ""groups"" : ""Keyboard&Mouse"", ""isPartOfComposite"" : true },
|
||||
/// { ""path"" : ""<Mouse>/delta"", ""action"" : ""look"", ""groups"" : ""Keyboard&Mouse"" },
|
||||
/// { ""path"" : ""<Mouse>/leftButton"", ""action"" : ""fire"", ""groups"" : ""Keyboard&Mouse"" }
|
||||
/// ]
|
||||
/// },
|
||||
/// {
|
||||
/// ""name"" : ""ui"",
|
||||
/// ""actions"" : [
|
||||
/// { ""name"" : ""navigate"" }
|
||||
/// ],
|
||||
/// ""bindings"" : [
|
||||
/// { ""path"" : ""<Gamepad>/dpad"", ""action"" : ""navigate"", ""groups"" : ""Gamepad"" }
|
||||
/// ]
|
||||
/// }
|
||||
/// ],
|
||||
/// ""controlSchemes"" : [
|
||||
/// {
|
||||
/// ""name"" : ""Gamepad"",
|
||||
/// ""bindingGroup"" : ""Gamepad"",
|
||||
/// ""devices"" : [
|
||||
/// { ""devicePath"" : ""<Gamepad>"" }
|
||||
/// ]
|
||||
/// },
|
||||
/// {
|
||||
/// ""name"" : ""Keyboard&Mouse"",
|
||||
/// ""bindingGroup"" : ""Keyboard&Mouse"",
|
||||
/// ""devices"" : [
|
||||
/// { ""devicePath"" : ""<Keyboard>"" },
|
||||
/// { ""devicePath"" : ""<Mouse>"" }
|
||||
/// ]
|
||||
/// }
|
||||
/// ]
|
||||
/// }");
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="json"/> is <c>null</c> or empty.</exception>
|
||||
/// <seealso cref="LoadFromJson"/>
|
||||
/// <seealso cref="ToJson"/>
|
||||
public static InputActionAsset FromJson(string json)
|
||||
{
|
||||
if (string.IsNullOrEmpty(json))
|
||||
throw new ArgumentNullException(nameof(json));
|
||||
|
||||
var asset = CreateInstance<InputActionAsset>();
|
||||
asset.LoadFromJson(json);
|
||||
return asset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find an <see cref="InputAction"/> by its name in one of the <see cref="InputActionMap"/>s
|
||||
/// in the asset.
|
||||
/// </summary>
|
||||
/// <param name="actionNameOrId">Name of the action as either a "map/action" combination (e.g. "gameplay/fire") or
|
||||
/// a simple name. In the former case, the name is split at the '/' slash and the first part is used to find
|
||||
/// a map with that name and the second part is used to find an action with that name inside the map. In the
|
||||
/// latter case, all maps are searched in order and the first action that has the given name in any of the maps
|
||||
/// is returned. Note that name comparisons are case-insensitive.
|
||||
///
|
||||
/// Alternatively, the given string can be a GUID as given by <see cref="InputAction.id"/>.</param>
|
||||
/// <param name="throwIfNotFound">If <c>true</c>, instead of returning <c>null</c> when the action
|
||||
/// cannot be found, throw <c>ArgumentException</c>.</param>
|
||||
/// <returns>The action with the corresponding name or <c>null</c> if no matching action could be found.</returns>
|
||||
/// <remarks>
|
||||
/// Note that no lookup structures are used internally to speed the operation up. Instead, the search is done
|
||||
/// linearly. For repeated access of an action, it is thus generally best to look up actions once ahead of
|
||||
/// time and cache the result.
|
||||
///
|
||||
/// If multiple actions have the same name and <paramref name="actionNameOrId"/> is not an ID and not an
|
||||
/// action name qualified by a map name (that is, in the form of <c>"mapName/actionName"</c>), the action that
|
||||
/// is returned will be from the first map in <see cref="actionMaps"/> that has an action with the given name.
|
||||
/// An exception is if, of the multiple actions with the same name, some are enabled and some are disabled. In
|
||||
/// this case, the first action that is enabled is returned.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var asset = ScriptableObject.CreateInstance<InputActionAsset>();
|
||||
///
|
||||
/// var map1 = new InputActionMap("map1");
|
||||
/// var map2 = new InputActionMap("map2");
|
||||
///
|
||||
/// asset.AddActionMap(map1);
|
||||
/// asset.AddActionMap(map2);
|
||||
///
|
||||
/// var action1 = map1.AddAction("action1");
|
||||
/// var action2 = map1.AddAction("action2");
|
||||
/// var action3 = map2.AddAction("action3");
|
||||
///
|
||||
/// // Search all maps in the asset for any action that has the given name.
|
||||
/// asset.FindAction("action1") // Returns action1.
|
||||
/// asset.FindAction("action2") // Returns action2
|
||||
/// asset.FindAction("action3") // Returns action3.
|
||||
///
|
||||
/// // Search for a specific action in a specific map.
|
||||
/// asset.FindAction("map1/action1") // Returns action1.
|
||||
/// asset.FindAction("map2/action2") // Returns action2.
|
||||
/// asset.FindAction("map3/action3") // Returns action3.
|
||||
///
|
||||
/// // Search by unique action ID.
|
||||
/// asset.FindAction(action1.id.ToString()) // Returns action1.
|
||||
/// asset.FindAction(action2.id.ToString()) // Returns action2.
|
||||
/// asset.FindAction(action3.id.ToString()) // Returns action3.
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="actionNameOrId"/> is <c>null</c>.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="throwIfNotFound"/> is true and the
|
||||
/// action could not be found. -Or- If <paramref name="actionNameOrId"/> contains a slash but is missing
|
||||
/// either the action or the map name.</exception>
|
||||
public InputAction FindAction(string actionNameOrId, bool throwIfNotFound = false)
|
||||
{
|
||||
if (actionNameOrId == null)
|
||||
throw new ArgumentNullException(nameof(actionNameOrId));
|
||||
|
||||
if (m_ActionMaps != null)
|
||||
{
|
||||
// Check if we have a "map/action" path.
|
||||
var indexOfSlash = actionNameOrId.IndexOf('/');
|
||||
if (indexOfSlash == -1)
|
||||
{
|
||||
// No slash so it's just a simple action name. Return either first enabled action or, if
|
||||
// none are enabled, first action with the given name.
|
||||
InputAction firstActionFound = null;
|
||||
for (var i = 0; i < m_ActionMaps.Length; ++i)
|
||||
{
|
||||
var action = m_ActionMaps[i].FindAction(actionNameOrId);
|
||||
if (action != null)
|
||||
{
|
||||
if (action.enabled || action.m_Id == actionNameOrId) // Match by ID is always exact.
|
||||
return action;
|
||||
if (firstActionFound == null)
|
||||
firstActionFound = action;
|
||||
}
|
||||
}
|
||||
if (firstActionFound != null)
|
||||
return firstActionFound;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Have a path. First search for the map, then for the action.
|
||||
var mapName = new Substring(actionNameOrId, 0, indexOfSlash);
|
||||
var actionName = new Substring(actionNameOrId, indexOfSlash + 1);
|
||||
|
||||
if (mapName.isEmpty || actionName.isEmpty)
|
||||
throw new ArgumentException("Malformed action path: " + actionNameOrId, nameof(actionNameOrId));
|
||||
|
||||
for (var i = 0; i < m_ActionMaps.Length; ++i)
|
||||
{
|
||||
var map = m_ActionMaps[i];
|
||||
if (Substring.Compare(map.name, mapName, StringComparison.InvariantCultureIgnoreCase) != 0)
|
||||
continue;
|
||||
|
||||
var actions = map.m_Actions;
|
||||
if (actions != null)
|
||||
{
|
||||
for (var n = 0; n < actions.Length; ++n)
|
||||
{
|
||||
var action = actions[n];
|
||||
if (Substring.Compare(action.name, actionName,
|
||||
StringComparison.InvariantCultureIgnoreCase) == 0)
|
||||
return action;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (throwIfNotFound)
|
||||
throw new ArgumentException($"No action '{actionNameOrId}' in '{this}'");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int FindBinding(InputBinding mask, out InputAction action)
|
||||
{
|
||||
var numMaps = m_ActionMaps.LengthSafe();
|
||||
|
||||
for (var i = 0; i < numMaps; ++i)
|
||||
{
|
||||
var actionMap = m_ActionMaps[i];
|
||||
|
||||
var bindingIndex = actionMap.FindBinding(mask, out action);
|
||||
if (bindingIndex >= 0)
|
||||
return bindingIndex;
|
||||
}
|
||||
|
||||
action = null;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find an <see cref="InputActionMap"/> in the asset by its name or ID.
|
||||
/// </summary>
|
||||
/// <param name="nameOrId">Name or ID (see <see cref="InputActionMap.id"/>) of the action map
|
||||
/// to look for. Matching is case-insensitive.</param>
|
||||
/// <param name="throwIfNotFound">If true, instead of returning <c>null</c>, throw <c>ArgumentException</c>.</param>
|
||||
/// <returns>The <see cref="InputActionMap"/> with a name or ID matching <paramref name="nameOrId"/> or
|
||||
/// <c>null</c> if no matching map could be found.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="nameOrId"/> is <c>null</c>.</exception>
|
||||
/// <exception cref="ArgumentException">If <paramref name="throwIfNotFound"/> is <c>true</c>, thrown if
|
||||
/// the action map cannot be found.</exception>
|
||||
/// <seealso cref="actionMaps"/>
|
||||
/// <seealso cref="FindActionMap(System.Guid)"/>
|
||||
public InputActionMap FindActionMap(string nameOrId, bool throwIfNotFound = false)
|
||||
{
|
||||
if (nameOrId == null)
|
||||
throw new ArgumentNullException(nameof(nameOrId));
|
||||
|
||||
if (m_ActionMaps == null)
|
||||
return null;
|
||||
|
||||
// If the name contains a hyphen, it may be a GUID.
|
||||
if (nameOrId.Contains('-') && Guid.TryParse(nameOrId, out var id))
|
||||
{
|
||||
for (var i = 0; i < m_ActionMaps.Length; ++i)
|
||||
{
|
||||
var map = m_ActionMaps[i];
|
||||
if (map.idDontGenerate == id)
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
// Default lookup is by name (case-insensitive).
|
||||
for (var i = 0; i < m_ActionMaps.Length; ++i)
|
||||
{
|
||||
var map = m_ActionMaps[i];
|
||||
if (string.Compare(nameOrId, map.name, StringComparison.InvariantCultureIgnoreCase) == 0)
|
||||
return map;
|
||||
}
|
||||
|
||||
if (throwIfNotFound)
|
||||
throw new ArgumentException($"Cannot find action map '{nameOrId}' in '{this}'");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find an <see cref="InputActionMap"/> in the asset by its ID.
|
||||
/// </summary>
|
||||
/// <param name="id">ID (see <see cref="InputActionMap.id"/>) of the action map
|
||||
/// to look for.</param>
|
||||
/// <returns>The <see cref="InputActionMap"/> with ID matching <paramref name="id"/> or
|
||||
/// <c>null</c> if no map in the asset has the given ID.</returns>
|
||||
/// <seealso cref="actionMaps"/>
|
||||
/// <seealso cref="FindActionMap"/>
|
||||
public InputActionMap FindActionMap(Guid id)
|
||||
{
|
||||
if (m_ActionMaps == null)
|
||||
return null;
|
||||
|
||||
for (var i = 0; i < m_ActionMaps.Length; ++i)
|
||||
{
|
||||
var map = m_ActionMaps[i];
|
||||
if (map.idDontGenerate == id)
|
||||
return map;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find an action by its ID (see <see cref="InputAction.id"/>).
|
||||
/// </summary>
|
||||
/// <param name="guid">ID of the action to look for.</param>
|
||||
/// <returns>The action in the asset with the given ID or null if no action
|
||||
/// in the asset has the given ID.</returns>
|
||||
public InputAction FindAction(Guid guid)
|
||||
{
|
||||
if (m_ActionMaps == null)
|
||||
return null;
|
||||
|
||||
for (var i = 0; i < m_ActionMaps.Length; ++i)
|
||||
{
|
||||
var map = m_ActionMaps[i];
|
||||
var action = map.FindAction(guid);
|
||||
if (action != null)
|
||||
return action;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the control scheme with the given name and return its index
|
||||
/// in <see cref="controlSchemes"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the control scheme. Matching is case-insensitive.</param>
|
||||
/// <returns>The index of the given control scheme or -1 if no control scheme
|
||||
/// with the given name could be found.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c>
|
||||
/// or empty.</exception>
|
||||
public int FindControlSchemeIndex(string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
|
||||
if (m_ControlSchemes == null)
|
||||
return -1;
|
||||
|
||||
for (var i = 0; i < m_ControlSchemes.Length; ++i)
|
||||
if (string.Compare(name, m_ControlSchemes[i].name, StringComparison.InvariantCultureIgnoreCase) == 0)
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the control scheme with the given name and return it.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the control scheme. Matching is case-insensitive.</param>
|
||||
/// <returns>The control scheme with the given name or null if no scheme
|
||||
/// with the given name could be found in the asset.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c>
|
||||
/// or empty.</exception>
|
||||
public InputControlScheme? FindControlScheme(string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
|
||||
var index = FindControlSchemeIndex(name);
|
||||
if (index == -1)
|
||||
return null;
|
||||
|
||||
return m_ControlSchemes[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the asset contains bindings (in any of its action maps) that are usable
|
||||
/// with the given <paramref name="device"/>.
|
||||
/// </summary>
|
||||
/// <param name="device">An arbitrary input device.</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="device"/> is <c>null</c>.</exception>
|
||||
/// <remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Find out if the actions of the given PlayerInput can be used with
|
||||
/// // a gamepad.
|
||||
/// if (playerInput.actions.IsUsableWithDevice(Gamepad.all[0]))
|
||||
/// /* ... */;
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputActionMap.IsUsableWithDevice"/>
|
||||
/// <seealso cref="InputControlScheme.SupportsDevice"/>
|
||||
public bool IsUsableWithDevice(InputDevice device)
|
||||
{
|
||||
if (device == null)
|
||||
throw new ArgumentNullException(nameof(device));
|
||||
|
||||
// If we have control schemes, we let those dictate our search.
|
||||
var numControlSchemes = m_ControlSchemes.LengthSafe();
|
||||
if (numControlSchemes > 0)
|
||||
{
|
||||
for (var i = 0; i < numControlSchemes; ++i)
|
||||
{
|
||||
if (m_ControlSchemes[i].SupportsDevice(device))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, we'll go search bindings. Slow.
|
||||
var actionMapCount = m_ActionMaps.LengthSafe();
|
||||
for (var i = 0; i < actionMapCount; ++i)
|
||||
if (m_ActionMaps[i].IsUsableWithDevice(device))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable all action maps in the asset.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method is equivalent to calling <see cref="InputActionMap.Enable"/> on
|
||||
/// all maps in <see cref="actionMaps"/>.
|
||||
/// </remarks>
|
||||
public void Enable()
|
||||
{
|
||||
foreach (var map in actionMaps)
|
||||
map.Enable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable all action maps in the asset.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method is equivalent to calling <see cref="InputActionMap.Disable"/> on
|
||||
/// all maps in <see cref="actionMaps"/>.
|
||||
/// </remarks>
|
||||
public void Disable()
|
||||
{
|
||||
foreach (var map in actionMaps)
|
||||
map.Disable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return <c>true</c> if the given action is part of the asset.
|
||||
/// </summary>
|
||||
/// <param name="action">An action. Can be null.</param>
|
||||
/// <returns>True if the given action is part of the asset, false otherwise.</returns>
|
||||
public bool Contains(InputAction action)
|
||||
{
|
||||
var map = action?.actionMap;
|
||||
if (map == null)
|
||||
return false;
|
||||
|
||||
return map.asset == this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerate all actions in the asset.
|
||||
/// </summary>
|
||||
/// <returns>An enumerator going over the actions in the asset.</returns>
|
||||
/// <remarks>
|
||||
/// Actions will be enumerated one action map in <see cref="actionMaps"/>
|
||||
/// after the other. The actions from each map will be yielded in turn.
|
||||
///
|
||||
/// This method will allocate GC heap memory.
|
||||
/// </remarks>
|
||||
public IEnumerator<InputAction> GetEnumerator()
|
||||
{
|
||||
if (m_ActionMaps == null)
|
||||
yield break;
|
||||
|
||||
for (var i = 0; i < m_ActionMaps.Length; ++i)
|
||||
{
|
||||
var actions = m_ActionMaps[i].actions;
|
||||
var actionCount = actions.Count;
|
||||
|
||||
for (var n = 0; n < actionCount; ++n)
|
||||
yield return actions[n];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerate all actions in the asset.
|
||||
/// </summary>
|
||||
/// <returns>An enumerator going over the actions in the asset.</returns>
|
||||
/// <seealso cref="GetEnumerator"/>
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
internal void MarkAsDirty()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
InputSystem.TrackDirtyInputActionAsset(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
internal bool IsEmpty()
|
||||
{
|
||||
return actionMaps.Count == 0 && controlSchemes.Count == 0;
|
||||
}
|
||||
|
||||
internal void OnWantToChangeSetup()
|
||||
{
|
||||
if (m_ActionMaps.LengthSafe() > 0)
|
||||
m_ActionMaps[0].OnWantToChangeSetup();
|
||||
}
|
||||
|
||||
internal void OnSetupChanged()
|
||||
{
|
||||
MarkAsDirty();
|
||||
|
||||
if (m_ActionMaps.LengthSafe() > 0)
|
||||
m_ActionMaps[0].OnSetupChanged();
|
||||
else
|
||||
m_SharedStateForAllMaps = null;
|
||||
}
|
||||
|
||||
private void ReResolveIfNecessary(bool fullResolve)
|
||||
{
|
||||
if (m_SharedStateForAllMaps == null)
|
||||
return;
|
||||
|
||||
Debug.Assert(m_ActionMaps != null && m_ActionMaps.Length > 0);
|
||||
// State is share between all action maps in the asset. Resolving bindings for the
|
||||
// first map will resolve them for all maps.
|
||||
m_ActionMaps[0].LazyResolveBindings(fullResolve);
|
||||
}
|
||||
|
||||
internal void ResolveBindingsIfNecessary()
|
||||
{
|
||||
if (m_ActionMaps.LengthSafe() > 0)
|
||||
foreach (var map in m_ActionMaps)
|
||||
if (map.ResolveBindingsIfNecessary())
|
||||
break;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
Disable();
|
||||
if (m_SharedStateForAllMaps != null)
|
||||
{
|
||||
m_SharedStateForAllMaps.Dispose(); // Will clean up InputActionMap state.
|
||||
m_SharedStateForAllMaps = null;
|
||||
}
|
||||
}
|
||||
|
||||
////TODO: ApplyBindingOverrides, RemoveBindingOverrides, RemoveAllBindingOverrides
|
||||
|
||||
[SerializeField] internal InputActionMap[] m_ActionMaps;
|
||||
[SerializeField] internal InputControlScheme[] m_ControlSchemes;
|
||||
#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
|
||||
[SerializeField] internal bool m_IsProjectWide;
|
||||
#endif
|
||||
|
||||
////TODO: make this persistent across domain reloads
|
||||
/// <summary>
|
||||
/// Shared state for all action maps in the asset.
|
||||
/// </summary>
|
||||
[NonSerialized] internal InputActionState m_SharedStateForAllMaps;
|
||||
[NonSerialized] internal InputBinding? m_BindingMask;
|
||||
[NonSerialized] internal int m_ParameterOverridesCount;
|
||||
[NonSerialized] internal InputActionRebindingExtensions.ParameterOverride[] m_ParameterOverrides;
|
||||
|
||||
[NonSerialized] internal InputActionMap.DeviceArray m_Devices;
|
||||
|
||||
[Serializable]
|
||||
internal struct WriteFileJson
|
||||
{
|
||||
public string name;
|
||||
public InputActionMap.WriteMapJson[] maps;
|
||||
public InputControlScheme.SchemeJson[] controlSchemes;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal struct WriteFileJsonNoName
|
||||
{
|
||||
public InputActionMap.WriteMapJson[] maps;
|
||||
public InputControlScheme.SchemeJson[] controlSchemes;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal struct ReadFileJson
|
||||
{
|
||||
public string name;
|
||||
public InputActionMap.ReadMapJson[] maps;
|
||||
public InputControlScheme.SchemeJson[] controlSchemes;
|
||||
|
||||
public void ToAsset(InputActionAsset asset)
|
||||
{
|
||||
asset.name = name;
|
||||
asset.m_ActionMaps = new InputActionMap.ReadFileJson {maps = maps}.ToMaps();
|
||||
asset.m_ControlSchemes = InputControlScheme.SchemeJson.ToSchemes(controlSchemes);
|
||||
|
||||
// Link maps to their asset.
|
||||
if (asset.m_ActionMaps != null)
|
||||
foreach (var map in asset.m_ActionMaps)
|
||||
map.m_Asset = asset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 39fa3d8997a24136984ca6e2c99902bc
|
||||
timeCreated: 1509594627
|
||||
@@ -0,0 +1,68 @@
|
||||
namespace UnityEngine.InputSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates what type of change related to an <see cref="InputAction">input action</see> occurred.
|
||||
/// </summary>
|
||||
/// <seealso cref="InputSystem.onActionChange"/>
|
||||
public enum InputActionChange
|
||||
{
|
||||
/// <summary>
|
||||
/// An individual action was enabled.
|
||||
/// </summary>
|
||||
/// <seealso cref="InputAction.Enable"/>
|
||||
ActionEnabled,
|
||||
|
||||
/// <summary>
|
||||
/// An individual action was disabled.
|
||||
/// </summary>
|
||||
/// <seealso cref="InputAction.Disable"/>
|
||||
ActionDisabled,
|
||||
|
||||
/// <summary>
|
||||
/// An <see cref="InputActionMap">action map</see> was enabled.
|
||||
/// </summary>
|
||||
/// <seealso cref="InputActionMap.Enable"/>
|
||||
ActionMapEnabled,
|
||||
|
||||
/// <summary>
|
||||
/// An <see cref="InputActionMap">action map</see> was disabled.
|
||||
/// </summary>
|
||||
/// <seealso cref="InputActionMap.Disable"/>
|
||||
ActionMapDisabled,
|
||||
|
||||
/// <summary>
|
||||
/// An <see cref="InputAction"/> was started.
|
||||
/// </summary>
|
||||
/// <seealso cref="InputAction.started"/>
|
||||
/// <seealso cref="InputActionPhase.Started"/>
|
||||
ActionStarted,
|
||||
|
||||
/// <summary>
|
||||
/// An <see cref="InputAction"/> was performed.
|
||||
/// </summary>
|
||||
/// <seealso cref="InputAction.performed"/>
|
||||
/// <seealso cref="InputActionPhase.Performed"/>
|
||||
ActionPerformed,
|
||||
|
||||
/// <summary>
|
||||
/// An <see cref="InputAction"/> was canceled.
|
||||
/// </summary>
|
||||
/// <seealso cref="InputAction.canceled"/>
|
||||
/// <seealso cref="InputActionPhase.Canceled"/>
|
||||
ActionCanceled,
|
||||
|
||||
/// <summary>
|
||||
/// Bindings on an action or set of actions are about to be re-resolved. This is called while <see cref="InputAction.controls"/>
|
||||
/// for actions are still untouched and thus still reflect the old binding state of each action.
|
||||
/// </summary>
|
||||
/// <seealso cref="InputAction.controls"/>
|
||||
BoundControlsAboutToChange,
|
||||
|
||||
/// <summary>
|
||||
/// Bindings on an action or set of actions have been resolved. This is called after <see cref="InputAction.controls"/>
|
||||
/// have been updated.
|
||||
/// </summary>
|
||||
/// <seealso cref="InputAction.controls"/>
|
||||
BoundControlsChanged,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c77cd15ef9ca14d3e928d192049c276d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 87354c477cc09c441b957b3aae10d813
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 050df780a05f4754b28e85d32a88da95
|
||||
timeCreated: 1647030981
|
||||
@@ -0,0 +1,135 @@
|
||||
using UnityEngine.InputSystem.Interactions;
|
||||
|
||||
////REVIEW: this goes beyond just actions; is there a better name? just InputPhase?
|
||||
|
||||
////REVIEW: what about opening up phases completely to interactions and allow them to come up with whatever custom phases?
|
||||
|
||||
namespace UnityEngine.InputSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Trigger phase of an <see cref="InputAction"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Actions can be triggered in steps. For example, a <see cref="SlowTapInteraction">
|
||||
/// 'slow tap'</see> will put an action into <see cref="Started"/> phase when a button
|
||||
/// the action is bound to is pressed. At that point, however, the action still
|
||||
/// has to wait for the expiration of a timer in order to make it a 'slow tap'. If
|
||||
/// the button is release before the timer expires, the action will be <see cref="Canceled"/>
|
||||
/// whereas if the button is held long enough, the action will be <see cref="Performed"/>.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputAction.phase"/>
|
||||
/// <seealso cref="InputAction.CallbackContext.phase"/>
|
||||
/// <seealso cref="InputAction.started"/>
|
||||
/// <seealso cref="InputAction.performed"/>
|
||||
/// <seealso cref="InputAction.canceled"/>
|
||||
public enum InputActionPhase
|
||||
{
|
||||
/// <summary>
|
||||
/// The action is not enabled.
|
||||
/// </summary>
|
||||
Disabled,
|
||||
|
||||
/// <summary>
|
||||
/// The action is enabled and waiting for input on its associated controls.
|
||||
///
|
||||
/// This is the phase that an action goes back to once it has been <see cref="Performed"/>
|
||||
/// or <see cref="Canceled"/>.
|
||||
/// </summary>
|
||||
Waiting,
|
||||
|
||||
/// <summary>
|
||||
/// An associated control has been actuated such that it may lead to the action
|
||||
/// being triggered. Will lead to <see cref="InputAction.started"/> getting called.
|
||||
///
|
||||
/// This phase will only be invoked if there are interactions on the respective control
|
||||
/// binding. Without any interactions, an action will go straight from <see cref="Waiting"/>
|
||||
/// into <see cref="Performed"/> and back into <see cref="Waiting"/> whenever an associated
|
||||
/// control changes value.
|
||||
///
|
||||
/// An example of an interaction that uses the <see cref="Started"/> phase is <see cref="SlowTapInteraction"/>.
|
||||
/// When the button it is bound to is pressed, the associated action goes into the <see cref="Started"/>
|
||||
/// phase. At this point, the interaction does not yet know whether the button press will result in just
|
||||
/// a tap or will indeed result in slow tap. If the button is released before the time it takes to
|
||||
/// recognize a slow tap, then the action will go to <see cref="Canceled"/> and then back to <see cref="Waiting"/>.
|
||||
/// If, however, the button is held long enough for it to qualify as a slow tap, the action will progress
|
||||
/// to <see cref="Performed"/> and then go back to <see cref="Waiting"/>.
|
||||
///
|
||||
/// <see cref="Started"/> can be useful for UI feedback. For example, in a game where the weapon can be charged,
|
||||
/// UI feedback can be initiated when the action is <see cref="Started"/>.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// fireAction.started +=
|
||||
/// ctx =>
|
||||
/// {
|
||||
/// if (ctx.interaction is SlowTapInteraction)
|
||||
/// {
|
||||
/// weaponCharging = true;
|
||||
/// weaponChargeStartTime = ctx.time;
|
||||
/// }
|
||||
/// }
|
||||
/// fireAction.canceled +=
|
||||
/// ctx =>
|
||||
/// {
|
||||
/// weaponCharging = false;
|
||||
/// }
|
||||
/// fireAction.performed +=
|
||||
/// ctx =>
|
||||
/// {
|
||||
/// Fire();
|
||||
/// weaponCharging = false;
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
///
|
||||
/// By default, an action is started as soon as a control moves away from its default value. This is
|
||||
/// the case for both <see cref="InputActionType.Button"/> actions (which, however, does not yet have to mean
|
||||
/// that the button press threshold has been reached; see <see cref="InputSettings.defaultButtonPressPoint"/>)
|
||||
/// and <see cref="InputActionType.Value"/> actions. <see cref="InputActionType.PassThrough"/> does not use
|
||||
/// the <c>Started</c> phase and instead goes straight to <see cref="Performed"/>.
|
||||
///
|
||||
/// For <see cref="InputActionType.Value"/> actions, <c>Started</c> will immediately be followed by <see cref="Performed"/>.
|
||||
///
|
||||
/// Note that interactions (see <see cref="IInputInteraction"/>) can alter how an action does or does not progress through
|
||||
/// the phases.
|
||||
/// </summary>
|
||||
Started,
|
||||
|
||||
/// <summary>
|
||||
/// The action has been performed. Leads to <see cref="InputAction.performed"/> getting called.
|
||||
///
|
||||
/// By default, a <see cref="InputActionType.Button"/> action performs when a control crosses the button
|
||||
/// press threshold (see <see cref="InputSettings.defaultButtonPressPoint"/>), a <see cref="InputActionType.Value"/>
|
||||
/// action performs on any value change that isn't the default value, and a <see cref="InputActionType.PassThrough"/>
|
||||
/// action performs on any value change including going back to the default value.
|
||||
///
|
||||
/// Note that interactions (see <see cref="IInputInteraction"/>) can alter how an action does or does not progress through
|
||||
/// the phases.
|
||||
///
|
||||
/// For a given action, finding out whether it was performed in the current frame can be done with <see cref="InputAction.WasPerformedThisFrame"/>.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// action.WasPerformedThisFrame();
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </summary>
|
||||
Performed,
|
||||
|
||||
/// <summary>
|
||||
/// The action has stopped. Leads to <see cref="InputAction.canceled"/> getting called.
|
||||
///
|
||||
/// By default, a <see cref="InputActionType.Button"/> action cancels when a control falls back below the button
|
||||
/// press threshold (see <see cref="InputSettings.defaultButtonPressPoint"/>) and a <see cref="InputActionType.Value"/>
|
||||
/// action cancels when a control moves back to its default value. A <see cref="InputActionType.PassThrough"/> action
|
||||
/// does not generally cancel based on input on its controls.
|
||||
///
|
||||
/// An action will also get canceled when it is disabled while in progress (see <see cref="InputAction.Disable"/>).
|
||||
/// Also, when an <see cref="InputDevice"/> that is
|
||||
///
|
||||
/// Note that interactions (see <see cref="IInputInteraction"/>) can alter how an action does or does not progress through
|
||||
/// the phases.
|
||||
/// </summary>
|
||||
Canceled
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c2db5674bb583c0449389396f935abf8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,176 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.InputSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// A serializable property type that can either reference an action externally defined
|
||||
/// in an <see cref="InputActionAsset"/> or define a new action directly on the property.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This struct is meant to be used for serialized fields in <c>MonoBehaviour</c> and
|
||||
/// <c>ScriptableObject</c> classes. It has a custom property drawer attached to it
|
||||
/// that allows to switch between using the property as a reference and using it
|
||||
/// to define an action in place.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// public class MyBehavior : MonoBehaviour
|
||||
/// {
|
||||
/// // This can be edited in the inspector to either reference an existing
|
||||
/// // action or to define an action directly on the component.
|
||||
/// public InputActionProperty myAction;
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputAction"/>
|
||||
/// <seealso cref="InputActionReference"/>
|
||||
[Serializable]
|
||||
public struct InputActionProperty : IEquatable<InputActionProperty>, IEquatable<InputAction>, IEquatable<InputActionReference>
|
||||
{
|
||||
/// <summary>
|
||||
/// The effective action held on to by the property.
|
||||
/// </summary>
|
||||
/// <value>The effective action object contained in the property.</value>
|
||||
/// <remarks>
|
||||
/// This property will return <c>null</c> if the property is using a <see cref="reference"/> and
|
||||
/// the referenced action cannot be found. Also, it will be <c>null</c> if the property
|
||||
/// has been manually initialized with a <c>null</c> <see cref="InputAction"/> using
|
||||
/// <see cref="InputActionProperty(InputAction)"/>.
|
||||
/// </remarks>
|
||||
public InputAction action => m_UseReference ? m_Reference != null ? m_Reference.action : null : m_Action;
|
||||
|
||||
/// <summary>
|
||||
/// If the property is set to use a reference to the action, this property returns
|
||||
/// the reference. Otherwise it returns <c>null</c>.
|
||||
/// </summary>
|
||||
/// <value>Reference to external input action, if defined.</value>
|
||||
public InputActionReference reference => m_UseReference ? m_Reference : null;
|
||||
|
||||
/// <summary>
|
||||
/// The serialized loose action created in code serialized with this property.
|
||||
/// </summary>
|
||||
/// <value>The serialized action field.</value>
|
||||
internal InputAction serializedAction => m_Action;
|
||||
|
||||
/// <summary>
|
||||
/// The serialized reference to an external action.
|
||||
/// </summary>
|
||||
/// <value>The serialized reference field.</value>
|
||||
internal InputActionReference serializedReference => m_Reference;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the property to contain the given action.
|
||||
/// </summary>
|
||||
/// <param name="action">An action.</param>
|
||||
/// <remarks>
|
||||
/// When the struct is serialized, it will serialize the given action as part of it.
|
||||
/// The <see cref="reference"/> property will return <c>null</c>.
|
||||
/// </remarks>
|
||||
public InputActionProperty(InputAction action)
|
||||
{
|
||||
m_UseReference = false;
|
||||
m_Action = action;
|
||||
m_Reference = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the property to use the given action reference.
|
||||
/// </summary>
|
||||
/// <param name="reference">Reference to an <see cref="InputAction"/>.</param>
|
||||
/// <remarks>
|
||||
/// When the struct is serialized, it will only serialize a reference to
|
||||
/// the given <paramref name="reference"/> object.
|
||||
/// </remarks>
|
||||
public InputActionProperty(InputActionReference reference)
|
||||
{
|
||||
m_UseReference = true;
|
||||
m_Action = null;
|
||||
m_Reference = reference;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two action properties to see whether they refer to the same action.
|
||||
/// </summary>
|
||||
/// <param name="other">Another action property.</param>
|
||||
/// <returns>True if both properties refer to the same action.</returns>
|
||||
public bool Equals(InputActionProperty other)
|
||||
{
|
||||
return m_Reference == other.m_Reference &&
|
||||
m_UseReference == other.m_UseReference &&
|
||||
m_Action == other.m_Action;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check whether the property refers to the same action.
|
||||
/// </summary>
|
||||
/// <param name="other">An action.</param>
|
||||
/// <returns>True if <see cref="action"/> is the same as <paramref name="other"/>.</returns>
|
||||
public bool Equals(InputAction other)
|
||||
{
|
||||
return ReferenceEquals(action, other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check whether the property references the same action.
|
||||
/// </summary>
|
||||
/// <param name="other">An action reference.</param>
|
||||
/// <returns>True if the property and <paramref name="other"/> reference the same action.</returns>
|
||||
public bool Equals(InputActionReference other)
|
||||
{
|
||||
return m_Reference == other;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check whether the given object is an InputActionProperty referencing the same action.
|
||||
/// </summary>
|
||||
/// <param name="obj">An object or <c>null</c>.</param>
|
||||
/// <returns>True if the given <paramref name="obj"/> is an InputActionProperty equivalent to this one.</returns>
|
||||
/// <seealso cref="Equals(InputActionProperty)"/>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (m_UseReference)
|
||||
return Equals(obj as InputActionReference);
|
||||
return Equals(obj as InputAction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute a hash code for the object.
|
||||
/// </summary>
|
||||
/// <returns>A hash code.</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
if (m_UseReference)
|
||||
return m_Reference != null ? m_Reference.GetHashCode() : 0;
|
||||
return m_Action != null ? m_Action.GetHashCode() : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare the two properties for equivalence.
|
||||
/// </summary>
|
||||
/// <param name="left">The first property.</param>
|
||||
/// <param name="right">The second property.</param>
|
||||
/// <returns>True if the two action properties are equivalent.</returns>
|
||||
/// <seealso cref="Equals(InputActionProperty)"/>
|
||||
public static bool operator==(InputActionProperty left, InputActionProperty right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare the two properties for not being equivalent.
|
||||
/// </summary>
|
||||
/// <param name="left">The first property.</param>
|
||||
/// <param name="right">The second property.</param>
|
||||
/// <returns>True if the two action properties are not equivalent.</returns>
|
||||
/// <seealso cref="Equals(InputActionProperty)"/>
|
||||
public static bool operator!=(InputActionProperty left, InputActionProperty right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
|
||||
[SerializeField] private bool m_UseReference;
|
||||
[SerializeField] private InputAction m_Action;
|
||||
[SerializeField] private InputActionReference m_Reference;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0c43a6876f1eb4fdf8b9dc2fee926776
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bd166cb4f7947a8498e7ad07a9c04294
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,236 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
////REVIEW: Can we somehow make this a simple struct? The one problem we have is that we can't put struct instances as sub-assets into
|
||||
//// the import (i.e. InputActionImporter can't do AddObjectToAsset with them). However, maybe there's a way around that. The thing
|
||||
//// is that we really want to store the asset reference plus the action GUID on the *user* side, i.e. the referencing side. Right
|
||||
//// now, what happens is that InputActionImporter puts these objects along with the reference and GUID they contain in the
|
||||
//// *imported* object, i.e. right with the asset. This partially defeats the whole purpose of having these objects and it means
|
||||
//// that now the GUID doesn't really matter anymore. Rather, it's the file ID that now has to be stable.
|
||||
////
|
||||
//// If we always store the GUID and asset reference on the user side, we can put the serialized data *anywhere* and it'll remain
|
||||
//// save and proper no matter what we do in InputActionImporter.
|
||||
|
||||
////REVIEW: should this throw if you try to assign an action that is not a singleton?
|
||||
|
||||
////REVIEW: akin to this, also have an InputActionMapReference?
|
||||
|
||||
namespace UnityEngine.InputSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// References a specific <see cref="InputAction"/> in an <see cref="InputActionMap"/>
|
||||
/// stored inside an <see cref="InputActionAsset"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The difference to a plain reference directly to an <see cref="InputAction"/> object is
|
||||
/// that an InputActionReference can be serialized without causing the referenced <see cref="InputAction"/>
|
||||
/// to be serialized as well. The reference will remain intact even if the action or the map
|
||||
/// that contains the action is renamed.
|
||||
///
|
||||
/// References can be set up graphically in the editor by dropping individual actions from the project
|
||||
/// browser onto a reference field.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputActionProperty"/>
|
||||
/// <seealso cref="InputAction"/>
|
||||
/// <seealso cref="InputActionAsset"/>
|
||||
public class InputActionReference : ScriptableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// The asset that the referenced action is part of. Null if the reference
|
||||
/// is not initialized or if the asset has been deleted.
|
||||
/// </summary>
|
||||
/// <value>InputActionAsset of the referenced action.</value>
|
||||
public InputActionAsset asset => m_Asset;
|
||||
|
||||
/// <summary>
|
||||
/// The action that the reference resolves to. Null if the action
|
||||
/// cannot be found.
|
||||
/// </summary>
|
||||
/// <value>The action that reference points to.</value>
|
||||
/// <remarks>
|
||||
/// Actions are resolved on demand based on their internally stored IDs.
|
||||
/// </remarks>
|
||||
public InputAction action
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_Action == null)
|
||||
{
|
||||
if (m_Asset == null)
|
||||
return null;
|
||||
|
||||
m_Action = m_Asset.FindAction(new Guid(m_ActionId));
|
||||
}
|
||||
|
||||
return m_Action;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the reference to refer to the given action.
|
||||
/// </summary>
|
||||
/// <param name="action">An input action. Must be contained in an <see cref="InputActionMap"/>
|
||||
/// that is itself contained in an <see cref="InputActionAsset"/>. Can be <c>null</c> in which
|
||||
/// case the reference is reset to its default state which does not reference an action.</param>
|
||||
/// <exception cref="InvalidOperationException"><paramref name="action"/> is not contained in an
|
||||
/// <see cref="InputActionMap"/> that is itself contained in an <see cref="InputActionAsset"/>.</exception>
|
||||
public void Set(InputAction action)
|
||||
{
|
||||
if (action == null)
|
||||
{
|
||||
m_Asset = default;
|
||||
m_ActionId = default;
|
||||
return;
|
||||
}
|
||||
|
||||
var map = action.actionMap;
|
||||
if (map == null || map.asset == null)
|
||||
throw new InvalidOperationException(
|
||||
$"Action '{action}' must be part of an InputActionAsset in order to be able to create an InputActionReference for it");
|
||||
|
||||
SetInternal(map.asset, action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Look up an action in the given asset and initialize the reference to
|
||||
/// point to it.
|
||||
/// </summary>
|
||||
/// <param name="asset">An .inputactions asset.</param>
|
||||
/// <param name="mapName">Name of the <see cref="InputActionMap"/> in <paramref name="asset"/>
|
||||
/// (see <see cref="InputActionAsset.actionMaps"/>). Case-insensitive.</param>
|
||||
/// <param name="actionName">Name of the action in <paramref name="mapName"/>. Case-insensitive.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="asset"/> is <c>null</c> -or-
|
||||
/// <paramref name="mapName"/> is <c>null</c> or empty -or- <paramref name="actionName"/>
|
||||
/// is <c>null</c> or empty.</exception>
|
||||
/// <exception cref="ArgumentException">No action map called <paramref name="mapName"/> could
|
||||
/// be found in <paramref name="asset"/> -or- no action called <paramref name="actionName"/>
|
||||
/// could be found in the action map called <paramref name="mapName"/> in <paramref name="asset"/>.</exception>
|
||||
public void Set(InputActionAsset asset, string mapName, string actionName)
|
||||
{
|
||||
if (asset == null)
|
||||
throw new ArgumentNullException(nameof(asset));
|
||||
if (string.IsNullOrEmpty(mapName))
|
||||
throw new ArgumentNullException(nameof(mapName));
|
||||
if (string.IsNullOrEmpty(actionName))
|
||||
throw new ArgumentNullException(nameof(actionName));
|
||||
|
||||
var actionMap = asset.FindActionMap(mapName);
|
||||
if (actionMap == null)
|
||||
throw new ArgumentException($"No action map '{mapName}' in '{asset}'", nameof(mapName));
|
||||
|
||||
var action = actionMap.FindAction(actionName);
|
||||
if (action == null)
|
||||
throw new ArgumentException($"No action '{actionName}' in map '{mapName}' of asset '{asset}'",
|
||||
nameof(actionName));
|
||||
|
||||
SetInternal(asset, action);
|
||||
}
|
||||
|
||||
private void SetInternal(InputActionAsset asset, InputAction action)
|
||||
{
|
||||
var actionMap = action.actionMap;
|
||||
if (!asset.actionMaps.Contains(actionMap))
|
||||
throw new ArgumentException(
|
||||
$"Action '{action}' is not contained in asset '{asset}'", nameof(action));
|
||||
|
||||
m_Asset = asset;
|
||||
m_ActionId = action.id.ToString();
|
||||
name = GetDisplayName(action);
|
||||
|
||||
////REVIEW: should this dirty the asset if IDs had not been generated yet?
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a string representation of the reference useful for debugging.
|
||||
/// </summary>
|
||||
/// <returns>A string representation of the reference.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
try
|
||||
{
|
||||
var action = this.action;
|
||||
return $"{m_Asset.name}:{action.actionMap.name}/{action.name}";
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (m_Asset != null)
|
||||
return $"{m_Asset.name}:{m_ActionId}";
|
||||
}
|
||||
|
||||
return base.ToString();
|
||||
}
|
||||
|
||||
internal static string GetDisplayName(InputAction action)
|
||||
{
|
||||
return !string.IsNullOrEmpty(action?.actionMap?.name) ? $"{action.actionMap?.name}/{action.name}" : action?.name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a string representation useful for showing in UI.
|
||||
/// </summary>
|
||||
internal string ToDisplayName()
|
||||
{
|
||||
return string.IsNullOrEmpty(name) ? GetDisplayName(action) : name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert an InputActionReference to the InputAction it points to.
|
||||
/// </summary>
|
||||
/// <param name="reference">An InputActionReference object. Can be null.</param>
|
||||
/// <returns>The value of <see cref="action"/> from <paramref name="reference"/>. Can be null.</returns>
|
||||
public static implicit operator InputAction(InputActionReference reference)
|
||||
{
|
||||
return reference?.action;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new InputActionReference object that references the given action.
|
||||
/// </summary>
|
||||
/// <param name="action">An input action. Must be contained in an <see cref="InputActionMap"/>
|
||||
/// that is itself contained in an <see cref="InputActionAsset"/>. Can be <c>null</c> in which
|
||||
/// case the reference is reset to its default state which does not reference an action.</param>
|
||||
/// <returns>A new InputActionReference referencing <paramref name="action"/>.</returns>
|
||||
public static InputActionReference Create(InputAction action)
|
||||
{
|
||||
if (action == null)
|
||||
return null;
|
||||
var reference = CreateInstance<InputActionReference>();
|
||||
reference.Set(action);
|
||||
return reference;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the cached <see cref="m_Action"/> field for all current <see cref="InputActionReference"/> objects.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// After calling this, the next call to <see cref="action"/> will retrieve a new <see cref="InputAction"/> reference from the existing <see cref="InputActionAsset"/> just as if
|
||||
/// using it for the first time. The serialized <see cref="m_Asset"/> and <see cref="m_ActionId"/> fields are not touched and will continue to hold their current values.
|
||||
///
|
||||
/// This method is used to clear the Action references when exiting PlayMode since those objects are no longer valid.
|
||||
/// </remarks>
|
||||
internal static void ResetCachedAction()
|
||||
{
|
||||
var allActionRefs = Resources.FindObjectsOfTypeAll(typeof(InputActionReference));
|
||||
foreach (InputActionReference obj in allActionRefs)
|
||||
{
|
||||
obj.m_Action = null;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField] internal InputActionAsset m_Asset;
|
||||
// Can't serialize System.Guid and Unity's GUID is editor only so these
|
||||
// go out as strings.
|
||||
[SerializeField] internal string m_ActionId;
|
||||
|
||||
/// <summary>
|
||||
/// The resolved, cached input action.
|
||||
/// </summary>
|
||||
[NonSerialized] private InputAction m_Action;
|
||||
|
||||
// Make annoying Microsoft code analyzer happy.
|
||||
public InputAction ToInputAction()
|
||||
{
|
||||
return action;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fc1515ab76e54f068e2f2207940fab32
|
||||
timeCreated: 1509649918
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae31a855334ecf64f848ae8e0b4e5a24
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ca5a63bf867c240879f2eb2179771ffb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,736 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
|
||||
////REVIEW: why not switch to this being the default mechanism? seems like this could allow us to also solve
|
||||
//// the actions-update-when-not-expected problem; plus give us access to easy polling
|
||||
|
||||
////REVIEW: should this automatically unsubscribe itself on disposal?
|
||||
|
||||
////TODO: make it possible to persist this same way that it should be possible to persist InputEventTrace
|
||||
|
||||
////TODO: make this one thread-safe
|
||||
|
||||
////TODO: add random access capability
|
||||
|
||||
////TODO: protect traces against controls changing configuration (if state layouts change, we're affected)
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Records the triggering of actions into a sequence of events that can be replayed at will.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is an alternate way to the callback-based responses (such as <see cref="InputAction.performed"/>)
|
||||
/// of <see cref="InputAction">input actions</see>. Instead of executing response code right away whenever
|
||||
/// an action triggers, an <see cref="RecordAction">event is recorded</see> which can then be queried on demand.
|
||||
///
|
||||
/// The recorded data will stay valid even if the bindings on the actions are changed (e.g. by enabling a different
|
||||
/// set of bindings through altering <see cref="InputAction.bindingMask"/> or <see cref="InputActionMap.devices"/> or
|
||||
/// when modifying the paths of bindings altogether). Note, however, that when this happens, a trace will have
|
||||
/// to make a private copy of the data that stores the binding resolution state. This means that there can be
|
||||
/// GC allocation spike when reconfiguring actions that have recorded data in traces.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var trace = new InputActionTrace();
|
||||
///
|
||||
/// // Subscribe trace to single action.
|
||||
/// // (Use UnsubscribeFrom to unsubscribe)
|
||||
/// trace.SubscribeTo(myAction);
|
||||
///
|
||||
/// // Subscribe trace to entire action map.
|
||||
/// // (Use UnsubscribeFrom to unsubscribe)
|
||||
/// trace.SubscribeTo(myActionMap);
|
||||
///
|
||||
/// // Subscribe trace to all actions in the system.
|
||||
/// trace.SubscribeToAll();
|
||||
///
|
||||
/// // Record a single triggering of an action.
|
||||
/// myAction.performed +=
|
||||
/// ctx =>
|
||||
/// {
|
||||
/// if (ctx.ReadValue<float>() > 0.5f)
|
||||
/// trace.RecordAction(ctx);
|
||||
/// };
|
||||
///
|
||||
/// // Output trace to console.
|
||||
/// Debug.Log(string.Join(",\n", trace));
|
||||
///
|
||||
/// // Walk through all recorded actions and then clear trace.
|
||||
/// foreach (var record in trace)
|
||||
/// {
|
||||
/// Debug.Log($"{record.action} was {record.phase} by control {record.control} at {record.time}");
|
||||
///
|
||||
/// // To read out the value, you either have to know the value type or read the
|
||||
/// // value out as a generic byte buffer. Here we assume that the value type is
|
||||
/// // float.
|
||||
///
|
||||
/// Debug.Log("Value: " + record.ReadValue<float>());
|
||||
///
|
||||
/// // An alternative is read the value as an object. In this case, you don't have
|
||||
/// // to know the value type but there will be a boxed object allocation.
|
||||
/// Debug.Log("Value: " + record.ReadValueAsObject());
|
||||
/// }
|
||||
/// trace.Clear();
|
||||
///
|
||||
/// // Unsubscribe trace from everything.
|
||||
/// trace.UnsubscribeFromAll();
|
||||
///
|
||||
/// // Release memory held by trace.
|
||||
/// trace.Dispose();
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputAction.started"/>
|
||||
/// <seealso cref="InputAction.performed"/>
|
||||
/// <seealso cref="InputAction.canceled"/>
|
||||
/// <seealso cref="InputSystem.onActionChange"/>
|
||||
public sealed class InputActionTrace : IEnumerable<InputActionTrace.ActionEventPtr>, IDisposable
|
||||
{
|
||||
////REVIEW: this is of limited use without having access to ActionEvent
|
||||
/// <summary>
|
||||
/// Directly access the underlying raw memory queue.
|
||||
/// </summary>
|
||||
public InputEventBuffer buffer => m_EventBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of events in the associated event buffer.
|
||||
/// </summary>
|
||||
public int count => m_EventBuffer.eventCount;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new default initialized <c>InputActionTrace</c>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When you use this constructor, the new InputActionTrace object does not start recording any actions.
|
||||
/// To record actions, you must explicitly set them up after creating the object.
|
||||
/// Alternatively, you can use one of the other constructor overloads which begin recording actions immediately.
|
||||
/// </remarks>
|
||||
/// <seealso cref="SubscribeTo(InputAction)"/>
|
||||
/// <seealso cref="SubscribeTo(InputActionMap)"/>
|
||||
/// <seealso cref="SubscribeToAll"/>
|
||||
public InputActionTrace()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new <c>InputActionTrace</c> that records <paramref name="action"/>.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to be recorded.</param>
|
||||
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="action"/> is <c>null</c>.</exception>
|
||||
public InputActionTrace(InputAction action)
|
||||
{
|
||||
if (action == null)
|
||||
throw new ArgumentNullException(nameof(action));
|
||||
SubscribeTo(action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new <c>InputActionTrace</c> that records all actions in <paramref name="actionMap"/>.
|
||||
/// </summary>
|
||||
/// <param name="actionMap">The action-map containing actions to be recorded.</param>
|
||||
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="action"/> is <c>null</c>.</exception>
|
||||
public InputActionTrace(InputActionMap actionMap)
|
||||
{
|
||||
if (actionMap == null)
|
||||
throw new ArgumentNullException(nameof(actionMap));
|
||||
SubscribeTo(actionMap);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Record any action getting triggered anywhere.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This does not require the trace to actually hook into every single action or action map in the system.
|
||||
/// Instead, the trace will listen to <see cref="InputSystem.onActionChange"/> and automatically record
|
||||
/// every triggered action.
|
||||
/// </remarks>
|
||||
/// <seealso cref="SubscribeTo(InputAction)"/>
|
||||
/// <seealso cref="SubscribeTo(InputActionMap)"/>
|
||||
public void SubscribeToAll()
|
||||
{
|
||||
if (m_SubscribedToAll)
|
||||
return;
|
||||
|
||||
HookOnActionChange();
|
||||
m_SubscribedToAll = true;
|
||||
|
||||
// Remove manually created subscriptions.
|
||||
while (m_SubscribedActions.length > 0)
|
||||
UnsubscribeFrom(m_SubscribedActions[m_SubscribedActions.length - 1]);
|
||||
while (m_SubscribedActionMaps.length > 0)
|
||||
UnsubscribeFrom(m_SubscribedActionMaps[m_SubscribedActionMaps.length - 1]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribes from all actions currently being recorded.
|
||||
/// </summary>
|
||||
/// <seealso cref="UnsubscribeFrom(InputAction)"/>
|
||||
/// <seealso cref="UnsubscribeFrom(InputActionMap)"/>
|
||||
public void UnsubscribeFromAll()
|
||||
{
|
||||
// Only unhook from OnActionChange if we don't have any recorded actions. If we do have
|
||||
// any, we still need the callback to be notified about when binding data changes.
|
||||
if (count == 0)
|
||||
UnhookOnActionChange();
|
||||
|
||||
m_SubscribedToAll = false;
|
||||
|
||||
while (m_SubscribedActions.length > 0)
|
||||
UnsubscribeFrom(m_SubscribedActions[m_SubscribedActions.length - 1]);
|
||||
while (m_SubscribedActionMaps.length > 0)
|
||||
UnsubscribeFrom(m_SubscribedActionMaps[m_SubscribedActionMaps.length - 1]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes to <paramref name="action"/>.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to be recorded.</param>
|
||||
/// <remarks>
|
||||
/// **Note:** This method does not prevent you from subscribing to the same action multiple times.
|
||||
/// If you subscribe to the same action multiple times, your event buffer will contain duplicate entries.
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentNullException">If <paramref name="action"/> is <c>null</c>.</exception>
|
||||
/// <seealso cref="SubscribeTo(InputActionMap)"/>
|
||||
/// <seealso cref="SubscribeToAll"/>
|
||||
public void SubscribeTo(InputAction action)
|
||||
{
|
||||
if (action == null)
|
||||
throw new ArgumentNullException(nameof(action));
|
||||
|
||||
if (m_CallbackDelegate == null)
|
||||
m_CallbackDelegate = RecordAction;
|
||||
|
||||
action.performed += m_CallbackDelegate;
|
||||
action.started += m_CallbackDelegate;
|
||||
action.canceled += m_CallbackDelegate;
|
||||
|
||||
m_SubscribedActions.AppendWithCapacity(action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes to all actions contained within <paramref name="actionMap"/>.
|
||||
/// </summary>
|
||||
/// <param name="actionMap">The action-map containing all actions to be recorded.</param>
|
||||
/// <remarks>
|
||||
/// **Note:** This method does not prevent you from subscribing to the same action multiple times.
|
||||
/// If you subscribe to the same action multiple times, your event buffer will contain duplicate entries.
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="actionMap"/> is null.</exception>
|
||||
/// <seealso cref="SubscribeTo(InputAction)"/>
|
||||
/// <seealso cref="SubscribeToAll"/>
|
||||
public void SubscribeTo(InputActionMap actionMap)
|
||||
{
|
||||
if (actionMap == null)
|
||||
throw new ArgumentNullException(nameof(actionMap));
|
||||
|
||||
if (m_CallbackDelegate == null)
|
||||
m_CallbackDelegate = RecordAction;
|
||||
|
||||
actionMap.actionTriggered += m_CallbackDelegate;
|
||||
|
||||
m_SubscribedActionMaps.AppendWithCapacity(actionMap);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribes from an action, if that action was previously subscribed to.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to unsubscribe from.</param>
|
||||
/// <remarks>
|
||||
/// **Note:** This method has no side effects if you attempt to unsubscribe from an action that you have not previously subscribed to.
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="action"/> is <c>null</c>.</exception>
|
||||
/// <seealso cref="UnsubscribeFrom(InputActionMap)"/>
|
||||
/// <seealso cref="UnsubscribeFromAll"/>
|
||||
public void UnsubscribeFrom(InputAction action)
|
||||
{
|
||||
if (action == null)
|
||||
throw new ArgumentNullException(nameof(action));
|
||||
|
||||
if (m_CallbackDelegate == null)
|
||||
return;
|
||||
|
||||
action.performed -= m_CallbackDelegate;
|
||||
action.started -= m_CallbackDelegate;
|
||||
action.canceled -= m_CallbackDelegate;
|
||||
|
||||
var index = m_SubscribedActions.IndexOfReference(action);
|
||||
if (index != -1)
|
||||
m_SubscribedActions.RemoveAtWithCapacity(index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribes from all actions included in <paramref name="actionMap"/>.
|
||||
/// </summary>
|
||||
/// <param name="actionMap">The action-map containing actions to unsubscribe from.</param>
|
||||
/// <remarks>
|
||||
/// **Note:** This method has no side effects if you attempt to unsubscribe from an action-map that you have not previously subscribed to.
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="actionMap"/> is <c>null</c>.</exception>
|
||||
/// <seealso cref="UnsubscribeFrom(InputAction)"/>
|
||||
/// <seealso cref="UnsubscribeFromAll"/>
|
||||
public void UnsubscribeFrom(InputActionMap actionMap)
|
||||
{
|
||||
if (actionMap == null)
|
||||
throw new ArgumentNullException(nameof(actionMap));
|
||||
|
||||
if (m_CallbackDelegate == null)
|
||||
return;
|
||||
|
||||
actionMap.actionTriggered -= m_CallbackDelegate;
|
||||
|
||||
var index = m_SubscribedActionMaps.IndexOfReference(actionMap);
|
||||
if (index != -1)
|
||||
m_SubscribedActionMaps.RemoveAtWithCapacity(index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Record the triggering of an action as an <see cref="ActionEventPtr">action event</see>.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <see cref="InputAction.performed"/>
|
||||
/// <see cref="InputAction.started"/>
|
||||
/// <see cref="InputAction.canceled"/>
|
||||
/// <see cref="InputActionMap.actionTriggered"/>
|
||||
public unsafe void RecordAction(InputAction.CallbackContext context)
|
||||
{
|
||||
// Find/add state.
|
||||
var stateIndex = m_ActionMapStates.IndexOfReference(context.m_State);
|
||||
if (stateIndex == -1)
|
||||
stateIndex = m_ActionMapStates.AppendWithCapacity(context.m_State);
|
||||
|
||||
// Make sure we get notified if there's a change to binding setups.
|
||||
HookOnActionChange();
|
||||
|
||||
// Allocate event.
|
||||
var valueSizeInBytes = context.valueSizeInBytes;
|
||||
var eventPtr =
|
||||
(ActionEvent*)m_EventBuffer.AllocateEvent(ActionEvent.GetEventSizeWithValueSize(valueSizeInBytes));
|
||||
|
||||
// Initialize event.
|
||||
ref var triggerState = ref context.m_State.actionStates[context.m_ActionIndex];
|
||||
eventPtr->baseEvent.type = ActionEvent.Type;
|
||||
eventPtr->baseEvent.time = triggerState.time;
|
||||
eventPtr->stateIndex = stateIndex;
|
||||
eventPtr->controlIndex = triggerState.controlIndex;
|
||||
eventPtr->bindingIndex = triggerState.bindingIndex;
|
||||
eventPtr->interactionIndex = triggerState.interactionIndex;
|
||||
eventPtr->startTime = triggerState.startTime;
|
||||
eventPtr->phase = triggerState.phase;
|
||||
|
||||
// Store value.
|
||||
// NOTE: If the action triggered from a composite, this stores the value as
|
||||
// read from the composite.
|
||||
// NOTE: Also, the value we store is a fully processed value.
|
||||
var valueBuffer = eventPtr->valueData;
|
||||
context.ReadValue(valueBuffer, valueSizeInBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all recorded data.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// **Note:** This method does not unsubscribe any actions that the instance is listening to, so after clearing the recorded data, new input on those subscribed actions will continue to be recorded.
|
||||
/// </remarks>
|
||||
public void Clear()
|
||||
{
|
||||
m_EventBuffer.Reset();
|
||||
m_ActionMapStates.ClearWithCapacity();
|
||||
}
|
||||
|
||||
~InputActionTrace()
|
||||
{
|
||||
DisposeInternal();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
if (count == 0)
|
||||
return "[]";
|
||||
|
||||
var str = new StringBuilder();
|
||||
str.Append('[');
|
||||
var isFirst = true;
|
||||
foreach (var eventPtr in this)
|
||||
{
|
||||
if (!isFirst)
|
||||
str.Append(",\n");
|
||||
str.Append(eventPtr.ToString());
|
||||
isFirst = false;
|
||||
}
|
||||
str.Append(']');
|
||||
return str.ToString();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
UnsubscribeFromAll();
|
||||
DisposeInternal();
|
||||
}
|
||||
|
||||
private void DisposeInternal()
|
||||
{
|
||||
// Nuke clones we made of InputActionMapStates.
|
||||
for (var i = 0; i < m_ActionMapStateClones.length; ++i)
|
||||
m_ActionMapStateClones[i].Dispose();
|
||||
|
||||
m_EventBuffer.Dispose();
|
||||
m_ActionMapStates.Clear();
|
||||
m_ActionMapStateClones.Clear();
|
||||
|
||||
if (m_ActionChangeDelegate != null)
|
||||
{
|
||||
InputSystem.onActionChange -= m_ActionChangeDelegate;
|
||||
m_ActionChangeDelegate = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that enumerates all action events recorded for this instance.
|
||||
/// </summary>
|
||||
/// <returns>Enumerator instance, never <c>null</c>.</returns>
|
||||
/// <seealso cref="ActionEventPtr"/>
|
||||
public IEnumerator<ActionEventPtr> GetEnumerator()
|
||||
{
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
private bool m_SubscribedToAll;
|
||||
private bool m_OnActionChangeHooked;
|
||||
private InlinedArray<InputAction> m_SubscribedActions;
|
||||
private InlinedArray<InputActionMap> m_SubscribedActionMaps;
|
||||
private InputEventBuffer m_EventBuffer;
|
||||
private InlinedArray<InputActionState> m_ActionMapStates;
|
||||
private InlinedArray<InputActionState> m_ActionMapStateClones;
|
||||
private Action<InputAction.CallbackContext> m_CallbackDelegate;
|
||||
private Action<object, InputActionChange> m_ActionChangeDelegate;
|
||||
|
||||
private void HookOnActionChange()
|
||||
{
|
||||
if (m_OnActionChangeHooked)
|
||||
return;
|
||||
|
||||
if (m_ActionChangeDelegate == null)
|
||||
m_ActionChangeDelegate = OnActionChange;
|
||||
|
||||
InputSystem.onActionChange += m_ActionChangeDelegate;
|
||||
m_OnActionChangeHooked = true;
|
||||
}
|
||||
|
||||
private void UnhookOnActionChange()
|
||||
{
|
||||
if (!m_OnActionChangeHooked)
|
||||
return;
|
||||
|
||||
InputSystem.onActionChange -= m_ActionChangeDelegate;
|
||||
m_OnActionChangeHooked = false;
|
||||
}
|
||||
|
||||
private void OnActionChange(object actionOrMapOrAsset, InputActionChange change)
|
||||
{
|
||||
// If we're subscribed to all actions, check if an action got triggered.
|
||||
if (m_SubscribedToAll)
|
||||
{
|
||||
switch (change)
|
||||
{
|
||||
case InputActionChange.ActionStarted:
|
||||
case InputActionChange.ActionPerformed:
|
||||
case InputActionChange.ActionCanceled:
|
||||
Debug.Assert(actionOrMapOrAsset is InputAction, "Expected an action");
|
||||
var triggeredAction = (InputAction)actionOrMapOrAsset;
|
||||
var actionIndex = triggeredAction.m_ActionIndexInState;
|
||||
var stateForAction = triggeredAction.m_ActionMap.m_State;
|
||||
|
||||
var context = new InputAction.CallbackContext
|
||||
{
|
||||
m_State = stateForAction,
|
||||
m_ActionIndex = actionIndex,
|
||||
};
|
||||
|
||||
RecordAction(context);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// We're only interested in changes to the binding resolution state of actions.
|
||||
if (change != InputActionChange.BoundControlsAboutToChange)
|
||||
return;
|
||||
|
||||
// Grab the associated action map(s).
|
||||
if (actionOrMapOrAsset is InputAction action)
|
||||
CloneActionStateBeforeBindingsChange(action.m_ActionMap);
|
||||
else if (actionOrMapOrAsset is InputActionMap actionMap)
|
||||
CloneActionStateBeforeBindingsChange(actionMap);
|
||||
else if (actionOrMapOrAsset is InputActionAsset actionAsset)
|
||||
foreach (var actionMapInAsset in actionAsset.actionMaps)
|
||||
CloneActionStateBeforeBindingsChange(actionMapInAsset);
|
||||
else
|
||||
Debug.Assert(false, "Expected InputAction, InputActionMap or InputActionAsset");
|
||||
}
|
||||
|
||||
private void CloneActionStateBeforeBindingsChange(InputActionMap actionMap)
|
||||
{
|
||||
// Grab the state.
|
||||
var state = actionMap.m_State;
|
||||
if (state == null)
|
||||
{
|
||||
// Bindings have not been resolved yet for this action map. We shouldn't even be
|
||||
// on the notification list in this case, but just in case, ignore.
|
||||
return;
|
||||
}
|
||||
|
||||
// See if we're using the given state.
|
||||
var stateIndex = m_ActionMapStates.IndexOfReference(state);
|
||||
if (stateIndex == -1)
|
||||
return;
|
||||
|
||||
// Yes, we are so make our own private copy of its current state.
|
||||
// NOTE: We do not put these local InputActionMapStates on the global list.
|
||||
var clone = state.Clone();
|
||||
m_ActionMapStateClones.Append(clone);
|
||||
m_ActionMapStates[stateIndex] = clone;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A wrapper around <see cref="ActionEvent"/> that automatically translates all the
|
||||
/// information in events into their high-level representations.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For example, instead of returning <see cref="ActionEvent.controlIndex">control indices</see>,
|
||||
/// it automatically resolves and returns the respective <see cref="InputControl">controls</see>.
|
||||
/// </remarks>
|
||||
public unsafe struct ActionEventPtr
|
||||
{
|
||||
internal InputActionState m_State;
|
||||
internal ActionEvent* m_Ptr;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="InputAction"/> associated with this action event.
|
||||
/// </summary>
|
||||
public InputAction action => m_State.GetActionOrNull(m_Ptr->bindingIndex);
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="InputActionPhase"/> associated with this action event.
|
||||
/// </summary>
|
||||
/// <seealso cref="InputAction.phase"/>
|
||||
/// <seealso cref="InputAction.CallbackContext.phase"/>
|
||||
public InputActionPhase phase => m_Ptr->phase;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="InputControl"/> instance associated with this action event.
|
||||
/// </summary>
|
||||
public InputControl control => m_State.controls[m_Ptr->controlIndex];
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IInputInteraction"/> instance associated with this action event if applicable, or <c>null</c> if the action event is not associated with an input interaction.
|
||||
/// </summary>
|
||||
public IInputInteraction interaction
|
||||
{
|
||||
get
|
||||
{
|
||||
var index = m_Ptr->interactionIndex;
|
||||
if (index == InputActionState.kInvalidIndex)
|
||||
return null;
|
||||
|
||||
return m_State.interactions[index];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The time, in seconds since your game or app started, that the event occurred.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Times are in seconds and progress linearly in real-time. The timeline is the same as for <see cref="Time.realtimeSinceStartup"/>.
|
||||
/// </remarks>
|
||||
public double time => m_Ptr->baseEvent.time;
|
||||
|
||||
/// <summary>
|
||||
/// The time, in seconds since your game or app started, that the <see cref="phase"/> transitioned into <see cref="InputActionPhase.Started"/>.
|
||||
/// </summary>
|
||||
public double startTime => m_Ptr->startTime;
|
||||
|
||||
/// <summary>
|
||||
/// The duration, in seconds, that has elapsed between when this event was generated and when the
|
||||
/// action <see cref="phase"/> transitioned to <see cref="InputActionPhase.Started"/> and has remained active.
|
||||
/// </summary>
|
||||
public double duration => time - startTime;
|
||||
|
||||
/// <summary>
|
||||
/// The size, in bytes, of the value associated with this action event.
|
||||
/// </summary>
|
||||
public int valueSizeInBytes => m_Ptr->valueSizeInBytes;
|
||||
|
||||
/// <summary>
|
||||
/// Reads the value associated with this event as an <c>object</c>.
|
||||
/// </summary>
|
||||
/// <returns><c>object</c> representing the value of this action event.</returns>
|
||||
/// <seealso cref="ReadOnlyArray{TValue}"/>
|
||||
/// <seealso cref="ReadValue(void*, int)"/>
|
||||
public object ReadValueAsObject()
|
||||
{
|
||||
if (m_Ptr == null)
|
||||
throw new InvalidOperationException("ActionEventPtr is invalid");
|
||||
|
||||
var valuePtr = m_Ptr->valueData;
|
||||
|
||||
// Check if the value came from a composite.
|
||||
var bindingIndex = m_Ptr->bindingIndex;
|
||||
if (m_State.bindingStates[bindingIndex].isPartOfComposite)
|
||||
{
|
||||
// Yes, so have to put the value/struct data we read into a boxed
|
||||
// object based on the value type of the composite.
|
||||
|
||||
var compositeBindingIndex = m_State.bindingStates[bindingIndex].compositeOrCompositeBindingIndex;
|
||||
var compositeIndex = m_State.bindingStates[compositeBindingIndex].compositeOrCompositeBindingIndex;
|
||||
var composite = m_State.composites[compositeIndex];
|
||||
Debug.Assert(composite != null, "NULL composite instance");
|
||||
|
||||
var valueType = composite.valueType;
|
||||
if (valueType == null)
|
||||
throw new InvalidOperationException($"Cannot read value from Composite '{composite}' which does not have a valueType set");
|
||||
|
||||
return Marshal.PtrToStructure(new IntPtr(valuePtr), valueType);
|
||||
}
|
||||
|
||||
// Expecting action to only trigger from part bindings or bindings outside of composites.
|
||||
Debug.Assert(!m_State.bindingStates[bindingIndex].isComposite, "Action should not have triggered directly from a composite binding");
|
||||
|
||||
// Read value through InputControl.
|
||||
var valueSizeInBytes = m_Ptr->valueSizeInBytes;
|
||||
return control.ReadValueFromBufferAsObject(valuePtr, valueSizeInBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the value associated with this event into the contiguous memory buffer defined by <c>[buffer, buffer + bufferSize)</c>.
|
||||
/// </summary>
|
||||
/// <param name="buffer">Pointer to the contiguous memory buffer to write value data to.</param>
|
||||
/// <param name="bufferSize">The size, in bytes, of the contiguous buffer pointed to by <paramref name="buffer"/>.</param>
|
||||
/// <exception cref="NullReferenceException">If <paramref name="buffer"/> is <c>null</c>.</exception>
|
||||
/// <exception cref="ArgumentException">If the given <paramref name="bufferSize"/> is less than the number of bytes required to write the event value to <paramref name="buffer"/>.</exception>
|
||||
/// <seealso cref="ReadValueAsObject"/>
|
||||
/// <seealso cref="ReadValue{TValue}"/>
|
||||
public void ReadValue(void* buffer, int bufferSize)
|
||||
{
|
||||
var valueSizeInBytes = m_Ptr->valueSizeInBytes;
|
||||
|
||||
////REVIEW: do we want more checking than this?
|
||||
if (bufferSize < valueSizeInBytes)
|
||||
throw new ArgumentException(
|
||||
$"Expected buffer of at least {valueSizeInBytes} bytes but got buffer of just {bufferSize} bytes instead",
|
||||
nameof(bufferSize));
|
||||
|
||||
UnsafeUtility.MemCpy(buffer, m_Ptr->valueData, valueSizeInBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the value associated with this event as an object of type <typeparamref name="TValue"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">The event value type to be used.</typeparam>
|
||||
/// <returns>Object of type <typeparamref name="TValue"/>.</returns>
|
||||
/// <exception cref="InvalidOperationException">In case the size of <typeparamref name="TValue"/> does not match the size of the value associated with this event.</exception>
|
||||
public TValue ReadValue<TValue>()
|
||||
where TValue : struct
|
||||
{
|
||||
var valueSizeInBytes = m_Ptr->valueSizeInBytes;
|
||||
|
||||
////REVIEW: do we want more checking than this?
|
||||
if (UnsafeUtility.SizeOf<TValue>() != valueSizeInBytes)
|
||||
throw new InvalidOperationException(
|
||||
$"Cannot read a value of type '{typeof(TValue).Name}' with size {UnsafeUtility.SizeOf<TValue>()} from event on action '{action}' with value size {valueSizeInBytes}");
|
||||
|
||||
var result = new TValue();
|
||||
var resultPtr = UnsafeUtility.AddressOf(ref result);
|
||||
UnsafeUtility.MemCpy(resultPtr, m_Ptr->valueData, valueSizeInBytes);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
if (m_Ptr == null)
|
||||
return "<null>";
|
||||
|
||||
var actionName = action.actionMap != null ? $"{action.actionMap.name}/{action.name}" : action.name;
|
||||
return $"{{ action={actionName} phase={phase} time={time} control={control} value={ReadValueAsObject()} interaction={interaction} duration={duration} }}";
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe struct Enumerator : IEnumerator<ActionEventPtr>
|
||||
{
|
||||
private readonly InputActionTrace m_Trace;
|
||||
private readonly ActionEvent* m_Buffer;
|
||||
private readonly int m_EventCount;
|
||||
private ActionEvent* m_CurrentEvent;
|
||||
private int m_CurrentIndex;
|
||||
|
||||
public Enumerator(InputActionTrace trace)
|
||||
{
|
||||
m_Trace = trace;
|
||||
m_Buffer = (ActionEvent*)trace.m_EventBuffer.bufferPtr.data;
|
||||
m_EventCount = trace.m_EventBuffer.eventCount;
|
||||
m_CurrentEvent = null;
|
||||
m_CurrentIndex = 0;
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (m_CurrentIndex == m_EventCount)
|
||||
return false;
|
||||
|
||||
if (m_CurrentEvent == null)
|
||||
{
|
||||
m_CurrentEvent = m_Buffer;
|
||||
return m_CurrentEvent != null;
|
||||
}
|
||||
|
||||
Debug.Assert(m_CurrentEvent != null);
|
||||
|
||||
++m_CurrentIndex;
|
||||
if (m_CurrentIndex == m_EventCount)
|
||||
return false;
|
||||
|
||||
m_CurrentEvent = (ActionEvent*)InputEvent.GetNextInMemory((InputEvent*)m_CurrentEvent);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
m_CurrentEvent = null;
|
||||
m_CurrentIndex = 0;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public ActionEventPtr Current
|
||||
{
|
||||
get
|
||||
{
|
||||
var state = m_Trace.m_ActionMapStates[m_CurrentEvent->stateIndex];
|
||||
return new ActionEventPtr
|
||||
{
|
||||
m_State = state,
|
||||
m_Ptr = m_CurrentEvent,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
object IEnumerator.Current => Current;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4ff1587ed21f442578f1ad36c4779a95
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,206 @@
|
||||
namespace UnityEngine.InputSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines the behavior with which an <see cref="InputAction"/> triggers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// While all actions essentially function the same way, there are differences in how an action
|
||||
/// will react to changes in values on the controls it is bound to.
|
||||
///
|
||||
/// The most straightforward type of behavior is <see cref="PassThrough"/> which does not expect
|
||||
/// any kind of value change pattern but simply triggers the action on every single value change.
|
||||
/// A pass-through action will not use <see cref="InputAction.started"/> or
|
||||
/// <see cref="InputAction.canceled"/> except on bindings that have an interaction added to them.
|
||||
/// Pass-through actions are most useful for sourcing input from arbitrary many controls and
|
||||
/// simply piping all input through without much processing on the side of the action.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // An action that triggers every time any button on the gamepad is
|
||||
/// // pressed or released.
|
||||
/// var action = new InputAction(
|
||||
/// type: InputActionType.PassThrough,
|
||||
/// binding: "<Gamepad>/<Button>");
|
||||
///
|
||||
/// action.performed +=
|
||||
/// ctx =>
|
||||
/// {
|
||||
/// var button = (ButtonControl)ctx.control;
|
||||
/// if (button.wasPressedThisFrame)
|
||||
/// Debug.Log($"Button {ctx.control} was pressed");
|
||||
/// else if (button.wasReleasedThisFrame)
|
||||
/// Debug.Log($"Button {ctx.control} was released");
|
||||
/// // NOTE: We may get calls here in which neither the if nor the else
|
||||
/// // clause are true here. A button like the gamepad left and right
|
||||
/// // triggers, for example, do not just have a binary on/off state
|
||||
/// // but rather a [0..1] value range.
|
||||
/// };
|
||||
///
|
||||
/// action.Enable();
|
||||
/// </code>
|
||||
/// </example>
|
||||
///
|
||||
/// Note that pass-through actions do not perform any kind of disambiguation of input
|
||||
/// which makes them great for just forwarding input from any connected controls but
|
||||
/// makes them a poor choice when only one input should be generated from however
|
||||
/// many connected controls there are. For more details, see <a
|
||||
/// href="../manual/ActionBindings.html#conflicting-inputs">here</a>.
|
||||
///
|
||||
/// The other two behavior types are <see cref="Button"/> and <see cref="Value"/>.
|
||||
///
|
||||
/// A <see cref="Value"/> action starts (<see cref="InputAction.started"/>) as soon as its
|
||||
/// input moves away from its default value. After that it immediately performs (<see cref="InputAction.performed"/>)
|
||||
/// and every time the input changes value it performs again except if the input moves back
|
||||
/// to the default value -- in which case the action cancels (<see cref="InputAction.canceled"/>).
|
||||
///
|
||||
/// Also, unlike both <see cref="Button"/> and <see cref="PassThrough"/> actions, <see cref="Value"/>
|
||||
/// actions perform what's called "initial state check" on the first input update after the action
|
||||
/// was enabled. What this does is check controls bound to the action and if they are already actuated
|
||||
/// (that is, at non-default value), the action will immediately be started and performed. What
|
||||
/// this means in practice is that when a value action is bound to, say, the left stick on a
|
||||
/// gamepad and the stick is already moved out of its resting position, then the action will
|
||||
/// immediately trigger instead of first requiring the stick to be moved slightly.
|
||||
///
|
||||
/// <see cref="Button"/> and <see cref="PassThrough"/> actions, on the other hand, perform
|
||||
/// no such initial state check. For buttons, for example, this means that if a button is
|
||||
/// already pressed when an action is enabled, it first has to be released and then
|
||||
/// pressed again for the action to be triggered.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // An action that starts when the left stick on the gamepad is actuated
|
||||
/// // and stops when the stick is released.
|
||||
/// var action = new InputAction(
|
||||
/// type: InputActionType.Value,
|
||||
/// binding: "<Gamepad>/leftStick");
|
||||
///
|
||||
/// action.started +=
|
||||
/// ctx =>
|
||||
/// {
|
||||
/// Debug.Log("--- Stick Starts ---");
|
||||
/// };
|
||||
/// action.performed +=
|
||||
/// ctx =>
|
||||
/// {
|
||||
/// Debug.Log("Stick Value: " + ctx.ReadValue<Vector2D>();
|
||||
/// };
|
||||
/// action.canceled +=
|
||||
/// ctx =>
|
||||
/// {
|
||||
/// Debug.Log("# Stick Released");
|
||||
/// };
|
||||
///
|
||||
/// action.Enable();
|
||||
/// </code>
|
||||
/// </example>
|
||||
///
|
||||
/// A <see cref="Button"/> action essentially operates like a <see cref="Value"/> action except
|
||||
/// that it does not perform an initial state check.
|
||||
///
|
||||
/// One final noteworthy difference of both <see cref="Button"/> and <see cref="Value"/> compared
|
||||
/// to <see cref="PassThrough"/> is that both of them perform what is referred to as "disambiguation"
|
||||
/// when multiple actions are bound to the control. <see cref="PassThrough"/> does not care how
|
||||
/// many controls are bound to the action -- it simply passes every input through as is, no matter
|
||||
/// where it comes from.
|
||||
///
|
||||
/// <see cref="Button"/> and <see cref="Value"/>, on the other hand, will treat input differently
|
||||
/// if it is coming from several sources at the same time. Note that this can only happen when there
|
||||
/// are multiple controls bound to a single actions -- either by a single binding resolving to
|
||||
/// more than one control (e.g. <c>"*/{PrimaryAction}"</c>) or by multiple bindings all targeting
|
||||
/// the same action and being active at the same time. If only a single control is bound to an
|
||||
/// action, then the disambiguation code is automatically bypassed.
|
||||
///
|
||||
/// Disambiguation works the following way: when an action has not yet been started, it will react
|
||||
/// to the first input that has a non-default value. Once it receives such an input, it will start
|
||||
/// tracking the source of that input. While the action is in-progress, if it receives input from
|
||||
/// a source other than the control it is currently tracking, it will check whether the input has
|
||||
/// a greater magnitude (see <see cref="InputControl.EvaluateMagnitude()"/>) than the control the
|
||||
/// action is already tracking. If so, the action will switch from its current control to the control
|
||||
/// with the stronger input.
|
||||
///
|
||||
/// Note that this process does also works in reverse. When the control currently driving the action
|
||||
/// lowers its value below that of another control that is also actuated and bound to the action,
|
||||
/// the action will switch to that control.
|
||||
///
|
||||
/// Put simply, a <see cref="Button"/> or <see cref="Value"/> action bound to multiple controls will
|
||||
/// always track the control with the strongest input.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputAction.type"/>
|
||||
public enum InputActionType
|
||||
{
|
||||
/// <summary>
|
||||
/// An action that reads a single value from its connected sources. If multiple bindings
|
||||
/// actuate at the same time, performs disambiguation (see <see
|
||||
/// href="../manual/ActionBindings.html#conflicting-inputs"/>) to detect the highest value contributor
|
||||
/// at any one time.
|
||||
///
|
||||
/// A value action starts (<see cref="InputActionPhase.Started"/>) and then performs (<see cref="InputActionPhase.Performed"/>)
|
||||
/// as soon as a bound control changes to a non-default value. For example, if an action is bound to <see cref="Gamepad.leftStick"/>
|
||||
/// and the stick moves from (0,0) to (0.5,0.5), the action starts and performs.
|
||||
///
|
||||
/// After being started, the action will perform on every value change that is not the default value. In the example here, if
|
||||
/// the stick goes to (0.75,0.75) and then to (1,1), the action will perform twice.
|
||||
///
|
||||
/// Finally, if the control value changes back to the default value, the action is canceled (<see cref="InputActionPhase.Canceled"/>).
|
||||
/// Meaning that if the stick moves back to (0,0), <see cref="InputAction.canceled"/> will be triggered.
|
||||
/// </summary>
|
||||
Value,
|
||||
|
||||
/// <summary>
|
||||
/// An action that acts as a trigger.
|
||||
///
|
||||
/// A button action has a defined trigger point that corresponds to <see cref="InputActionPhase.Performed"/>.
|
||||
/// After being performed, the action goes back to waiting state to await the next triggering.
|
||||
///
|
||||
/// Note that a button action may still use <see cref="InputActionPhase.Started"/> and does not necessarily
|
||||
/// trigger immediately on input. For example, if <see cref="Interactions.HoldInteraction"/> is used, the
|
||||
/// action will start as soon as a bound button crosses its press threshold but will not trigger until the
|
||||
/// button is held for the set hold duration (<see cref="Interactions.HoldInteraction.duration"/>).
|
||||
///
|
||||
/// Irrespective of which type an action is set to, it is possible to find out whether it was or is considered
|
||||
/// pressed and/or released using <see cref="InputAction.IsPressed"/>, <see cref="InputAction.WasPressedThisFrame"/>,
|
||||
/// and <see cref="InputAction.WasReleasedThisFrame"/>.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// action.IsPressed();
|
||||
/// action.WasPressedThisFrame();
|
||||
/// action.WasReleasedThisFrame();
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </summary>
|
||||
Button,
|
||||
|
||||
/// <summary>
|
||||
/// An action that has no specific type of behavior and instead acts as a simple pass-through for
|
||||
/// any value change on any bound control. In effect, this turns an action from a single value producer into a mere
|
||||
/// input "sink".
|
||||
///
|
||||
/// This is in some ways similar to <see cref="Value"/>. However, there are two key differences.
|
||||
///
|
||||
/// For one, the action will not perform any disambiguation when bound to multiple controls concurrently.
|
||||
/// This means that if, for example, the action is bound to both the left and the right stick on a <see cref="Gamepad"/>,
|
||||
/// and the left stick goes to (0.5,0.5) and the right stick then goes to (0.25,0.25), the action will perform
|
||||
/// twice yielding a value of (0.5,0.5) first and a value of (0.25, 0.25) next. This is different from <see cref="Value"/>
|
||||
/// where upon actuation to (0.5,0.5), the left stick would get to drive the action and the actuation of the right
|
||||
/// stick would be ignored as it does not exceed the magnitude of the actuation on the left stick.
|
||||
///
|
||||
/// The second key difference is that only <see cref="InputActionPhase.Performed"/> is used and will get triggered
|
||||
/// on every value change regardless of what the value is. This is different from <see cref="Value"/> where the
|
||||
/// action will trigger <see cref="InputActionPhase.Started"/> when moving away from its default value and will
|
||||
/// trigger <see cref="InputActionPhase.Canceled"/> when going back to the default value.
|
||||
///
|
||||
/// Note that a pass-through action my still get cancelled and thus see <see cref="InputAction.canceled"/> getting called.
|
||||
/// This happens when a factor other than input on a device causes an action in progress to be cancelled. An example
|
||||
/// of this is when an action is disabled (see <see cref="InputAction.Disable"/>) or when focus is lost (see <see cref="InputSettings.backgroundBehavior"/>)
|
||||
/// and a device connection to an action is reset (see <see cref="InputSystem.ResetDevice"/>).
|
||||
///
|
||||
/// Also note that for a pass-through action, calling <see cref="InputAction.ReadValue{TValue}"/> is often not
|
||||
/// very useful as it will only return the value of the very last control that fed into the action. For pass-through
|
||||
/// actions, it is usually best to listen to <see cref="InputAction.performed"/> in order to be notified about every
|
||||
/// single value change. Where this is not necessary, it is generally better to employ a <see cref="Value"/> action
|
||||
/// instead.
|
||||
/// </summary>
|
||||
PassThrough,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b21f8c6bf1fd94d1fb47907209b23367
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,914 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine.InputSystem.Layouts;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
|
||||
////REVIEW: do we really need overridable processors and interactions?
|
||||
|
||||
// Downsides to the current approach:
|
||||
// - Being able to address entire batches of controls through a single control is awesome. Especially
|
||||
// when combining it type-kind of queries (e.g. "<MyDevice>/<Button>"). However, it complicates things
|
||||
// in quite a few areas. There's quite a few bits in InputActionState that could be simplified if a
|
||||
// binding simply maps to a control.
|
||||
|
||||
namespace UnityEngine.InputSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// A mapping of controls to an action.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Each binding represents a value received from controls (see <see cref="InputControl"/>).
|
||||
/// There are two main types of bindings: "normal" bindings and "composite" bindings.
|
||||
///
|
||||
/// Normal bindings directly bind to control(s) by means of <see cref="path"/> which is a "control path"
|
||||
/// (see <see cref="InputControlPath"/> for details about how to form paths). At runtime, the
|
||||
/// path of such a binding may match none, one, or multiple controls. Each control matched by the
|
||||
/// path will feed input into the binding.
|
||||
///
|
||||
/// Composite bindings do not bind to controls themselves. Instead, they receive their input
|
||||
/// from their "part" bindings and then return a value representing a "composition" of those
|
||||
/// inputs. What composition specifically is performed depends on the type of the composite.
|
||||
/// <see cref="Composites.AxisComposite"/>, for example, will return a floating-point axis value
|
||||
/// computed from the state of two buttons.
|
||||
///
|
||||
/// The action that is triggered by a binding is determined by its <see cref="action"/> property.
|
||||
/// The resolution to an <see cref="InputAction"/> depends on where the binding is used. For example,
|
||||
/// bindings that are part of <see cref="InputActionMap.bindings"/> will resolve action names to
|
||||
/// actions in the same <see cref="InputActionMap"/>.
|
||||
///
|
||||
/// A binding can also be used as a form of search mask or filter. In this use, <see cref="path"/>,
|
||||
/// <see cref="action"/>, and <see cref="groups"/> become search criteria that are matched
|
||||
/// against other bindings. See <see cref="Matches(InputBinding)"/> for details. This use
|
||||
/// is employed in places such as <see cref="InputActionRebindingExtensions"/> as well as in
|
||||
/// binding masks on actions (<see cref="InputAction.bindingMask"/>), action maps (<see
|
||||
/// cref="InputActionMap.bindingMask"/>), and assets (<see cref="InputActionAsset.bindingMask"/>).
|
||||
/// </remarks>
|
||||
[Serializable]
|
||||
public struct InputBinding : IEquatable<InputBinding>
|
||||
{
|
||||
/// <summary>
|
||||
/// Character that is used to separate elements in places such as <see cref="groups"/>,
|
||||
/// <see cref="interactions"/>, and <see cref="processors"/>.
|
||||
/// </summary>
|
||||
/// Some strings on bindings represent lists of elements. An example is <see cref="groups"/>
|
||||
/// which may associate a binding with several binding groups, each one delimited by the
|
||||
/// separator.
|
||||
///
|
||||
/// <remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // A binding that belongs to the "Keyboard&Mouse" and "Gamepad" group.
|
||||
/// new InputBinding
|
||||
/// {
|
||||
/// path = "*/{PrimaryAction},
|
||||
/// groups = "Keyboard&Mouse;Gamepad"
|
||||
/// };
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
public const char Separator = ';';
|
||||
|
||||
internal const string kSeparatorString = ";";
|
||||
|
||||
/// <summary>
|
||||
/// Optional name for the binding.
|
||||
/// </summary>
|
||||
/// <value>Name of the binding.</value>
|
||||
/// <remarks>
|
||||
/// For bindings that are part of composites (see <see cref="isPartOfComposite"/>), this is
|
||||
/// the name of the field on the binding composite object that should be initialized with
|
||||
/// the control target of the binding.
|
||||
/// </remarks>
|
||||
public string name
|
||||
{
|
||||
get => m_Name;
|
||||
set => m_Name = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unique ID of the binding.
|
||||
/// </summary>
|
||||
/// <value>Unique ID of the binding.</value>
|
||||
/// <remarks>
|
||||
/// This can be used, for example, when storing binding overrides in local user configurations.
|
||||
/// Using the binding ID, an override can remain associated with one specific binding.
|
||||
/// </remarks>
|
||||
public Guid id
|
||||
{
|
||||
get
|
||||
{
|
||||
////REVIEW: this is inconsistent with InputActionMap and InputAction which generate IDs, if necessary
|
||||
if (string.IsNullOrEmpty(m_Id))
|
||||
return default;
|
||||
return new Guid(m_Id);
|
||||
}
|
||||
set => m_Id = value.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Control path being bound to.
|
||||
/// </summary>
|
||||
/// <value>Path of control(s) to source input from.</value>
|
||||
/// <remarks>
|
||||
/// Bindings reference <see cref="InputControl"/>s using a regular expression-like
|
||||
/// language. See <see cref="InputControlPath"/> for details.
|
||||
///
|
||||
/// If the binding is a composite (<see cref="isComposite"/>), the path is the composite
|
||||
/// string instead. For example, for a <see cref="Composites.Vector2Composite"/>, the
|
||||
/// path could be something like <c>"Vector2(normalize=false)"</c>.
|
||||
///
|
||||
/// The path of a binding may be non-destructively override at runtime using <see cref="overridePath"/>
|
||||
/// which unlike this property is not serialized. <see cref="effectivePath"/> represents the
|
||||
/// final, effective path.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // A binding that references the left mouse button.
|
||||
/// new InputBinding { path = "<Mouse>/leftButton" }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <seealso cref="overridePath"/>
|
||||
/// <seealso cref="InputControlPath"/>
|
||||
/// <seealso cref="InputControlPath.Parse"/>
|
||||
/// <seealso cref="InputControl.path"/>
|
||||
/// <seealso cref="InputSystem.FindControl"/>
|
||||
public string path
|
||||
{
|
||||
get => m_Path;
|
||||
set => m_Path = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the binding is overridden, this is the overriding path.
|
||||
/// Otherwise it is <c>null</c>.
|
||||
/// </summary>
|
||||
/// <value>Path to override the <see cref="path"/> property with.</value>
|
||||
/// <remarks>
|
||||
/// Unlike the <see cref="path"/> property, the value of the override path is not serialized.
|
||||
/// If set, it will take precedence and determine the result of <see cref="effectivePath"/>.
|
||||
///
|
||||
/// This property can be set to an empty string to disable the binding. During resolution,
|
||||
/// bindings with an empty <see cref="effectivePath"/> will get skipped.
|
||||
///
|
||||
/// To set the override on an existing binding, use the methods supplied by <see cref="InputActionRebindingExtensions"/>
|
||||
/// such as <see cref="InputActionRebindingExtensions.ApplyBindingOverride(InputAction,string,string,string)"/>.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Override the binding to <Gamepad>/buttonSouth on
|
||||
/// // myAction with a binding to <Gamepad>/buttonNorth.
|
||||
/// myAction.ApplyBindingOverride(
|
||||
/// new InputBinding
|
||||
/// {
|
||||
/// path = "<Gamepad>/buttonSouth",
|
||||
/// overridePath = "<Gamepad>/buttonNorth"
|
||||
/// });
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="path"/>
|
||||
/// <seealso cref="overrideInteractions"/>
|
||||
/// <seealso cref="overrideProcessors"/>
|
||||
/// <seealso cref="hasOverrides"/>
|
||||
/// <seealso cref="InputActionRebindingExtensions.SaveBindingOverridesAsJson(IInputActionCollection2)"/>
|
||||
/// <seealso cref="InputActionRebindingExtensions.LoadBindingOverridesFromJson(IInputActionCollection2,string,bool)"/>
|
||||
/// <seealso cref="InputActionRebindingExtensions.ApplyBindingOverride(InputAction,int,InputBinding)"/>
|
||||
public string overridePath
|
||||
{
|
||||
get => m_OverridePath;
|
||||
set => m_OverridePath = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optional list of interactions and their parameters.
|
||||
/// </summary>
|
||||
/// <value>Interactions to put on the binding.</value>
|
||||
/// <remarks>
|
||||
/// Each element in the list is a name of an interaction (as registered with
|
||||
/// <see cref="InputSystem.RegisterInteraction{T}"/>) followed by an optional
|
||||
/// list of parameters.
|
||||
///
|
||||
/// For example, <c>"slowTap(duration=1.2,pressPoint=0.123)"</c> is one element
|
||||
/// that puts a <see cref="Interactions.SlowTapInteraction"/> on the binding and
|
||||
/// sets <see cref="Interactions.SlowTapInteraction.duration"/> to 1.2 and
|
||||
/// <see cref="Interactions.SlowTapInteraction.pressPoint"/> to 0.123.
|
||||
///
|
||||
/// Multiple interactions can be put on a binding by separating them with a comma.
|
||||
/// For example, <c>"tap,slowTap(duration=1.2)"</c> puts both a
|
||||
/// <see cref="Interactions.TapInteraction"/> and <see cref="Interactions.SlowTapInteraction"/>
|
||||
/// on the binding. See <see cref="IInputInteraction"/> for why the order matters.
|
||||
/// </remarks>
|
||||
/// <seealso cref="IInputInteraction"/>
|
||||
/// <seealso cref="overrideInteractions"/>
|
||||
/// <seealso cref="hasOverrides"/>
|
||||
/// <seealso cref="InputActionRebindingExtensions.SaveBindingOverridesAsJson(IInputActionCollection2)"/>
|
||||
/// <seealso cref="InputActionRebindingExtensions.LoadBindingOverridesFromJson(IInputActionCollection2,string,bool)"/>
|
||||
/// <seealso cref="InputActionRebindingExtensions.ApplyBindingOverride(InputAction,int,InputBinding)"/>
|
||||
public string interactions
|
||||
{
|
||||
get => m_Interactions;
|
||||
set => m_Interactions = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interaction settings to override <see cref="interactions"/> with.
|
||||
/// </summary>
|
||||
/// <value>Override string for <see cref="interactions"/> or <c>null</c>.</value>
|
||||
/// <remarks>
|
||||
/// If this is not <c>null</c>, it replaces the value of <see cref="interactions"/>.
|
||||
/// </remarks>
|
||||
/// <seealso cref="effectiveInteractions"/>
|
||||
/// <seealso cref="interactions"/>
|
||||
/// <seealso cref="overridePath"/>
|
||||
/// <seealso cref="overrideProcessors"/>
|
||||
/// <seealso cref="hasOverrides"/>
|
||||
/// <seealso cref="InputActionRebindingExtensions.SaveBindingOverridesAsJson(IInputActionCollection2)"/>
|
||||
/// <seealso cref="InputActionRebindingExtensions.LoadBindingOverridesFromJson(IInputActionCollection2,string,bool)"/>
|
||||
/// <seealso cref="InputActionRebindingExtensions.ApplyBindingOverride(InputAction,int,InputBinding)"/>
|
||||
public string overrideInteractions
|
||||
{
|
||||
get => m_OverrideInteractions;
|
||||
set => m_OverrideInteractions = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optional list of processors to apply to control values.
|
||||
/// </summary>
|
||||
/// <value>Value processors to apply to the binding.</value>
|
||||
/// <remarks>
|
||||
/// This string has the same format as <see cref="InputControlAttribute.processors"/>.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputProcessor{TValue}"/>
|
||||
/// <seealso cref="overrideProcessors"/>
|
||||
public string processors
|
||||
{
|
||||
get => m_Processors;
|
||||
set => m_Processors = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processor settings to override <see cref="processors"/> with.
|
||||
/// </summary>
|
||||
/// <value>Override string for <see cref="processors"/> or <c>null</c>.</value>
|
||||
/// <remarks>
|
||||
/// If this is not <c>null</c>, it replaces the value of <see cref="processors"/>.
|
||||
/// </remarks>
|
||||
/// <seealso cref="effectiveProcessors"/>
|
||||
/// <seealso cref="processors"/>
|
||||
/// <seealso cref="overridePath"/>
|
||||
/// <seealso cref="overrideInteractions"/>
|
||||
/// <seealso cref="hasOverrides"/>
|
||||
public string overrideProcessors
|
||||
{
|
||||
get => m_OverrideProcessors;
|
||||
set => m_OverrideProcessors = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optional list of binding groups that the binding belongs to.
|
||||
/// </summary>
|
||||
/// <value>List of binding groups or <c>null</c>.</value>
|
||||
/// <remarks>
|
||||
/// This is used, for example, to divide bindings into <see cref="InputControlScheme"/>s.
|
||||
/// Each control scheme is associated with a unique binding group through <see
|
||||
/// cref="InputControlScheme.bindingGroup"/>.
|
||||
///
|
||||
/// A binding may be associated with multiple groups by listing each group name
|
||||
/// separate by a semicolon (<see cref="Separator"/>).
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// new InputBinding
|
||||
/// {
|
||||
/// path = "*/{PrimaryAction},
|
||||
/// // Associate the binding both with the "KeyboardMouse" and
|
||||
/// // the "Gamepad" group.
|
||||
/// groups = "KeyboardMouse;Gamepad",
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
///
|
||||
/// Note that the system places no restriction on what binding groups are used
|
||||
/// for in practice. Their use by <see cref="InputControlScheme"/> is only one
|
||||
/// possible one, but which groups to apply and how to use them is ultimately
|
||||
/// up to you.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputControlScheme.bindingGroup"/>
|
||||
public string groups
|
||||
{
|
||||
get => m_Groups;
|
||||
set => m_Groups = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Name or ID of the action triggered by the binding.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is null if the binding does not trigger an action.
|
||||
///
|
||||
/// For InputBindings that are used as masks, this can be a "mapName/actionName" combination
|
||||
/// or "mapName/*" to match all actions in the given map.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputAction.name"/>
|
||||
/// <seealso cref="InputAction.id"/>
|
||||
public string action
|
||||
{
|
||||
get => m_Action;
|
||||
set => m_Action = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the binding is a composite.
|
||||
/// </summary>
|
||||
/// <value>True if the binding is a composite.</value>
|
||||
/// <remarks>
|
||||
/// Composite bindings to not bind to controls to themselves but rather source their
|
||||
/// input from one or more "part binding" (see <see cref="isPartOfComposite"/>).
|
||||
///
|
||||
/// See <see cref="InputBindingComposite{TValue}"/> for more details.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputBindingComposite{TValue}"/>
|
||||
public bool isComposite
|
||||
{
|
||||
get => (m_Flags & Flags.Composite) == Flags.Composite;
|
||||
set
|
||||
{
|
||||
if (value)
|
||||
m_Flags |= Flags.Composite;
|
||||
else
|
||||
m_Flags &= ~Flags.Composite;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the binding is a "part binding" of a composite.
|
||||
/// </summary>
|
||||
/// <value>True if the binding is part of a composite.</value>
|
||||
/// <remarks>
|
||||
/// The bindings that make up a composite are laid out sequentially in <see cref="InputActionMap.bindings"/>.
|
||||
/// First comes the composite itself which is flagged with <see cref="isComposite"/>. It mentions
|
||||
/// the composite and its parameters in its <see cref="path"/> property. After the composite itself come
|
||||
/// the part bindings. All subsequent bindings marked as <c>isPartOfComposite</c> will be associated
|
||||
/// with the composite.
|
||||
/// </remarks>
|
||||
/// <seealso cref="isComposite"/>
|
||||
/// <seealso cref="InputBindingComposite{TValue}"/>
|
||||
public bool isPartOfComposite
|
||||
{
|
||||
get => (m_Flags & Flags.PartOfComposite) == Flags.PartOfComposite;
|
||||
set
|
||||
{
|
||||
if (value)
|
||||
m_Flags |= Flags.PartOfComposite;
|
||||
else
|
||||
m_Flags &= ~Flags.PartOfComposite;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if any of the override properties, that is, <see cref="overridePath"/>, <see cref="overrideProcessors"/>,
|
||||
/// and/or <see cref="overrideInteractions"/>, are set (not <c>null</c>).
|
||||
/// </summary>
|
||||
public bool hasOverrides => overridePath != null || overrideProcessors != null || overrideInteractions != null;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new binding.
|
||||
/// </summary>
|
||||
/// <param name="path">Path for the binding.</param>
|
||||
/// <param name="action">Action to trigger from the binding.</param>
|
||||
/// <param name="groups">Semicolon-separated list of binding <see cref="InputBinding.groups"/> the binding is associated with.</param>
|
||||
/// <param name="processors">Comma-separated list of <see cref="InputBinding.processors"/> to apply to the binding.</param>
|
||||
/// <param name="interactions">Comma-separated list of <see cref="InputBinding.interactions"/> to apply to the
|
||||
/// binding.</param>
|
||||
/// <param name="name">Optional name for the binding.</param>
|
||||
public InputBinding(string path, string action = null, string groups = null, string processors = null,
|
||||
string interactions = null, string name = null)
|
||||
{
|
||||
m_Path = path;
|
||||
m_Action = action;
|
||||
m_Groups = groups;
|
||||
m_Processors = processors;
|
||||
m_Interactions = interactions;
|
||||
m_Name = name;
|
||||
m_Id = default;
|
||||
m_Flags = default;
|
||||
m_OverridePath = default;
|
||||
m_OverrideInteractions = default;
|
||||
m_OverrideProcessors = default;
|
||||
}
|
||||
|
||||
public string GetNameOfComposite()
|
||||
{
|
||||
if (!isComposite)
|
||||
return null;
|
||||
return NameAndParameters.Parse(effectivePath).name;
|
||||
}
|
||||
|
||||
internal void GenerateId()
|
||||
{
|
||||
m_Id = Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
internal void RemoveOverrides()
|
||||
{
|
||||
m_OverridePath = null;
|
||||
m_OverrideInteractions = null;
|
||||
m_OverrideProcessors = null;
|
||||
}
|
||||
|
||||
public static InputBinding MaskByGroup(string group)
|
||||
{
|
||||
return new InputBinding {groups = group};
|
||||
}
|
||||
|
||||
public static InputBinding MaskByGroups(params string[] groups)
|
||||
{
|
||||
return new InputBinding {groups = string.Join(kSeparatorString, groups.Where(x => !string.IsNullOrEmpty(x)))};
|
||||
}
|
||||
|
||||
[SerializeField] private string m_Name;
|
||||
[SerializeField] internal string m_Id;
|
||||
[Tooltip("Path of the control to bind to. Matched at runtime to controls from InputDevices present at the time.\n\nCan either be "
|
||||
+ "graphically from the control picker dropdown UI or edited manually in text mode by clicking the 'T' button. Internally, both "
|
||||
+ "methods result in control path strings that look like, for example, \"<Gamepad>/buttonSouth\".")]
|
||||
[SerializeField] private string m_Path;
|
||||
[SerializeField] private string m_Interactions;
|
||||
[SerializeField] private string m_Processors;
|
||||
[SerializeField] internal string m_Groups;
|
||||
[SerializeField] private string m_Action;
|
||||
[SerializeField] internal Flags m_Flags;
|
||||
|
||||
[NonSerialized] private string m_OverridePath;
|
||||
[NonSerialized] private string m_OverrideInteractions;
|
||||
[NonSerialized] private string m_OverrideProcessors;
|
||||
|
||||
/// <summary>
|
||||
/// This is the bindings path which is effectively being used.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is either <see cref="overridePath"/> if that is set, or <see cref="path"/> otherwise.
|
||||
/// </remarks>
|
||||
public string effectivePath => overridePath ?? path;
|
||||
|
||||
/// <summary>
|
||||
/// This is the interaction config which is effectively being used.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is either <see cref="overrideInteractions"/> if that is set, or <see cref="interactions"/> otherwise.
|
||||
/// </remarks>
|
||||
public string effectiveInteractions => overrideInteractions ?? interactions;
|
||||
|
||||
/// <summary>
|
||||
/// This is the processor config which is effectively being used.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is either <see cref="overrideProcessors"/> if that is set, or <see cref="processors"/> otherwise.
|
||||
/// </remarks>
|
||||
public string effectiveProcessors => overrideProcessors ?? processors;
|
||||
|
||||
internal bool isEmpty =>
|
||||
string.IsNullOrEmpty(effectivePath) && string.IsNullOrEmpty(action) &&
|
||||
string.IsNullOrEmpty(groups);
|
||||
|
||||
/// <summary>
|
||||
/// Check whether the binding is equivalent to the given binding.
|
||||
/// </summary>
|
||||
/// <param name="other">Another binding.</param>
|
||||
/// <returns>True if the two bindings are equivalent.</returns>
|
||||
/// <remarks>
|
||||
/// Bindings are equivalent if their <see cref="effectivePath"/>, <see cref="effectiveInteractions"/>,
|
||||
/// and <see cref="effectiveProcessors"/>, plus their <see cref="action"/> and <see cref="groups"/>
|
||||
/// properties are the same. Note that the string comparisons ignore both case and culture.
|
||||
/// </remarks>
|
||||
public bool Equals(InputBinding other)
|
||||
{
|
||||
return string.Equals(effectivePath, other.effectivePath, StringComparison.InvariantCultureIgnoreCase) &&
|
||||
string.Equals(effectiveInteractions, other.effectiveInteractions, StringComparison.InvariantCultureIgnoreCase) &&
|
||||
string.Equals(effectiveProcessors, other.effectiveProcessors, StringComparison.InvariantCultureIgnoreCase) &&
|
||||
string.Equals(groups, other.groups, StringComparison.InvariantCultureIgnoreCase) &&
|
||||
string.Equals(action, other.action, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare the binding to the given object.
|
||||
/// </summary>
|
||||
/// <param name="obj">An object. May be <c>null</c>.</param>
|
||||
/// <returns>True if the given object is an <c>InputBinding</c> that equals this one.</returns>
|
||||
/// <seealso cref="Equals(InputBinding)"/>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj))
|
||||
return false;
|
||||
|
||||
return obj is InputBinding binding && Equals(binding);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare the two bindings for equality.
|
||||
/// </summary>
|
||||
/// <param name="left">The first binding.</param>
|
||||
/// <param name="right">The second binding.</param>
|
||||
/// <returns>True if the two bindings are equal.</returns>
|
||||
/// <seealso cref="Equals(InputBinding)"/>
|
||||
public static bool operator==(InputBinding left, InputBinding right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare the two bindings for inequality.
|
||||
/// </summary>
|
||||
/// <param name="left">The first binding.</param>
|
||||
/// <param name="right">The second binding.</param>
|
||||
/// <returns>True if the two bindings are not equal.</returns>
|
||||
/// <seealso cref="Equals(InputBinding)"/>
|
||||
public static bool operator!=(InputBinding left, InputBinding right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute a hash code for the binding.
|
||||
/// </summary>
|
||||
/// <returns>A hash code.</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hashCode = (effectivePath != null ? effectivePath.GetHashCode() : 0);
|
||||
hashCode = (hashCode * 397) ^ (effectiveInteractions != null ? effectiveInteractions.GetHashCode() : 0);
|
||||
hashCode = (hashCode * 397) ^ (effectiveProcessors != null ? effectiveProcessors.GetHashCode() : 0);
|
||||
hashCode = (hashCode * 397) ^ (groups != null ? groups.GetHashCode() : 0);
|
||||
hashCode = (hashCode * 397) ^ (action != null ? action.GetHashCode() : 0);
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a string representation of the binding useful for debugging.
|
||||
/// </summary>
|
||||
/// <returns>A string representation of the binding.</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var binding = new InputBinding
|
||||
/// {
|
||||
/// action = "fire",
|
||||
/// path = "<Gamepad>/buttonSouth",
|
||||
/// groups = "Gamepad"
|
||||
/// };
|
||||
///
|
||||
/// // Returns "fire: <Gamepad>/buttonSouth [Gamepad]".
|
||||
/// binding.ToString();
|
||||
/// </code>
|
||||
/// </example>
|
||||
public override string ToString()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
// Add action.
|
||||
if (!string.IsNullOrEmpty(action))
|
||||
{
|
||||
builder.Append(action);
|
||||
builder.Append(':');
|
||||
}
|
||||
|
||||
// Add path.
|
||||
var path = effectivePath;
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
builder.Append(path);
|
||||
|
||||
// Add groups.
|
||||
if (!string.IsNullOrEmpty(groups))
|
||||
{
|
||||
builder.Append('[');
|
||||
builder.Append(groups);
|
||||
builder.Append(']');
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A set of flags to turn individual default behaviors of <see cref="InputBinding.ToDisplayString(DisplayStringOptions,InputControl)"/> off.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum DisplayStringOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Do not use short names of controls as set up by <see cref="InputControlAttribute.shortDisplayName"/>
|
||||
/// and the <c>"shortDisplayName"</c> property in JSON. This will, for example, not use LMB instead of "left Button"
|
||||
/// on <see cref="Mouse.leftButton"/>.
|
||||
/// </summary>
|
||||
DontUseShortDisplayNames = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// By default device names are omitted from display strings. With this option, they are included instead.
|
||||
/// For example, <c>"A"</c> will be <c>"A [Gamepad]"</c> instead.
|
||||
/// </summary>
|
||||
DontOmitDevice = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// By default, interactions on bindings are included in the resulting display string. For example, a binding to
|
||||
/// the gamepad's A button that has a "Hold" interaction on it, would come out as "Hold A". This can be suppressed
|
||||
/// with this option in which case the same setup would come out as just "A".
|
||||
/// </summary>
|
||||
DontIncludeInteractions = 1 << 2,
|
||||
|
||||
/// <summary>
|
||||
/// By default, <see cref="effectivePath"/> is used for generating a display name. Using this option, the display
|
||||
/// string can be forced to <see cref="path"/> instead.
|
||||
/// </summary>
|
||||
IgnoreBindingOverrides = 1 << 3,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turn the binding into a string suitable for display in a UI.
|
||||
/// </summary>
|
||||
/// <param name="options">Optional set of formatting options.</param>
|
||||
/// <param name="control">Optional control to which the binding has been resolved. If this is supplied,
|
||||
/// the resulting string can reflect things such as the current keyboard layout or hardware/platform-specific
|
||||
/// naming of controls (e.g. Xbox vs PS4 controllers as opposed to naming things generically based on the
|
||||
/// <see cref="Gamepad"/> layout).</param>
|
||||
/// <returns>A string representation of the binding suitable for display in a UI.</returns>
|
||||
/// <remarks>
|
||||
/// This method works only for bindings that are not composites. If the method is called on a binding
|
||||
/// that is a composite (<see cref="isComposite"/> is true), an empty string will be returned. To automatically
|
||||
/// handle composites, use <see cref="InputActionRebindingExtensions.GetBindingDisplayString(InputAction,DisplayStringOptions,string)"/>
|
||||
/// instead.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var gamepadBinding = new InputBinding("<Gamepad>/buttonSouth");
|
||||
/// var mouseBinding = new InputBinding("<Mouse>/leftButton");
|
||||
/// var keyboardBinding = new InputBinding("<Keyboard>/a");
|
||||
///
|
||||
/// // Prints "A" except on PS4 where it prints "Cross".
|
||||
/// Debug.Log(gamepadBinding.ToDisplayString());
|
||||
///
|
||||
/// // Prints "LMB".
|
||||
/// Debug.Log(mouseBinding.ToDisplayString());
|
||||
///
|
||||
/// // Print "Left Button".
|
||||
/// Debug.Log(mouseBinding.ToDisplayString(DisplayStringOptions.DontUseShortDisplayNames));
|
||||
///
|
||||
/// // Prints the character associated with the "A" key on the current keyboard layout.
|
||||
/// Debug.Log(keyboardBinding, control: Keyboard.current);
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputControlPath.ToHumanReadableString(string,InputControlPath.HumanReadableStringOptions,InputControl)"/>
|
||||
/// <seealso cref="InputActionRebindingExtensions.GetBindingDisplayString(InputAction,int,InputBinding.DisplayStringOptions)"/>
|
||||
public string ToDisplayString(DisplayStringOptions options = default, InputControl control = default)
|
||||
{
|
||||
return ToDisplayString(out var _, out var _, options, control);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turn the binding into a string suitable for display in a UI.
|
||||
/// </summary>
|
||||
/// <param name="options">Optional set of formatting options.</param>
|
||||
/// <param name="control">Optional control to which the binding has been resolved. If this is supplied,
|
||||
/// the resulting string can reflect things such as the current keyboard layout or hardware/platform-specific
|
||||
/// naming of controls (e.g. Xbox vs PS4 controllers as opposed to naming things generically based on the
|
||||
/// <see cref="Gamepad"/> layout).</param>
|
||||
/// <returns>A string representation of the binding suitable for display in a UI.</returns>
|
||||
/// <remarks>
|
||||
/// This method is the same as <see cref="ToDisplayString(DisplayStringOptions,InputControl)"/> except that it
|
||||
/// will also return the name of the device layout and path of the control, if applicable to the binding. This is
|
||||
/// useful when needing more context on the resulting display string, for example to decide on an icon to display
|
||||
/// instead of the textual display string.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var displayString = new InputBinding("<Gamepad>/dpad/up")
|
||||
/// .ToDisplayString(out deviceLayout, out controlPath);
|
||||
///
|
||||
/// // Will print "D-Pad Up".
|
||||
/// Debug.Log(displayString);
|
||||
///
|
||||
/// // Will print "Gamepad".
|
||||
/// Debug.Log(deviceLayout);
|
||||
///
|
||||
/// // Will print "dpad/up".
|
||||
/// Debug.Log(controlPath);
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputControlPath.ToHumanReadableString(string,out string,out string,InputControlPath.HumanReadableStringOptions,InputControl)"/>
|
||||
/// <seealso cref="InputActionRebindingExtensions.GetBindingDisplayString(InputAction,int,out string,out string,InputBinding.DisplayStringOptions)"/>
|
||||
public string ToDisplayString(out string deviceLayoutName, out string controlPath, DisplayStringOptions options = default,
|
||||
InputControl control = default)
|
||||
{
|
||||
if (isComposite)
|
||||
{
|
||||
deviceLayoutName = null;
|
||||
controlPath = null;
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var readableStringOptions = default(InputControlPath.HumanReadableStringOptions);
|
||||
if ((options & DisplayStringOptions.DontOmitDevice) == 0)
|
||||
readableStringOptions |= InputControlPath.HumanReadableStringOptions.OmitDevice;
|
||||
if ((options & DisplayStringOptions.DontUseShortDisplayNames) == 0)
|
||||
readableStringOptions |= InputControlPath.HumanReadableStringOptions.UseShortNames;
|
||||
|
||||
var pathToUse = (options & DisplayStringOptions.IgnoreBindingOverrides) != 0
|
||||
? path
|
||||
: effectivePath;
|
||||
|
||||
var result = InputControlPath.ToHumanReadableString(pathToUse, out deviceLayoutName, out controlPath, readableStringOptions, control);
|
||||
|
||||
if (!string.IsNullOrEmpty(effectiveInteractions) && (options & DisplayStringOptions.DontIncludeInteractions) == 0)
|
||||
{
|
||||
var interactionString = string.Empty;
|
||||
var parsedInteractions = NameAndParameters.ParseMultiple(effectiveInteractions);
|
||||
|
||||
foreach (var element in parsedInteractions)
|
||||
{
|
||||
var interaction = element.name;
|
||||
var interactionDisplayName = InputInteraction.GetDisplayName(interaction);
|
||||
|
||||
// An interaction can avoid being mentioned explicitly by just setting its display
|
||||
// name to an empty string.
|
||||
if (string.IsNullOrEmpty(interactionDisplayName))
|
||||
continue;
|
||||
|
||||
////TODO: this will need support for localization
|
||||
if (!string.IsNullOrEmpty(interactionString))
|
||||
interactionString = $"{interactionString} or {interactionDisplayName}";
|
||||
else
|
||||
interactionString = interactionDisplayName;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(interactionString))
|
||||
result = $"{interactionString} {result}";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal bool TriggersAction(InputAction action)
|
||||
{
|
||||
// Match both name and ID on binding.
|
||||
return string.Compare(action.name, this.action, StringComparison.InvariantCultureIgnoreCase) == 0
|
||||
|| this.action == action.m_Id;
|
||||
}
|
||||
|
||||
////TODO: also support matching by name (taking the binding tree into account so that components
|
||||
//// of composites can be referenced through their parent)
|
||||
|
||||
/// <summary>
|
||||
/// Check whether <paramref name="binding"/> matches the mask
|
||||
/// represented by the current binding.
|
||||
/// </summary>
|
||||
/// <param name="binding">An input binding.</param>
|
||||
/// <returns>True if <paramref name="binding"/> is matched by the mask represented
|
||||
/// by <c>this</c>.</returns>
|
||||
/// <remarks>
|
||||
/// In this method, the current binding acts as a "mask". When used this way, only
|
||||
/// three properties of the binding are taken into account: <see cref="path"/>,
|
||||
/// <see cref="groups"/>, and <see cref="action"/>.
|
||||
///
|
||||
/// For each of these properties, the method checks whether they are set on the current
|
||||
/// binding and, if so, matches them against the respective property in <paramref name="binding"/>.
|
||||
///
|
||||
/// The way this matching works is that the value of the property in the current binding is
|
||||
/// allowed to be a semicolon-separated list where each element specifies one possible value
|
||||
/// that will produce a match.
|
||||
///
|
||||
/// Note that all comparisons are case-insensitive.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Create a couple bindings which we can test against.
|
||||
/// var keyboardBinding = new InputBinding
|
||||
/// {
|
||||
/// path = "<Keyboard>/space",
|
||||
/// groups = "Keyboard",
|
||||
/// action = "Fire"
|
||||
/// };
|
||||
/// var gamepadBinding = new InputBinding
|
||||
/// {
|
||||
/// path = "<Gamepad>/buttonSouth",
|
||||
/// groups = "Gamepad",
|
||||
/// action = "Jump"
|
||||
/// };
|
||||
/// var touchBinding = new InputBinding
|
||||
/// {
|
||||
/// path = "<Touchscreen>/*/tap",
|
||||
/// groups = "Touch",
|
||||
/// action = "Jump"
|
||||
/// };
|
||||
///
|
||||
/// // Example 1: Match any binding in the "Keyboard" or "Gamepad" group.
|
||||
/// var mask1 = new InputBinding
|
||||
/// {
|
||||
/// // We put two elements in the list here and separate them with a semicolon.
|
||||
/// groups = "Keyboard;Gamepad"
|
||||
/// };
|
||||
///
|
||||
/// mask1.Matches(keyboardBinding); // True
|
||||
/// mask1.Matches(gamepadBinding); // True
|
||||
/// mask1.Matches(touchBinding); // False
|
||||
///
|
||||
/// // Example 2: Match any binding to the "Jump" or the "Roll" action
|
||||
/// // (the latter we don't actually have a binding for)
|
||||
/// var mask2 = new InputBinding
|
||||
/// {
|
||||
/// action = "Jump;Roll"
|
||||
/// };
|
||||
///
|
||||
/// mask2.Matches(keyboardBinding); // False
|
||||
/// mask2.Matches(gamepadBinding); // True
|
||||
/// mask2.Matches(touchBinding); // True
|
||||
///
|
||||
/// // Example: Match any binding to the space or enter key in the
|
||||
/// // "Keyboard" group.
|
||||
/// var mask3 = new InputBinding
|
||||
/// {
|
||||
/// path = "<Keyboard>/space;<Keyboard>/enter",
|
||||
/// groups = "Keyboard"
|
||||
/// };
|
||||
///
|
||||
/// mask3.Matches(keyboardBinding); // True
|
||||
/// mask3.Matches(gamepadBinding); // False
|
||||
/// mask3.Matches(touchBinding); // False
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
public bool Matches(InputBinding binding)
|
||||
{
|
||||
return Matches(ref binding);
|
||||
}
|
||||
|
||||
// Internally we pass by reference to not unnecessarily copy the struct.
|
||||
internal bool Matches(ref InputBinding binding, MatchOptions options = default)
|
||||
{
|
||||
// Match name.
|
||||
if (name != null)
|
||||
{
|
||||
if (binding.name == null
|
||||
|| !StringHelpers.CharacterSeparatedListsHaveAtLeastOneCommonElement(name, binding.name, Separator))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Match path.
|
||||
if (path != null)
|
||||
{
|
||||
////REVIEW: should this use binding.effectivePath?
|
||||
////REVIEW: should this support matching by prefix only? should this use control path matching instead of just string comparisons?
|
||||
////TODO: handle things like ignoring leading '/'
|
||||
if (binding.path == null
|
||||
|| !StringHelpers.CharacterSeparatedListsHaveAtLeastOneCommonElement(path, binding.path, Separator))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Match action.
|
||||
if (action != null)
|
||||
{
|
||||
////TODO: handle "map/action" format
|
||||
////TODO: handle "map/*" format
|
||||
////REVIEW: this will not be able to handle cases where one binding references an action by ID and the other by name but both do mean the same action
|
||||
if (binding.action == null
|
||||
|| !StringHelpers.CharacterSeparatedListsHaveAtLeastOneCommonElement(action, binding.action, Separator))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Match groups.
|
||||
if (groups != null)
|
||||
{
|
||||
var haveGroupsOnBinding = !string.IsNullOrEmpty(binding.groups);
|
||||
if (!haveGroupsOnBinding && (options & MatchOptions.EmptyGroupMatchesAny) == 0)
|
||||
return false;
|
||||
|
||||
if (haveGroupsOnBinding
|
||||
&& !StringHelpers.CharacterSeparatedListsHaveAtLeastOneCommonElement(groups, binding.groups, Separator))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Match ID.
|
||||
if (!string.IsNullOrEmpty(m_Id))
|
||||
{
|
||||
if (binding.id != id)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum MatchOptions
|
||||
{
|
||||
EmptyGroupMatchesAny = 1 << 0,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum Flags
|
||||
{
|
||||
None = 0,
|
||||
Composite = 1 << 2,
|
||||
PartOfComposite = 1 << 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 49207568c569440f97a5bfe8b4144a16
|
||||
timeCreated: 1506842994
|
||||
@@ -0,0 +1,366 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine.InputSystem.Layouts;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
using UnityEngine.Scripting;
|
||||
|
||||
////TODO: support nested composites
|
||||
|
||||
////REVIEW: composites probably need a reset method, too (like interactions), so that they can be stateful
|
||||
|
||||
////REVIEW: isn't this about arbitrary value processing? can we open this up more and make it
|
||||
//// not just be about composing multiple bindings?
|
||||
|
||||
////REVIEW: when we get blittable type constraints, we can probably do away with the pointer-based ReadValue version
|
||||
|
||||
namespace UnityEngine.InputSystem
|
||||
{
|
||||
////TODO: clarify whether this can have state or not
|
||||
/// <summary>
|
||||
/// A binding that synthesizes a value from from several component bindings.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is the base class for composite bindings. See <see cref="InputBindingComposite{TValue}"/>
|
||||
/// for more details about composites and for how to define custom composites.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputSystem.RegisterBindingComposite{T}"/>
|
||||
/// <seealso cref="InputActionRebindingExtensions.GetParameterValue(InputAction,string,InputBinding)"/>
|
||||
/// <seealso cref="InputActionRebindingExtensions.ApplyParameterOverride(InputActionMap,string,PrimitiveValue,InputBinding)"/>
|
||||
/// <seealso cref="InputBinding.isComposite"/>
|
||||
public abstract class InputBindingComposite
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of value returned by the composite.
|
||||
/// </summary>
|
||||
/// <value>Type of value returned by the composite.</value>
|
||||
/// <remarks>
|
||||
/// Just like each <see cref="InputControl"/> has a specific type of value it
|
||||
/// will return, each composite has a specific type of value it will return.
|
||||
/// This is usually implicitly defined by the type parameter of <see
|
||||
/// cref="InputBindingComposite{TValue}"/>.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputControl.valueType"/>
|
||||
/// <seealso cref="InputAction.CallbackContext.valueType"/>
|
||||
public abstract Type valueType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Size of a value read by <see cref="ReadValue"/>.
|
||||
/// </summary>
|
||||
/// <value>Size of values stored in memory buffers by <see cref="ReadValue"/>.</value>
|
||||
/// <remarks>
|
||||
/// This is usually implicitly defined by the size of values derived
|
||||
/// from the type argument to <see cref="InputBindingComposite{TValue}"/>. E.g.
|
||||
/// if the type argument is <c>Vector2</c>, this property will be 8.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputControl.valueSizeInBytes"/>
|
||||
/// <seealso cref="InputAction.CallbackContext.valueSizeInBytes"/>
|
||||
public abstract int valueSizeInBytes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Read a value from the composite without having to know the value type (unlike
|
||||
/// <see cref="InputBindingComposite{TValue}.ReadValue(ref InputBindingCompositeContext)"/> and
|
||||
/// without allocating GC heap memory (unlike <see cref="ReadValueAsObject"/>).
|
||||
/// </summary>
|
||||
/// <param name="context">Callback context for the binding composite. Use this
|
||||
/// to access the values supplied by part bindings.</param>
|
||||
/// <param name="buffer">Buffer that receives the value read for the composite.</param>
|
||||
/// <param name="bufferSize">Size of the buffer allocated at <paramref name="buffer"/>.</param>
|
||||
/// <exception cref="ArgumentException"><paramref name="bufferSize"/> is smaller than
|
||||
/// <see cref="valueSizeInBytes"/>.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="buffer"/> is <c>null</c>.</exception>
|
||||
/// <remarks>
|
||||
/// This API will be used if someone calls <see cref="InputAction.CallbackContext.ReadValue(void*,int)"/>
|
||||
/// with the action leading to the composite.
|
||||
///
|
||||
/// By deriving from <see cref="InputBindingComposite{TValue}"/>, this will automatically
|
||||
/// be implemented for you.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputAction.CallbackContext.ReadValue"/>
|
||||
public abstract unsafe void ReadValue(ref InputBindingCompositeContext context, void* buffer, int bufferSize);
|
||||
|
||||
/// <summary>
|
||||
/// Read the value of the composite as a boxed object. This allows reading the value
|
||||
/// without having to know the value type and without having to deal with raw byte buffers.
|
||||
/// </summary>
|
||||
/// <param name="context">Callback context for the binding composite. Use this
|
||||
/// to access the values supplied by part bindings.</param>
|
||||
/// <returns>The current value of the composite according to the state passed in through
|
||||
/// <paramref name="context"/>.</returns>
|
||||
/// <remarks>
|
||||
/// This API will be used if someone calls <see cref="InputAction.CallbackContext.ReadValueAsObject"/>
|
||||
/// with the action leading to the composite.
|
||||
///
|
||||
/// By deriving from <see cref="InputBindingComposite{TValue}"/>, this will automatically
|
||||
/// be implemented for you.
|
||||
/// </remarks>
|
||||
public abstract object ReadValueAsObject(ref InputBindingCompositeContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Determine the current level of actuation of the composite.
|
||||
/// </summary>
|
||||
/// <param name="context">Callback context for the binding composite. Use this
|
||||
/// to access the values supplied by part bindings.</param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// This method by default returns -1, meaning that the composite does not support
|
||||
/// magnitudes. You can override the method to add support for magnitudes.
|
||||
///
|
||||
/// See <see cref="InputControl.EvaluateMagnitude()"/> for details of how magnitudes
|
||||
/// work.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputControl.EvaluateMagnitude()"/>
|
||||
public virtual float EvaluateMagnitude(ref InputBindingCompositeContext context)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after binding resolution for an <see cref="InputActionMap"/> is complete.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Some composites do not have predetermine value types. Two examples of this are
|
||||
/// <see cref="Composites.OneModifierComposite"/> and <see cref="Composites.TwoModifiersComposite"/>, which
|
||||
/// both have a <c>"binding"</c> part that can be bound to arbitrary controls. This means that the
|
||||
/// value type of these bindings can only be determined at runtime.
|
||||
///
|
||||
/// Overriding this method allows accessing the actual controls bound to each part
|
||||
/// at runtime.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// [InputControl] public int binding;
|
||||
///
|
||||
/// protected override void FinishSetup(ref InputBindingContext context)
|
||||
/// {
|
||||
/// // Get all controls bound to the 'binding' part.
|
||||
/// var controls = context.controls
|
||||
/// .Where(x => x.part == binding)
|
||||
/// .Select(x => x.control);
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
protected virtual void FinishSetup(ref InputBindingCompositeContext context)
|
||||
{
|
||||
}
|
||||
|
||||
// Avoid having to expose internal modifier.
|
||||
internal void CallFinishSetup(ref InputBindingCompositeContext context)
|
||||
{
|
||||
FinishSetup(ref context);
|
||||
}
|
||||
|
||||
internal static TypeTable s_Composites;
|
||||
|
||||
internal static Type GetValueType(string composite)
|
||||
{
|
||||
if (string.IsNullOrEmpty(composite))
|
||||
throw new ArgumentNullException(nameof(composite));
|
||||
|
||||
var compositeType = s_Composites.LookupTypeRegistration(composite);
|
||||
if (compositeType == null)
|
||||
return null;
|
||||
|
||||
return TypeHelpers.GetGenericTypeArgumentFromHierarchy(compositeType, typeof(InputBindingComposite<>), 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the name of the control layout that is expected for the given part (e.g. "Up") on the given
|
||||
/// composite (e.g. "Dpad").
|
||||
/// </summary>
|
||||
/// <param name="composite">Registration name of the composite.</param>
|
||||
/// <param name="part">Name of the part.</param>
|
||||
/// <returns>The layout name (such as "Button") expected for the given part on the composite or null if
|
||||
/// there is no composite with the given name or no part on the composite with the given name.</returns>
|
||||
/// <remarks>
|
||||
/// Expected control layouts can be set on composite parts by setting the <see cref="InputControlAttribute.layout"/>
|
||||
/// property on them.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// InputBindingComposite.GetExpectedControlLayoutName("Dpad", "Up") // Returns "Button"
|
||||
///
|
||||
/// // This is how Dpad communicates that:
|
||||
/// [InputControl(layout = "Button")] public int up;
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static string GetExpectedControlLayoutName(string composite, string part)
|
||||
{
|
||||
if (string.IsNullOrEmpty(composite))
|
||||
throw new ArgumentNullException(nameof(composite));
|
||||
if (string.IsNullOrEmpty(part))
|
||||
throw new ArgumentNullException(nameof(part));
|
||||
|
||||
var compositeType = s_Composites.LookupTypeRegistration(composite);
|
||||
if (compositeType == null)
|
||||
return null;
|
||||
|
||||
////TODO: allow it being properties instead of just fields
|
||||
var field = compositeType.GetField(part,
|
||||
BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.Public);
|
||||
if (field == null)
|
||||
return null;
|
||||
|
||||
var attribute = field.GetCustomAttribute<InputControlAttribute>(false);
|
||||
return attribute?.layout;
|
||||
}
|
||||
|
||||
internal static IEnumerable<string> GetPartNames(string composite)
|
||||
{
|
||||
if (string.IsNullOrEmpty(composite))
|
||||
throw new ArgumentNullException(nameof(composite));
|
||||
|
||||
var compositeType = s_Composites.LookupTypeRegistration(composite);
|
||||
if (compositeType == null)
|
||||
yield break;
|
||||
|
||||
foreach (var field in compositeType.GetFields(BindingFlags.Instance | BindingFlags.Public))
|
||||
{
|
||||
var controlAttribute = field.GetCustomAttribute<InputControlAttribute>();
|
||||
if (controlAttribute != null)
|
||||
yield return field.Name;
|
||||
}
|
||||
}
|
||||
|
||||
internal static string GetDisplayFormatString(string composite)
|
||||
{
|
||||
if (string.IsNullOrEmpty(composite))
|
||||
throw new ArgumentNullException(nameof(composite));
|
||||
|
||||
var compositeType = s_Composites.LookupTypeRegistration(composite);
|
||||
if (compositeType == null)
|
||||
return null;
|
||||
|
||||
var displayFormatAttribute = compositeType.GetCustomAttribute<DisplayStringFormatAttribute>();
|
||||
if (displayFormatAttribute == null)
|
||||
return null;
|
||||
|
||||
return displayFormatAttribute.formatString;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A binding composite arranges several bindings such that they form a "virtual control".
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">Type of value returned by the composite. This must be a "blittable"
|
||||
/// type, that is, a type whose values can simply be copied around.</typeparam>
|
||||
/// <remarks>
|
||||
/// Composite bindings are a special type of <see cref="InputBinding"/>. Whereas normally
|
||||
/// an input binding simply references a set of controls and returns whatever input values are
|
||||
/// generated by those controls, a composite binding sources input from several controls and
|
||||
/// derives a new value from that.
|
||||
///
|
||||
/// A good example for that is a classic WASD keyboard binding:
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var moveAction = new InputAction(name: "move");
|
||||
/// moveAction.AddCompositeBinding("Vector2")
|
||||
/// .With("Up", "<Keyboard>/w")
|
||||
/// .With("Down", "<Keyboard>/s")
|
||||
/// .With("Left", "<Keyboard>/a")
|
||||
/// .With("Right", "<Keyboard>/d")
|
||||
/// </code>
|
||||
/// </example>
|
||||
///
|
||||
/// Here, each direction is represented by a separate binding. "Up" is bound to "W", "Down"
|
||||
/// is bound to "S", and so on. Each direction individually returns a 0 or 1 depending
|
||||
/// on whether it is pressed or not.
|
||||
///
|
||||
/// However, as a composite, the binding to the "move" action returns a combined <c>Vector2</c>
|
||||
/// that is computed from the state of each of the directional controls. This is what composites
|
||||
/// do. They take inputs from their "parts" to derive an input for the binding as a whole.
|
||||
///
|
||||
/// Note that the properties and methods defined in <see cref="InputBindingComposite"/> and this
|
||||
/// class will generally be called internally by the input system and are not generally meant
|
||||
/// to be called directly from user land.
|
||||
///
|
||||
/// The set of composites available in the system is extensible. While some composites are
|
||||
/// such as <see cref="Composites.Vector2Composite"/> and <see cref="Composites.ButtonWithOneModifier"/>
|
||||
/// are available out of the box, new composites can be implemented by anyone and simply be autodiscover
|
||||
/// or manually registered with <see cref="InputSystem.RegisterBindingComposite{T}"/>.
|
||||
///
|
||||
/// See the "Custom Composite" sample (can be installed from package manager UI) for a detailed example
|
||||
/// of how to create a custom composite.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputSystem.RegisterBindingComposite{T}"/>
|
||||
public abstract class InputBindingComposite<TValue> : InputBindingComposite
|
||||
where TValue : struct
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of value returned by the composite, i.e. <c>typeof(TValue)</c>.
|
||||
/// </summary>
|
||||
/// <value>Returns <c>typeof(TValue)</c>.</value>
|
||||
public override Type valueType => typeof(TValue);
|
||||
|
||||
/// <summary>
|
||||
/// The size of values returned by the composite, i.e. <c>sizeof(TValue)</c>.
|
||||
/// </summary>
|
||||
/// <value>Returns <c>sizeof(TValue)</c>.</value>
|
||||
public override int valueSizeInBytes => UnsafeUtility.SizeOf<TValue>();
|
||||
|
||||
/// <summary>
|
||||
/// Read a value for the composite given the supplied context.
|
||||
/// </summary>
|
||||
/// <param name="context">Callback context for the binding composite. Use this
|
||||
/// to access the values supplied by part bindings.</param>
|
||||
/// <returns>The current value of the composite according to the state made
|
||||
/// accessible through <paramref name="context"/>.</returns>
|
||||
/// <remarks>
|
||||
/// This is the main method to implement in custom composites.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// public class CustomComposite : InputBindingComposite<float>
|
||||
/// {
|
||||
/// [InputControl(layout = "Button")]
|
||||
/// public int button;
|
||||
///
|
||||
/// public float scaleFactor = 1;
|
||||
///
|
||||
/// public override float ReadValue(ref InputBindingComposite context)
|
||||
/// {
|
||||
/// return context.ReadValue<float>(button) * scaleFactor;
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
///
|
||||
/// The other method to consider overriding is <see cref="InputBindingComposite.EvaluateMagnitude"/>.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputAction.ReadValue{TValue}"/>
|
||||
/// <seealso cref="InputAction.CallbackContext.ReadValue{TValue}"/>
|
||||
public abstract TValue ReadValue(ref InputBindingCompositeContext context);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override unsafe void ReadValue(ref InputBindingCompositeContext context, void* buffer, int bufferSize)
|
||||
{
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
|
||||
var valueSize = UnsafeUtility.SizeOf<TValue>();
|
||||
if (bufferSize < valueSize)
|
||||
throw new ArgumentException(
|
||||
$"Expected buffer of at least {UnsafeUtility.SizeOf<TValue>()} bytes but got buffer of only {bufferSize} bytes instead",
|
||||
nameof(bufferSize));
|
||||
|
||||
var value = ReadValue(ref context);
|
||||
var valuePtr = UnsafeUtility.AddressOf(ref value);
|
||||
|
||||
UnsafeUtility.MemCpy(buffer, valuePtr, valueSize);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override unsafe object ReadValueAsObject(ref InputBindingCompositeContext context)
|
||||
{
|
||||
var value = default(TValue);
|
||||
var valuePtr = UnsafeUtility.AddressOf(ref value);
|
||||
|
||||
ReadValue(ref context, valuePtr, UnsafeUtility.SizeOf<TValue>());
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6efadb7e1c29b4055adbd232610a4179
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,355 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.InputSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Contextual data made available when processing values of composite bindings.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// An instance of this struct is passed to <see
|
||||
/// cref="InputBindingComposite{TValue}.ReadValue(ref InputBindingCompositeContext)"/>.
|
||||
/// Use it to access contextual data such as the value for individual part bindings.
|
||||
///
|
||||
/// Note that an instance of this struct should never be held on to past the duration
|
||||
/// of the call to <c>ReadValue</c>. The data it retrieves is only valid during
|
||||
/// the callback.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputBindingComposite"/>
|
||||
/// <seealso cref="InputBindingComposite{TValue}"/>
|
||||
/// <seealso cref="InputBindingComposite{TValue}.ReadValue(ref InputBindingCompositeContext)"/>
|
||||
public struct InputBindingCompositeContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Information about a control bound to a part of a composite.
|
||||
/// </summary>
|
||||
public struct PartBinding
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifier of the part. This is the numeric identifier stored in the public
|
||||
/// fields of the composite by the input system.
|
||||
/// </summary>
|
||||
public int part { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The control bound to the part.
|
||||
/// </summary>
|
||||
public InputControl control { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerate all the controls that are part of the composite.
|
||||
/// </summary>
|
||||
/// <seealso cref="InputBindingComposite.FinishSetup"/>
|
||||
public IEnumerable<PartBinding> controls
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_State == null)
|
||||
yield break;
|
||||
|
||||
var totalBindingCount = m_State.totalBindingCount;
|
||||
for (var bindingIndex = m_BindingIndex + 1; bindingIndex < totalBindingCount; ++bindingIndex)
|
||||
{
|
||||
var bindingState = m_State.GetBindingState(bindingIndex);
|
||||
if (!bindingState.isPartOfComposite)
|
||||
break;
|
||||
|
||||
var controlStartIndex = bindingState.controlStartIndex;
|
||||
for (var i = 0; i < bindingState.controlCount; ++i)
|
||||
{
|
||||
var control = m_State.controls[controlStartIndex + i];
|
||||
yield return new PartBinding
|
||||
{
|
||||
part = bindingState.partIndex,
|
||||
control = control
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float EvaluateMagnitude(int partNumber)
|
||||
{
|
||||
return m_State.EvaluateCompositePartMagnitude(m_BindingIndex, partNumber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the value of the giving part binding.
|
||||
/// </summary>
|
||||
/// <param name="partNumber">Number of the part to read. This is assigned
|
||||
/// automatically by the input system and should be treated as an opaque
|
||||
/// identifier. See the example below.</param>
|
||||
/// <typeparam name="TValue">Type of value to read. This must match the
|
||||
/// value type expected from controls bound to the part.</typeparam>
|
||||
/// <returns>The value read from the part bindings.</returns>
|
||||
/// <exception cref="InvalidOperationException">The given <typeparamref name="TValue"/>
|
||||
/// value type does not match the actual value type of the control(s) bound
|
||||
/// to the part.</exception>
|
||||
/// <remarks>
|
||||
/// If no control is bound to the given part, the return value will always
|
||||
/// be <c>default(TValue)</c>. If a single control is bound to the part, the
|
||||
/// value will be that of the control. If multiple controls are bound to a
|
||||
/// part, the return value will be that greatest one according to <c>IComparable</c>
|
||||
/// implemented by <typeparamref name="TValue"/>.
|
||||
///
|
||||
/// Note that this method only works with values that are <c>IComparable</c>.
|
||||
/// To read a value type that is not <c>IComparable</c> or to supply a custom
|
||||
/// comparer, use <see cref="ReadValue{TValue,TComparer}(int,TComparer)"/>.
|
||||
///
|
||||
/// If an invalid <paramref name="partNumber"/> is supplied, the return value
|
||||
/// will simply be <c>default(TValue)</c>. No exception is thrown.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// public class MyComposite : InputBindingComposite<float>
|
||||
/// {
|
||||
/// // Defines a "part" binding for the composite. Each part can be
|
||||
/// // bound to arbitrary many times (including not at all). The "layout"
|
||||
/// // property of the attribute we supply determines what kind of
|
||||
/// // control is expected to be bound to the part.
|
||||
/// //
|
||||
/// // When initializing a composite instance, the input system will
|
||||
/// // automatically assign part numbers and store them in the fields
|
||||
/// // we define here.
|
||||
/// [InputControl(layout = "Button")]
|
||||
/// public int firstPart;
|
||||
///
|
||||
/// // Defines a second part.
|
||||
/// [InputControl(layout = "Vector2")]
|
||||
/// public int secondPart;
|
||||
///
|
||||
/// public override float ReadValue(ref InputBindingCompositeContext context)
|
||||
/// {
|
||||
/// // Read the button.
|
||||
/// var firstValue = context.ReadValue<float>();
|
||||
///
|
||||
/// // Read the vector.
|
||||
/// var secondValue = context.ReadValue<Vector2>();
|
||||
///
|
||||
/// // Perform some computation based on the inputs. Here, we just
|
||||
/// // scale the vector by the value we got from the button.
|
||||
/// return secondValue * firstValue;
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="ReadValue{TValue,TComparer}(int,TComparer)"/>
|
||||
/// <seealso cref="InputControl{TValue}.ReadValue"/>
|
||||
public unsafe TValue ReadValue<TValue>(int partNumber)
|
||||
where TValue : struct, IComparable<TValue>
|
||||
{
|
||||
if (m_State == null)
|
||||
return default;
|
||||
|
||||
return m_State.ReadCompositePartValue<TValue, DefaultComparer<TValue>>
|
||||
(m_BindingIndex, partNumber, null, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Same as <see cref="ReadValue{TValue}(int)"/> but also return the control
|
||||
/// from which the value was read.
|
||||
/// </summary>
|
||||
/// <param name="partNumber">Number of the part to read. This is assigned
|
||||
/// automatically by the input system and should be treated as an opaque
|
||||
/// identifier.</param>
|
||||
/// <param name="sourceControl">Receives the <see cref="InputControl"/> from
|
||||
/// which the value was read. If multiple controls are bound to the given part,
|
||||
/// this is the control whose value was ultimately selected. Will be set to
|
||||
/// <c>null</c> if <paramref name="partNumber"/> is not a valid part or if no
|
||||
/// controls are bound to the part.</param>
|
||||
/// <typeparam name="TValue">Type of value to read. This must match the
|
||||
/// value type expected from controls bound to the part.</typeparam>
|
||||
/// <returns>The value read from the part bindings.</returns>
|
||||
/// <remarks>
|
||||
/// Like <see cref="ReadValue{TValue}(int)"/>, this method relies on using <c>IComparable</c>
|
||||
/// implemented by <typeparamref name="TValue"/> to determine the greatest value
|
||||
/// if multiple controls are bound to the specified part.
|
||||
/// </remarks>
|
||||
/// <seealso cref="ReadValue{TValue}(int)"/>
|
||||
public unsafe TValue ReadValue<TValue>(int partNumber, out InputControl sourceControl)
|
||||
where TValue : struct, IComparable<TValue>
|
||||
{
|
||||
if (m_State == null)
|
||||
{
|
||||
sourceControl = null;
|
||||
return default;
|
||||
}
|
||||
|
||||
var value = m_State.ReadCompositePartValue<TValue, DefaultComparer<TValue>>(m_BindingIndex, partNumber,
|
||||
null, out var controlIndex);
|
||||
if (controlIndex != InputActionState.kInvalidIndex)
|
||||
sourceControl = m_State.controls[controlIndex];
|
||||
else
|
||||
sourceControl = null;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
////TODO: once we can break the API, remove the versions that rely on comparers and do everything through magnitude
|
||||
|
||||
/// <summary>
|
||||
/// Read the value of the given part bindings and use the given <paramref name="comparer"/>
|
||||
/// to determine which value to return if multiple controls are bound to the part.
|
||||
/// </summary>
|
||||
/// <param name="partNumber">Number of the part to read. This is assigned
|
||||
/// automatically by the input system and should be treated as an opaque
|
||||
/// identifier.</param>
|
||||
/// <param name="comparer">Instance of <typeparamref name="TComparer"/> for comparing
|
||||
/// multiple values.</param>
|
||||
/// <typeparam name="TValue">Type of value to read. This must match the
|
||||
/// value type expected from controls bound to the part.</typeparam>
|
||||
/// <returns>The value read from the part bindings.</returns>
|
||||
/// <typeparam name="TComparer">Comparer to use if multiple controls are bound to
|
||||
/// the given part. All values will be compared using <c>TComparer.Compare</c> and
|
||||
/// the greatest value will be returned.</typeparam>
|
||||
/// <returns>The value read from the part bindings.</returns>
|
||||
/// <remarks>
|
||||
/// This method is a useful alternative to <see cref="ReadValue{TValue}(int)"/> for
|
||||
/// value types that do not implement <c>IComparable</c> or when the default comparison
|
||||
/// behavior is undesirable.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// public class CompositeWithVector2Part : InputBindingComposite<Vector2>
|
||||
/// {
|
||||
/// [InputControl(layout = "Vector2")]
|
||||
/// public int part;
|
||||
///
|
||||
/// public override Vector2 ReadValue(ref InputBindingCompositeContext context)
|
||||
/// {
|
||||
/// // Return the Vector3 with the greatest magnitude.
|
||||
/// return context.ReadValue<Vector2, Vector2MagnitudeComparer>(part);
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="Utilities.Vector2MagnitudeComparer"/>
|
||||
/// <seealso cref="Utilities.Vector3MagnitudeComparer"/>
|
||||
public unsafe TValue ReadValue<TValue, TComparer>(int partNumber, TComparer comparer = default)
|
||||
where TValue : struct
|
||||
where TComparer : IComparer<TValue>
|
||||
{
|
||||
if (m_State == null)
|
||||
return default;
|
||||
|
||||
return m_State.ReadCompositePartValue<TValue, TComparer>(
|
||||
m_BindingIndex, partNumber, null, out _, comparer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Like <see cref="ReadValue{TValue,TComparer}(int,TComparer)"/> but also return
|
||||
/// the control from which the value has ultimately been read.
|
||||
/// </summary>
|
||||
/// <param name="partNumber">Number of the part to read. This is assigned
|
||||
/// automatically by the input system and should be treated as an opaque
|
||||
/// identifier.</param>
|
||||
/// <param name="sourceControl">Receives the <see cref="InputControl"/> from
|
||||
/// which the value was read. If multiple controls are bound to the given part,
|
||||
/// this is the control whose value was ultimately selected. Will be set to
|
||||
/// <c>null</c> if <paramref name="partNumber"/> is not a valid part or if no
|
||||
/// controls are bound to the part.</param>
|
||||
/// <param name="comparer">Instance of <typeparamref name="TComparer"/> for comparing
|
||||
/// multiple values.</param>
|
||||
/// <typeparam name="TValue">Type of value to read. This must match the
|
||||
/// value type expected from controls bound to the part.</typeparam>
|
||||
/// <returns>The value read from the part bindings.</returns>
|
||||
/// <typeparam name="TComparer">Comparer to use if multiple controls are bound to
|
||||
/// the given part. All values will be compared using <c>TComparer.Compare</c> and
|
||||
/// the greatest value will be returned.</typeparam>
|
||||
/// <returns>The value read from the part bindings.</returns>
|
||||
public unsafe TValue ReadValue<TValue, TComparer>(int partNumber, out InputControl sourceControl, TComparer comparer = default)
|
||||
where TValue : struct
|
||||
where TComparer : IComparer<TValue>
|
||||
{
|
||||
if (m_State == null)
|
||||
{
|
||||
sourceControl = null;
|
||||
return default;
|
||||
}
|
||||
|
||||
var value = m_State.ReadCompositePartValue<TValue, TComparer>(m_BindingIndex, partNumber, null,
|
||||
out var controlIndex, comparer);
|
||||
|
||||
if (controlIndex != InputActionState.kInvalidIndex)
|
||||
sourceControl = m_State.controls[controlIndex];
|
||||
else
|
||||
sourceControl = null;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Like <see cref="ReadValue{TValue}(int)"/> but treat bound controls as buttons. This means
|
||||
/// that custom <see cref="Controls.ButtonControl.pressPoint"/> are respected and that floating-point
|
||||
/// values from non-ButtonControls will be compared to <see cref="InputSettings.defaultButtonPressPoint"/>.
|
||||
/// </summary>
|
||||
/// <param name="partNumber">Number of the part to read. This is assigned
|
||||
/// automatically by the input system and should be treated as an opaque
|
||||
/// identifier.</param>
|
||||
/// <returns>True if any button bound to the part is pressed.</returns>
|
||||
/// <remarks>
|
||||
/// This method expects all controls bound to the part to be of type <c>InputControl<float></c>.
|
||||
///
|
||||
/// This method is different from just calling <see cref="ReadValue{TValue}(int)"/> with a <c>float</c>
|
||||
/// parameter and comparing the result to <see cref="InputSettings.defaultButtonPressPoint"/> in that
|
||||
/// custom press points set on individual ButtonControls will be respected.
|
||||
/// </remarks>
|
||||
/// <seealso cref="Controls.ButtonControl"/>
|
||||
/// <seealso cref="InputSettings.defaultButtonPressPoint"/>
|
||||
public unsafe bool ReadValueAsButton(int partNumber)
|
||||
{
|
||||
if (m_State == null)
|
||||
return default;
|
||||
|
||||
////REVIEW: wouldn't this have to take release points into account now?
|
||||
|
||||
var buttonValue = false;
|
||||
m_State.ReadCompositePartValue<float, DefaultComparer<float>>(m_BindingIndex, partNumber, &buttonValue,
|
||||
out _);
|
||||
return buttonValue;
|
||||
}
|
||||
|
||||
public unsafe void ReadValue(int partNumber, void* buffer, int bufferSize)
|
||||
{
|
||||
m_State?.ReadCompositePartValue(m_BindingIndex, partNumber, buffer, bufferSize);
|
||||
}
|
||||
|
||||
public object ReadValueAsObject(int partNumber)
|
||||
{
|
||||
return m_State.ReadCompositePartValueAsObject(m_BindingIndex, partNumber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the timestamp (see <see cref="LowLevel.InputEvent.time"/>) for when the given
|
||||
/// binding part crossed the button press threshold (see <see cref="Controls.ButtonControl.pressPoint"/>).
|
||||
/// </summary>
|
||||
/// <param name="partNumber">Number of the part to read. This is assigned
|
||||
/// automatically by the input system and should be treated as an opaque
|
||||
/// identifier.</param>
|
||||
/// <returns>Returns the time at which the given part binding moved into "press" state or 0 if there's
|
||||
/// current no press.</returns>
|
||||
/// <remarks>
|
||||
/// If the given part has more than a single binding and/or more than a single bound control, the <em>earliest</em>
|
||||
/// press time is returned.
|
||||
/// </remarks>
|
||||
public double GetPressTime(int partNumber)
|
||||
{
|
||||
return m_State.GetCompositePartPressTime(m_BindingIndex, partNumber);
|
||||
}
|
||||
|
||||
internal InputActionState m_State;
|
||||
internal int m_BindingIndex;
|
||||
|
||||
private struct DefaultComparer<TValue> : IComparer<TValue>
|
||||
where TValue : IComparable<TValue>
|
||||
{
|
||||
public int Compare(TValue x, TValue y)
|
||||
{
|
||||
return x.CompareTo(y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fbe76c3f0e980e6468b895ac1133c20d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,742 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Unity.Collections;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
|
||||
////TODO: reuse interaction, processor, and composite instances from prior resolves
|
||||
|
||||
namespace UnityEngine.InputSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Heart of the binding resolution machinery. Consumes lists of bindings
|
||||
/// and spits out out a list of resolved bindings together with their needed
|
||||
/// execution state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// One or more <see cref="InputActionMap">action maps</see> can be added to the same
|
||||
/// resolver. The result is a combination of the binding state of all maps.
|
||||
///
|
||||
/// The data set up by a resolver is for consumption by <see cref="InputActionState"/>.
|
||||
/// Essentially, InputBindingResolver does all the wiring and <see cref="InputActionState"/>
|
||||
/// does all the actual execution based on the resulting data.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputActionState.Initialize"/>
|
||||
internal struct InputBindingResolver : IDisposable
|
||||
{
|
||||
public int totalProcessorCount;
|
||||
public int totalCompositeCount;
|
||||
public int totalInteractionCount;
|
||||
public int totalMapCount => memory.mapCount;
|
||||
public int totalActionCount => memory.actionCount;
|
||||
public int totalBindingCount => memory.bindingCount;
|
||||
public int totalControlCount => memory.controlCount;
|
||||
|
||||
public InputActionMap[] maps;
|
||||
public InputControl[] controls;
|
||||
public InputActionState.UnmanagedMemory memory;
|
||||
public IInputInteraction[] interactions;
|
||||
public InputProcessor[] processors;
|
||||
public InputBindingComposite[] composites;
|
||||
|
||||
/// <summary>
|
||||
/// Binding mask used to globally mask out bindings.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is empty by default.
|
||||
///
|
||||
/// The bindings of each map will be <see cref="InputBinding.Matches">matched</see> against this
|
||||
/// binding. Any bindings that don't match will get skipped and not resolved to controls.
|
||||
///
|
||||
/// Note that regardless of whether a binding will be resolved to controls or not, it will get
|
||||
/// an entry in <see cref="memory"/>. Otherwise we would have to have a more complicated
|
||||
/// mapping from <see cref="InputActionMap.bindings"/> to a binding state in <see cref="memory"/>.
|
||||
/// </remarks>
|
||||
public InputBinding? bindingMask;
|
||||
|
||||
private bool m_IsControlOnlyResolve;
|
||||
|
||||
/// <summary>
|
||||
/// Release native memory held by the resolver.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
memory.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Steal the already allocated arrays from the given state.
|
||||
/// </summary>
|
||||
/// <param name="state">Action map state that was previously created.</param>
|
||||
/// <param name="isFullResolve">If false, the only thing that is allowed to change in the re-resolution
|
||||
/// is the list of controls. In other words, devices may have been added or removed but otherwise the configuration
|
||||
/// is exactly the same as in the last resolve. If true, anything may have changed and the resolver will only reuse
|
||||
/// allocations but not contents.</param>
|
||||
public void StartWithPreviousResolve(InputActionState state, bool isFullResolve)
|
||||
{
|
||||
Debug.Assert(state != null, "Received null state");
|
||||
Debug.Assert(!state.isProcessingControlStateChange,
|
||||
"Cannot re-resolve bindings for an InputActionState that is currently executing an action callback; binding resolution must be deferred to until after the callback has completed");
|
||||
|
||||
m_IsControlOnlyResolve = !isFullResolve;
|
||||
|
||||
maps = state.maps;
|
||||
interactions = state.interactions;
|
||||
processors = state.processors;
|
||||
composites = state.composites;
|
||||
controls = state.controls;
|
||||
|
||||
// Clear the arrays so that we don't leave references around.
|
||||
if (isFullResolve)
|
||||
{
|
||||
if (maps != null)
|
||||
Array.Clear(maps, 0, state.totalMapCount);
|
||||
if (interactions != null)
|
||||
Array.Clear(interactions, 0, state.totalInteractionCount);
|
||||
if (processors != null)
|
||||
Array.Clear(processors, 0, state.totalProcessorCount);
|
||||
if (composites != null)
|
||||
Array.Clear(composites, 0, state.totalCompositeCount);
|
||||
}
|
||||
if (controls != null) // Always clear this one as every resolve will change it.
|
||||
Array.Clear(controls, 0, state.totalControlCount);
|
||||
|
||||
// Null out the arrays on the state so that there is no strange bugs with
|
||||
// the state reading from arrays that no longer belong to it.
|
||||
state.maps = null;
|
||||
state.interactions = null;
|
||||
state.processors = null;
|
||||
state.composites = null;
|
||||
state.controls = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolve and add all bindings and actions from the given map.
|
||||
/// </summary>
|
||||
/// <param name="actionMap"></param>
|
||||
/// <remarks>
|
||||
/// This is where all binding resolution happens for actions. The method walks through the binding array
|
||||
/// in <paramref name="actionMap"/> and adds any controls, interactions, processors, and composites as it goes.
|
||||
/// </remarks>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1809:AvoidExcessiveLocals", Justification = "TODO: Refactor later.")]
|
||||
public unsafe void AddActionMap(InputActionMap actionMap)
|
||||
{
|
||||
Debug.Assert(actionMap != null, "Received null map");
|
||||
|
||||
InputSystem.EnsureInitialized();
|
||||
|
||||
var actionsInThisMap = actionMap.m_Actions;
|
||||
var bindingsInThisMap = actionMap.m_Bindings;
|
||||
var bindingCountInThisMap = bindingsInThisMap?.Length ?? 0;
|
||||
var actionCountInThisMap = actionsInThisMap?.Length ?? 0;
|
||||
var mapIndex = totalMapCount;
|
||||
|
||||
// Keep track of indices for this map.
|
||||
var actionStartIndex = totalActionCount;
|
||||
var bindingStartIndex = totalBindingCount;
|
||||
var controlStartIndex = totalControlCount;
|
||||
var interactionStartIndex = totalInteractionCount;
|
||||
var processorStartIndex = totalProcessorCount;
|
||||
var compositeStartIndex = totalCompositeCount;
|
||||
|
||||
// Allocate an initial block of memory. We probably will have to re-allocate once
|
||||
// at the end to accommodate interactions and controls added from the map.
|
||||
var newMemory = new InputActionState.UnmanagedMemory();
|
||||
newMemory.Allocate(
|
||||
mapCount: totalMapCount + 1,
|
||||
actionCount: totalActionCount + actionCountInThisMap,
|
||||
bindingCount: totalBindingCount + bindingCountInThisMap,
|
||||
// We reallocate for the following once we know the final count.
|
||||
interactionCount: totalInteractionCount,
|
||||
compositeCount: totalCompositeCount,
|
||||
controlCount: totalControlCount);
|
||||
if (memory.isAllocated)
|
||||
newMemory.CopyDataFrom(memory);
|
||||
|
||||
////TODO: make sure composite objects get all the bindings they need
|
||||
////TODO: handle case where we have bindings resolving to the same control
|
||||
//// (not so clear cut what to do there; each binding may have a different interaction setup, for example)
|
||||
var currentCompositeBindingIndex = InputActionState.kInvalidIndex;
|
||||
var currentCompositeIndex = InputActionState.kInvalidIndex;
|
||||
var currentCompositePartCount = 0;
|
||||
var currentCompositeActionIndexInMap = InputActionState.kInvalidIndex;
|
||||
InputAction currentCompositeAction = null;
|
||||
var bindingMaskOnThisMap = actionMap.m_BindingMask;
|
||||
var devicesForThisMap = actionMap.devices;
|
||||
var isSingletonAction = actionMap.m_SingletonAction != null;
|
||||
|
||||
// Can't use `using` as we need to use it with `ref`.
|
||||
var resolvedControls = new InputControlList<InputControl>(Allocator.Temp);
|
||||
|
||||
// We gather all controls in temporary memory and then move them over into newMemory once
|
||||
// we're done resolving.
|
||||
try
|
||||
{
|
||||
for (var n = 0; n < bindingCountInThisMap; ++n)
|
||||
{
|
||||
var bindingStatesPtr = newMemory.bindingStates;
|
||||
ref var unresolvedBinding = ref bindingsInThisMap[n];
|
||||
var bindingIndex = bindingStartIndex + n;
|
||||
var isComposite = unresolvedBinding.isComposite;
|
||||
var isPartOfComposite = !isComposite && unresolvedBinding.isPartOfComposite;
|
||||
var bindingState = &bindingStatesPtr[bindingIndex];
|
||||
|
||||
try
|
||||
{
|
||||
////TODO: if it's a composite, check if any of the children matches our binding masks (if any) and skip composite if none do
|
||||
|
||||
var firstControlIndex = 0; // numControls dictates whether this is a valid index or not.
|
||||
var firstInteractionIndex = InputActionState.kInvalidIndex;
|
||||
var firstProcessorIndex = InputActionState.kInvalidIndex;
|
||||
var actionIndexForBinding = InputActionState.kInvalidIndex;
|
||||
var partIndex = InputActionState.kInvalidIndex;
|
||||
|
||||
var numControls = 0;
|
||||
var numInteractions = 0;
|
||||
var numProcessors = 0;
|
||||
|
||||
// Make sure that if it's part of a composite, we are actually part of a composite.
|
||||
if (isPartOfComposite && currentCompositeBindingIndex == InputActionState.kInvalidIndex)
|
||||
throw new InvalidOperationException(
|
||||
$"Binding '{unresolvedBinding}' is marked as being part of a composite but the preceding binding is not a composite");
|
||||
|
||||
// Try to find action.
|
||||
//
|
||||
// NOTE: We ignore actions on bindings that are part of composites. We only allow
|
||||
// actions to be triggered from the composite itself.
|
||||
var actionIndexInMap = InputActionState.kInvalidIndex;
|
||||
var actionName = unresolvedBinding.action;
|
||||
InputAction action = null;
|
||||
if (!isPartOfComposite)
|
||||
{
|
||||
if (isSingletonAction)
|
||||
{
|
||||
// Singleton actions always ignore names.
|
||||
actionIndexInMap = 0;
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(actionName))
|
||||
{
|
||||
////REVIEW: should we fail here if we don't manage to find the action
|
||||
actionIndexInMap = actionMap.FindActionIndex(actionName);
|
||||
}
|
||||
|
||||
if (actionIndexInMap != InputActionState.kInvalidIndex)
|
||||
action = actionsInThisMap[actionIndexInMap];
|
||||
}
|
||||
else
|
||||
{
|
||||
actionIndexInMap = currentCompositeActionIndexInMap;
|
||||
action = currentCompositeAction;
|
||||
}
|
||||
|
||||
// If it's a composite, start a chain.
|
||||
if (isComposite)
|
||||
{
|
||||
currentCompositeBindingIndex = bindingIndex;
|
||||
currentCompositeAction = action;
|
||||
currentCompositeActionIndexInMap = actionIndexInMap;
|
||||
}
|
||||
|
||||
// Determine if the binding is disabled.
|
||||
// Disabled if path is empty.
|
||||
var path = unresolvedBinding.effectivePath;
|
||||
var bindingIsDisabled = string.IsNullOrEmpty(path)
|
||||
|
||||
// Also, if we can't find the action to trigger for the binding, we just go and disable
|
||||
// the binding.
|
||||
|| action == null
|
||||
|
||||
// Also, disabled if binding doesn't match with our binding mask (might be empty).
|
||||
|| (!isComposite && bindingMask != null &&
|
||||
!bindingMask.Value.Matches(ref unresolvedBinding,
|
||||
InputBinding.MatchOptions.EmptyGroupMatchesAny))
|
||||
|
||||
// Also, disabled if binding doesn't match the binding mask on the map (might be empty).
|
||||
|| (!isComposite && bindingMaskOnThisMap != null &&
|
||||
!bindingMaskOnThisMap.Value.Matches(ref unresolvedBinding,
|
||||
InputBinding.MatchOptions.EmptyGroupMatchesAny))
|
||||
|
||||
// Finally, also disabled if binding doesn't match the binding mask on the action (might be empty).
|
||||
|| (!isComposite && action?.m_BindingMask != null &&
|
||||
!action.m_BindingMask.Value.Matches(ref unresolvedBinding,
|
||||
InputBinding.MatchOptions.EmptyGroupMatchesAny));
|
||||
|
||||
// If the binding isn't disabled, look up controls now. We do this first as we may still disable the
|
||||
// binding if it doesn't resolve to any controls or resolves only to controls already bound to by
|
||||
// other bindings.
|
||||
//
|
||||
// NOTE: We continuously add controls here to `resolvedControls`. Once we've completed our
|
||||
// pass over the bindings in the map, `resolvedControls` will have all the controls for
|
||||
// the current map.
|
||||
if (!bindingIsDisabled && !isComposite)
|
||||
{
|
||||
firstControlIndex = memory.controlCount + resolvedControls.Count;
|
||||
if (devicesForThisMap != null)
|
||||
{
|
||||
// Search in devices for only this map.
|
||||
var list = devicesForThisMap.Value;
|
||||
for (var i = 0; i < list.Count; ++i)
|
||||
{
|
||||
var device = list[i];
|
||||
if (!device.added)
|
||||
continue; // Skip devices that have been removed.
|
||||
numControls += InputControlPath.TryFindControls(device, path, 0, ref resolvedControls);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Search globally.
|
||||
numControls = InputSystem.FindControls(path, ref resolvedControls);
|
||||
}
|
||||
}
|
||||
|
||||
// If the binding isn't disabled, resolve its controls, processors, and interactions.
|
||||
if (!bindingIsDisabled)
|
||||
{
|
||||
// NOTE: When isFullResolve==false, it is *imperative* that we do count processor and interaction
|
||||
// counts here come out exactly the same as in the previous full resolve.
|
||||
|
||||
// Instantiate processors.
|
||||
var processorString = unresolvedBinding.effectiveProcessors;
|
||||
if (!string.IsNullOrEmpty(processorString))
|
||||
{
|
||||
// Add processors from binding.
|
||||
firstProcessorIndex = InstantiateWithParameters(InputProcessor.s_Processors, processorString,
|
||||
ref processors, ref totalProcessorCount, actionMap, ref unresolvedBinding);
|
||||
if (firstProcessorIndex != InputActionState.kInvalidIndex)
|
||||
numProcessors = totalProcessorCount - firstProcessorIndex;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(action.m_Processors))
|
||||
{
|
||||
// Add processors from action.
|
||||
var index = InstantiateWithParameters(InputProcessor.s_Processors, action.m_Processors, ref processors,
|
||||
ref totalProcessorCount, actionMap, ref unresolvedBinding);
|
||||
if (index != InputActionState.kInvalidIndex)
|
||||
{
|
||||
if (firstProcessorIndex == InputActionState.kInvalidIndex)
|
||||
firstProcessorIndex = index;
|
||||
numProcessors += totalProcessorCount - index;
|
||||
}
|
||||
}
|
||||
|
||||
// Instantiate interactions.
|
||||
if (isPartOfComposite)
|
||||
{
|
||||
// Composite's part use composite interactions
|
||||
if (currentCompositeBindingIndex != InputActionState.kInvalidIndex)
|
||||
{
|
||||
firstInteractionIndex = bindingStatesPtr[currentCompositeBindingIndex].interactionStartIndex;
|
||||
numInteractions = bindingStatesPtr[currentCompositeBindingIndex].interactionCount;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var interactionString = unresolvedBinding.effectiveInteractions;
|
||||
if (!string.IsNullOrEmpty(interactionString))
|
||||
{
|
||||
// Add interactions from binding.
|
||||
firstInteractionIndex = InstantiateWithParameters(InputInteraction.s_Interactions, interactionString,
|
||||
ref interactions, ref totalInteractionCount, actionMap, ref unresolvedBinding);
|
||||
if (firstInteractionIndex != InputActionState.kInvalidIndex)
|
||||
numInteractions = totalInteractionCount - firstInteractionIndex;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(action.m_Interactions))
|
||||
{
|
||||
// Add interactions from action.
|
||||
var index = InstantiateWithParameters(InputInteraction.s_Interactions, action.m_Interactions,
|
||||
ref interactions, ref totalInteractionCount, actionMap, ref unresolvedBinding);
|
||||
if (index != InputActionState.kInvalidIndex)
|
||||
{
|
||||
if (firstInteractionIndex == InputActionState.kInvalidIndex)
|
||||
firstInteractionIndex = index;
|
||||
numInteractions += totalInteractionCount - index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If it's the start of a composite chain, create the composite.
|
||||
if (isComposite)
|
||||
{
|
||||
// The composite binding entry itself does not resolve to any controls.
|
||||
// It creates a composite binding object which is then populated from
|
||||
// subsequent bindings.
|
||||
|
||||
// Instantiate. For composites, the path is the name of the composite.
|
||||
var composite = InstantiateBindingComposite(ref unresolvedBinding, actionMap);
|
||||
currentCompositeIndex =
|
||||
ArrayHelpers.AppendWithCapacity(ref composites, ref totalCompositeCount, composite);
|
||||
|
||||
// Record where the controls for parts of the composite start.
|
||||
firstControlIndex = memory.controlCount + resolvedControls.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we've reached the end of a composite chain, finish
|
||||
// off the current composite.
|
||||
if (!isPartOfComposite && currentCompositeBindingIndex != InputActionState.kInvalidIndex)
|
||||
{
|
||||
currentCompositePartCount = 0;
|
||||
currentCompositeBindingIndex = InputActionState.kInvalidIndex;
|
||||
currentCompositeIndex = InputActionState.kInvalidIndex;
|
||||
currentCompositeAction = null;
|
||||
currentCompositeActionIndexInMap = InputActionState.kInvalidIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the binding is part of a composite, pass the resolved controls
|
||||
// on to the composite.
|
||||
if (isPartOfComposite && currentCompositeBindingIndex != InputActionState.kInvalidIndex && numControls > 0)
|
||||
{
|
||||
// Make sure the binding is named. The name determines what in the composite
|
||||
// to bind to.
|
||||
if (string.IsNullOrEmpty(unresolvedBinding.name))
|
||||
throw new InvalidOperationException(
|
||||
$"Binding '{unresolvedBinding}' that is part of composite '{composites[currentCompositeIndex]}' is missing a name");
|
||||
|
||||
// Assign an index to the current part of the composite which
|
||||
// can be used by the composite to read input from this part.
|
||||
partIndex = AssignCompositePartIndex(composites[currentCompositeIndex], unresolvedBinding.name,
|
||||
ref currentCompositePartCount);
|
||||
|
||||
// Keep track of total number of controls bound in the composite.
|
||||
bindingStatesPtr[currentCompositeBindingIndex].controlCount += numControls;
|
||||
|
||||
// Force action index on part binding to be same as that of composite.
|
||||
actionIndexForBinding = bindingStatesPtr[currentCompositeBindingIndex].actionIndex;
|
||||
}
|
||||
else if (actionIndexInMap != InputActionState.kInvalidIndex)
|
||||
{
|
||||
actionIndexForBinding = actionStartIndex + actionIndexInMap;
|
||||
}
|
||||
|
||||
// Store resolved binding.
|
||||
*bindingState = new InputActionState.BindingState
|
||||
{
|
||||
controlStartIndex = firstControlIndex,
|
||||
// For composites, this will be adjusted as we add each part.
|
||||
controlCount = numControls,
|
||||
interactionStartIndex = firstInteractionIndex,
|
||||
interactionCount = numInteractions,
|
||||
processorStartIndex = firstProcessorIndex,
|
||||
processorCount = numProcessors,
|
||||
isComposite = isComposite,
|
||||
isPartOfComposite = unresolvedBinding.isPartOfComposite,
|
||||
partIndex = partIndex,
|
||||
actionIndex = actionIndexForBinding,
|
||||
compositeOrCompositeBindingIndex = isComposite ? currentCompositeIndex : currentCompositeBindingIndex,
|
||||
mapIndex = totalMapCount,
|
||||
wantsInitialStateCheck = action?.wantsInitialStateCheck ?? false
|
||||
};
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"{exception.GetType().Name} while resolving binding '{unresolvedBinding}' in action map '{actionMap}'");
|
||||
Debug.LogException(exception);
|
||||
|
||||
// Don't swallow exceptions that indicate something is wrong in the code rather than
|
||||
// in the data.
|
||||
if (exception.IsExceptionIndicatingBugInCode())
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// Re-allocate memory to accommodate controls and interaction states. The count for those
|
||||
// we only know once we've completed all resolution.
|
||||
var controlCountInThisMap = resolvedControls.Count;
|
||||
var newTotalControlCount = memory.controlCount + controlCountInThisMap;
|
||||
if (newMemory.interactionCount != totalInteractionCount ||
|
||||
newMemory.compositeCount != totalCompositeCount ||
|
||||
newMemory.controlCount != newTotalControlCount)
|
||||
{
|
||||
var finalMemory = new InputActionState.UnmanagedMemory();
|
||||
|
||||
finalMemory.Allocate(
|
||||
mapCount: newMemory.mapCount,
|
||||
actionCount: newMemory.actionCount,
|
||||
bindingCount: newMemory.bindingCount,
|
||||
controlCount: newTotalControlCount,
|
||||
interactionCount: totalInteractionCount,
|
||||
compositeCount: totalCompositeCount);
|
||||
|
||||
finalMemory.CopyDataFrom(newMemory);
|
||||
|
||||
newMemory.Dispose();
|
||||
newMemory = finalMemory;
|
||||
}
|
||||
|
||||
// Add controls to array.
|
||||
var controlCountInArray = memory.controlCount;
|
||||
ArrayHelpers.AppendListWithCapacity(ref controls, ref controlCountInArray, resolvedControls);
|
||||
Debug.Assert(controlCountInArray == newTotalControlCount,
|
||||
"Control array should have combined count of old and new controls");
|
||||
|
||||
// Set up control to binding index mapping.
|
||||
for (var i = 0; i < bindingCountInThisMap; ++i)
|
||||
{
|
||||
var bindingStatesPtr = newMemory.bindingStates;
|
||||
var bindingState = &bindingStatesPtr[bindingStartIndex + i];
|
||||
var numControls = bindingState->controlCount;
|
||||
var startIndex = bindingState->controlStartIndex;
|
||||
for (var n = 0; n < numControls; ++n)
|
||||
newMemory.controlIndexToBindingIndex[startIndex + n] = bindingStartIndex + i;
|
||||
}
|
||||
|
||||
// Initialize initial interaction states.
|
||||
for (var i = memory.interactionCount; i < newMemory.interactionCount; ++i)
|
||||
{
|
||||
ref var interactionState = ref newMemory.interactionStates[i];
|
||||
interactionState.phase = InputActionPhase.Waiting;
|
||||
interactionState.triggerControlIndex = InputActionState.kInvalidIndex;
|
||||
}
|
||||
|
||||
// Initialize action data.
|
||||
var runningIndexInBindingIndices = memory.bindingCount;
|
||||
for (var i = 0; i < actionCountInThisMap; ++i)
|
||||
{
|
||||
var action = actionsInThisMap[i];
|
||||
var actionIndex = actionStartIndex + i;
|
||||
|
||||
// Correlate action with its trigger state.
|
||||
action.m_ActionIndexInState = actionIndex;
|
||||
|
||||
Debug.Assert(runningIndexInBindingIndices < ushort.MaxValue, "Binding start index on action exceeds limit");
|
||||
newMemory.actionBindingIndicesAndCounts[actionIndex * 2] = (ushort)runningIndexInBindingIndices;
|
||||
|
||||
// Collect bindings for action.
|
||||
var firstBindingIndexForAction = -1;
|
||||
var bindingCountForAction = 0;
|
||||
var numPossibleConcurrentActuations = 0;
|
||||
|
||||
for (var n = 0; n < bindingCountInThisMap; ++n)
|
||||
{
|
||||
var bindingIndex = bindingStartIndex + n;
|
||||
var bindingState = &newMemory.bindingStates[bindingIndex];
|
||||
if (bindingState->actionIndex != actionIndex)
|
||||
continue;
|
||||
if (bindingState->isPartOfComposite)
|
||||
continue;
|
||||
|
||||
Debug.Assert(bindingIndex <= ushort.MaxValue, "Binding index exceeds limit");
|
||||
newMemory.actionBindingIndices[runningIndexInBindingIndices] = (ushort)bindingIndex;
|
||||
++runningIndexInBindingIndices;
|
||||
++bindingCountForAction;
|
||||
|
||||
if (firstBindingIndexForAction == -1)
|
||||
firstBindingIndexForAction = bindingIndex;
|
||||
|
||||
// Keep track of how many concurrent actuations we may be seeing on the action so that
|
||||
// we know whether we need to enable conflict resolution or not.
|
||||
if (bindingState->isComposite)
|
||||
{
|
||||
// Composite binding. Actuates as a whole. Check if the composite has successfully
|
||||
// resolved any controls. If so, it adds one possible actuation.
|
||||
if (bindingState->controlCount > 0)
|
||||
++numPossibleConcurrentActuations;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Normal binding. Every successfully resolved control results in one possible actuation.
|
||||
numPossibleConcurrentActuations += bindingState->controlCount;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstBindingIndexForAction == -1)
|
||||
firstBindingIndexForAction = 0;
|
||||
|
||||
Debug.Assert(bindingCountForAction < ushort.MaxValue, "Binding count on action exceeds limit");
|
||||
newMemory.actionBindingIndicesAndCounts[actionIndex * 2 + 1] = (ushort)bindingCountForAction;
|
||||
|
||||
// See if we may need conflict resolution on this action. Never needed for pass-through actions.
|
||||
// Otherwise, if we have more than one bound control or have several bindings and one of them
|
||||
// is a composite, we enable it.
|
||||
var isPassThroughAction = action.type == InputActionType.PassThrough;
|
||||
var isButtonAction = action.type == InputActionType.Button;
|
||||
var mayNeedConflictResolution = !isPassThroughAction && numPossibleConcurrentActuations > 1;
|
||||
|
||||
// Initialize initial trigger state.
|
||||
newMemory.actionStates[actionIndex] =
|
||||
new InputActionState.TriggerState
|
||||
{
|
||||
phase = InputActionPhase.Disabled,
|
||||
mapIndex = mapIndex,
|
||||
controlIndex = InputActionState.kInvalidIndex,
|
||||
interactionIndex = InputActionState.kInvalidIndex,
|
||||
isPassThrough = isPassThroughAction,
|
||||
isButton = isButtonAction,
|
||||
mayNeedConflictResolution = mayNeedConflictResolution,
|
||||
bindingIndex = firstBindingIndexForAction
|
||||
};
|
||||
}
|
||||
|
||||
// Store indices for map.
|
||||
newMemory.mapIndices[mapIndex] =
|
||||
new InputActionState.ActionMapIndices
|
||||
{
|
||||
actionStartIndex = actionStartIndex,
|
||||
actionCount = actionCountInThisMap,
|
||||
controlStartIndex = controlStartIndex,
|
||||
controlCount = controlCountInThisMap,
|
||||
bindingStartIndex = bindingStartIndex,
|
||||
bindingCount = bindingCountInThisMap,
|
||||
interactionStartIndex = interactionStartIndex,
|
||||
interactionCount = totalInteractionCount - interactionStartIndex,
|
||||
processorStartIndex = processorStartIndex,
|
||||
processorCount = totalProcessorCount - processorStartIndex,
|
||||
compositeStartIndex = compositeStartIndex,
|
||||
compositeCount = totalCompositeCount - compositeStartIndex,
|
||||
};
|
||||
actionMap.m_MapIndexInState = mapIndex;
|
||||
var finalActionMapCount = memory.mapCount;
|
||||
ArrayHelpers.AppendWithCapacity(ref maps, ref finalActionMapCount, actionMap, capacityIncrement: 4);
|
||||
Debug.Assert(finalActionMapCount == newMemory.mapCount,
|
||||
"Final action map count should match old action map count plus one");
|
||||
|
||||
// As a final act, swap the new memory in.
|
||||
memory.Dispose();
|
||||
memory = newMemory;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Don't leak our native memory when we throw an exception.
|
||||
newMemory.Dispose();
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
resolvedControls.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private List<NameAndParameters> m_Parameters; // We retain this to reuse the allocation.
|
||||
private int InstantiateWithParameters<TType>(TypeTable registrations, string namesAndParameters, ref TType[] array, ref int count, InputActionMap actionMap, ref InputBinding binding)
|
||||
{
|
||||
if (!NameAndParameters.ParseMultiple(namesAndParameters, ref m_Parameters))
|
||||
return InputActionState.kInvalidIndex;
|
||||
|
||||
var firstIndex = count;
|
||||
for (var i = 0; i < m_Parameters.Count; ++i)
|
||||
{
|
||||
// Look up type.
|
||||
var objectRegistrationName = m_Parameters[i].name;
|
||||
var type = registrations.LookupTypeRegistration(objectRegistrationName);
|
||||
if (type == null)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"No {typeof(TType).Name} with name '{objectRegistrationName}' (mentioned in '{namesAndParameters}') has been registered");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!m_IsControlOnlyResolve)
|
||||
{
|
||||
// Instantiate it.
|
||||
if (!(Activator.CreateInstance(type) is TType instance))
|
||||
{
|
||||
Debug.LogError(
|
||||
$"Type '{type.Name}' registered as '{objectRegistrationName}' (mentioned in '{namesAndParameters}') is not an {typeof(TType).Name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Pass parameters to it.
|
||||
ApplyParameters(m_Parameters[i].parameters, instance, actionMap, ref binding, objectRegistrationName,
|
||||
namesAndParameters);
|
||||
|
||||
// Add to list.
|
||||
ArrayHelpers.AppendWithCapacity(ref array, ref count, instance);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(type.IsInstanceOfType(array[count]), "Type of instance in array does not match expected type");
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
return firstIndex;
|
||||
}
|
||||
|
||||
private static InputBindingComposite InstantiateBindingComposite(ref InputBinding binding, InputActionMap actionMap)
|
||||
{
|
||||
var nameAndParametersParsed = NameAndParameters.Parse(binding.effectivePath);
|
||||
|
||||
// Look up.
|
||||
var type = InputBindingComposite.s_Composites.LookupTypeRegistration(nameAndParametersParsed.name);
|
||||
if (type == null)
|
||||
throw new InvalidOperationException(
|
||||
$"No binding composite with name '{nameAndParametersParsed.name}' has been registered");
|
||||
|
||||
// Instantiate.
|
||||
if (!(Activator.CreateInstance(type) is InputBindingComposite instance))
|
||||
throw new InvalidOperationException(
|
||||
$"Registered type '{type.Name}' used for '{nameAndParametersParsed.name}' is not an InputBindingComposite");
|
||||
|
||||
// Set parameters.
|
||||
ApplyParameters(nameAndParametersParsed.parameters, instance, actionMap, ref binding, nameAndParametersParsed.name,
|
||||
binding.effectivePath);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private static void ApplyParameters(ReadOnlyArray<NamedValue> parameters, object instance, InputActionMap actionMap, ref InputBinding binding, string objectRegistrationName, string namesAndParameters)
|
||||
{
|
||||
foreach (var parameter in parameters)
|
||||
{
|
||||
// Find field.
|
||||
var field = instance.GetType().GetField(parameter.name,
|
||||
BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
if (field == null)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"Type '{instance.GetType().Name}' registered as '{objectRegistrationName}' (mentioned in '{namesAndParameters}') has no public field called '{parameter.name}'");
|
||||
continue;
|
||||
}
|
||||
var fieldTypeCode = Type.GetTypeCode(field.FieldType);
|
||||
|
||||
// See if we have a parameter override.
|
||||
var parameterOverride =
|
||||
InputActionRebindingExtensions.ParameterOverride.Find(actionMap, ref binding, parameter.name, objectRegistrationName);
|
||||
var value = parameterOverride != null
|
||||
? parameterOverride.Value.value
|
||||
: parameter.value;
|
||||
|
||||
field.SetValue(instance, value.ConvertTo(fieldTypeCode).ToObject());
|
||||
}
|
||||
}
|
||||
|
||||
private static int AssignCompositePartIndex(object composite, string name, ref int currentCompositePartCount)
|
||||
{
|
||||
var type = composite.GetType();
|
||||
|
||||
////REVIEW: check for [InputControl] attribute?
|
||||
|
||||
////TODO: allow this to be a property instead
|
||||
// Look up field.
|
||||
var field = type.GetField(name,
|
||||
BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
if (field == null)
|
||||
throw new InvalidOperationException(
|
||||
$"Cannot find public field '{name}' used as parameter of binding composite '{composite}' of type '{type}'");
|
||||
|
||||
////REVIEW: should we wrap part numbers in a struct instead of using int?
|
||||
|
||||
// Type-check.
|
||||
var fieldType = field.FieldType;
|
||||
if (fieldType != typeof(int))
|
||||
throw new InvalidOperationException(
|
||||
$"Field '{name}' used as a parameter of binding composite '{composite}' must be of type 'int' but is of type '{type.Name}' instead");
|
||||
|
||||
////REVIEW: this creates garbage; need a better solution to get to zero garbage during re-resolving
|
||||
// See if we've already assigned a part index. This can happen if there are multiple bindings
|
||||
// for the same named slot on the composite (e.g. multiple "Negative" bindings on an axis composite).
|
||||
var partIndex = (int)field.GetValue(composite);
|
||||
if (partIndex == 0)
|
||||
{
|
||||
// No, not assigned yet. Create new part index.
|
||||
partIndex = ++currentCompositePartCount;
|
||||
field.SetValue(composite, partIndex);
|
||||
}
|
||||
|
||||
return partIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84ed8f0fc9aae9347b3b0a962eeacf32
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 92d6890c6702d4891a156d7d30cdbdcb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,370 @@
|
||||
using System;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
|
||||
namespace UnityEngine.InputSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Information passed to <see cref="IInputInteraction">interactions</see>
|
||||
/// when their associated controls trigger.
|
||||
/// </summary>
|
||||
/// <seealso cref="IInputInteraction.Process"/>
|
||||
public struct InputInteractionContext
|
||||
{
|
||||
/// <summary>
|
||||
/// The action associated with the binding.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the binding is not associated with an action, this is <c>null</c>.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputBinding.action"/>
|
||||
public InputAction action => m_State.GetActionOrNull(ref m_TriggerState);
|
||||
|
||||
/// <summary>
|
||||
/// The bound control that changed its state to trigger the binding associated
|
||||
/// with the interaction.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// In case the binding associated with the interaction is a composite, this is
|
||||
/// one of the controls that are part of the composite.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputBinding.path"/>
|
||||
public InputControl control => m_State.GetControl(ref m_TriggerState);
|
||||
|
||||
/// <summary>
|
||||
/// The phase the interaction is currently in.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Each interaction on a binding has its own phase independent of the action the binding is applied to.
|
||||
/// If an interaction gets to "drive" an action at a particular point in time, its phase will determine
|
||||
/// the phase of the action.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputAction.phase"/>
|
||||
/// <seealso cref="Started"/>
|
||||
/// <seealso cref="Waiting"/>
|
||||
/// <seealso cref="Performed"/>
|
||||
/// <seealso cref="Canceled"/>
|
||||
public InputActionPhase phase => m_TriggerState.phase;
|
||||
|
||||
/// <summary>
|
||||
/// Time stamp of the input event that caused <see cref="control"/> to trigger a change in the
|
||||
/// state of <see cref="action"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="InputEvent.time"/>
|
||||
public double time => m_TriggerState.time;
|
||||
|
||||
/// <summary>
|
||||
/// Timestamp of the <see cref="InputEvent"/> that caused the interaction to transition
|
||||
/// to <see cref="InputActionPhase.Started"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="InputEvent.time"/>
|
||||
public double startTime => m_TriggerState.startTime;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the interaction's <see cref="IInputInteraction.Process"/> method has been called because
|
||||
/// a timer set by <see cref="SetTimeout"/> has expired.
|
||||
/// </summary>
|
||||
/// <seealso cref="SetTimeout"/>
|
||||
public bool timerHasExpired
|
||||
{
|
||||
get => (m_Flags & Flags.TimerHasExpired) != 0;
|
||||
internal set
|
||||
{
|
||||
if (value)
|
||||
m_Flags |= Flags.TimerHasExpired;
|
||||
else
|
||||
m_Flags &= ~Flags.TimerHasExpired;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if the interaction is waiting for input
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default, an interaction will return this this phase after every time it has been performed
|
||||
/// (<see cref="InputActionPhase.Performed"/>). This can be changed by using <see cref="PerformedAndStayStarted"/>
|
||||
/// or <see cref="PerformedAndStayPerformed"/>.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputActionPhase.Waiting"/>
|
||||
public bool isWaiting => phase == InputActionPhase.Waiting;
|
||||
|
||||
/// <summary>
|
||||
/// True if the interaction has been started.
|
||||
/// </summary>
|
||||
/// <seealso cref="InputActionPhase.Started"/>
|
||||
/// <seealso cref="Started"/>
|
||||
public bool isStarted => phase == InputActionPhase.Started;
|
||||
|
||||
/// <summary>
|
||||
/// Compute the current level of control actuation.
|
||||
/// </summary>
|
||||
/// <returns>The current level of control actuation (usually [0..1]) or -1 if the control is actuated
|
||||
/// but does not support computing magnitudes.</returns>
|
||||
/// <seealso cref="ControlIsActuated"/>
|
||||
/// <seealso cref="InputControl.EvaluateMagnitude()"/>
|
||||
public float ComputeMagnitude()
|
||||
{
|
||||
return m_TriggerState.magnitude;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the control that triggered the interaction has been actuated beyond the given threshold.
|
||||
/// </summary>
|
||||
/// <param name="threshold">Threshold that must be reached for the control to be considered actuated. If this is zero,
|
||||
/// the threshold must be exceeded. If it is any positive value, the value must be at least matched.</param>
|
||||
/// <returns>True if the trigger control is actuated.</returns>
|
||||
/// <seealso cref="InputControlExtensions.IsActuated"/>
|
||||
/// <seealso cref="ComputeMagnitude"/>
|
||||
public bool ControlIsActuated(float threshold = 0)
|
||||
{
|
||||
return InputActionState.IsActuated(ref m_TriggerState, threshold);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mark the interaction has having begun.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This affects the current interaction only. There might be multiple interactions on a binding
|
||||
/// and arbitrary many interactions might concurrently be in started state. However, only one interaction
|
||||
/// (usually the one that starts first) is allowed to drive the action's state as a whole. If an interaction
|
||||
/// that is currently driving an action is canceled, however, the next interaction in the list that has
|
||||
/// been started will take over and continue driving the action.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// public class MyInteraction : IInputInteraction<float>
|
||||
/// {
|
||||
/// public void Process(ref IInputInteractionContext context)
|
||||
/// {
|
||||
/// if (context.isWaiting && context.ControlIsActuated())
|
||||
/// {
|
||||
/// // We've waited for input and got it. Start the interaction.
|
||||
/// context.Started();
|
||||
/// }
|
||||
/// else if (context.isStarted && !context.ControlIsActuated())
|
||||
/// {
|
||||
/// // Interaction has been completed.
|
||||
/// context.Performed();
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// public void Reset()
|
||||
/// {
|
||||
/// // No reset code needed. We're not keeping any state locally in the interaction.
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
public void Started()
|
||||
{
|
||||
m_TriggerState.startTime = time;
|
||||
m_State.ChangePhaseOfInteraction(InputActionPhase.Started, ref m_TriggerState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks the interaction as being performed and then transitions back to <see cref="InputActionPhase.Waiting"/>
|
||||
/// to wait for input. This behavior is desirable for interaction events that are instant and reflect
|
||||
/// a transitional interaction pattern such as <see cref="Interactions.PressInteraction"/> or <see cref="Interactions.TapInteraction"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that this affects the current interaction only. There might be multiple interactions on a binding
|
||||
/// and arbitrary many interactions might concurrently be in started state. However, only one interaction
|
||||
/// (usually the one that starts first) is allowed to drive the action's state as a whole. If an interaction
|
||||
/// that is currently driving an action is canceled, however, the next interaction in the list that has
|
||||
/// been started will take over and continue driving the action.
|
||||
/// </remarks>
|
||||
public void Performed()
|
||||
{
|
||||
if (m_TriggerState.phase == InputActionPhase.Waiting)
|
||||
m_TriggerState.startTime = time;
|
||||
m_State.ChangePhaseOfInteraction(InputActionPhase.Performed, ref m_TriggerState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks the interaction as being performed and then transitions into I <see cref="InputActionPhase.Started"/>
|
||||
/// to wait for an initial trigger condition to be true before being performed again. This behavior
|
||||
/// may be desirable for interaction events that reflect transitional interaction patterns but should
|
||||
/// be considered as started until a cancellation condition is true, such as releasing a button.
|
||||
/// </summary>
|
||||
public void PerformedAndStayStarted()
|
||||
{
|
||||
if (m_TriggerState.phase == InputActionPhase.Waiting)
|
||||
m_TriggerState.startTime = time;
|
||||
m_State.ChangePhaseOfInteraction(InputActionPhase.Performed, ref m_TriggerState,
|
||||
phaseAfterPerformed: InputActionPhase.Started);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks the interaction as being performed and then stays in that state waiting for an input to
|
||||
/// cancel the interactions active state. This behavior is desirable for interaction events that
|
||||
/// are active for a duration until a cancellation condition is true, such as <see cref="Interactions.HoldInteraction"/> or <see cref="Interactions.TapInteraction"/> where releasing
|
||||
/// the associated button cancels the interaction..
|
||||
/// </summary>
|
||||
public void PerformedAndStayPerformed()
|
||||
{
|
||||
if (m_TriggerState.phase == InputActionPhase.Waiting)
|
||||
m_TriggerState.startTime = time;
|
||||
m_State.ChangePhaseOfInteraction(InputActionPhase.Performed, ref m_TriggerState,
|
||||
phaseAfterPerformed: InputActionPhase.Performed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks the interaction as being interrupted or aborted. This is relevant to signal that the interaction
|
||||
/// pattern was not completed, for example, the user pressed and then released a button before the minimum
|
||||
/// time required for a <see cref="Interactions.HoldInteraction"/> to complete.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is used by most existing interactions to cancel the transitions in the interaction state machine
|
||||
/// when a condition required to proceed turned false or other indirect requirements were not met, such as
|
||||
/// time-based conditions.
|
||||
/// </remarks>
|
||||
public void Canceled()
|
||||
{
|
||||
if (m_TriggerState.phase != InputActionPhase.Canceled)
|
||||
m_State.ChangePhaseOfInteraction(InputActionPhase.Canceled, ref m_TriggerState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Put the interaction back into <see cref="InputActionPhase.Waiting"/> state.
|
||||
/// </summary>
|
||||
/// <seealso cref="InputAction.phase"/>
|
||||
/// <seealso cref="InputActionPhase"/>
|
||||
/// <seealso cref="Started"/>
|
||||
/// <seealso cref="Performed"/>
|
||||
/// <seealso cref="Canceled"/>
|
||||
public void Waiting()
|
||||
{
|
||||
if (m_TriggerState.phase != InputActionPhase.Waiting)
|
||||
m_State.ChangePhaseOfInteraction(InputActionPhase.Waiting, ref m_TriggerState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start a timeout that triggers within <paramref name="seconds"/>.
|
||||
/// </summary>
|
||||
/// <param name="seconds">Number of seconds before the timeout is triggered.</param>
|
||||
/// <remarks>
|
||||
/// An interaction might wait a set amount of time for something to happen and then
|
||||
/// do something depending on whether it did or did not happen. By calling this method,
|
||||
/// a timeout is installed such that in the input update that the timer expires in, the
|
||||
/// interaction's <see cref="IInputInteraction.Process"/> method is called with <see cref="timerHasExpired"/>
|
||||
/// being true.
|
||||
///
|
||||
/// Changing the phase of the interaction while a timeout is running will implicitly cancel
|
||||
/// the timeout.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Let's say we're writing a Process() method for an interaction that,
|
||||
/// // after a control has been actuated, waits for 1 second for it to be
|
||||
/// // released again. If that happens, the interaction performs. If not,
|
||||
/// // it cancels.
|
||||
/// public void Process(ref InputInteractionContext context)
|
||||
/// {
|
||||
/// // timerHasExpired will be true if we get called when our timeout
|
||||
/// // has expired.
|
||||
/// if (context.timerHasExpired)
|
||||
/// {
|
||||
/// // The user did not release the control quickly enough.
|
||||
/// // Our interaction is not successful, so cancel.
|
||||
/// context.Canceled();
|
||||
/// return;
|
||||
/// }
|
||||
///
|
||||
/// if (context.ControlIsActuated())
|
||||
/// {
|
||||
/// if (!context.isStarted)
|
||||
/// {
|
||||
/// // The control has been actuated. We want to give the user a max
|
||||
/// // of 1 second to release it. So we start the interaction now and then
|
||||
/// // set the timeout.
|
||||
/// context.Started();
|
||||
/// context.SetTimeout(1);
|
||||
/// }
|
||||
/// }
|
||||
/// else
|
||||
/// {
|
||||
/// // Control has been released. If we're currently waiting for a release,
|
||||
/// // it has come in time before out timeout expired. In other words, the
|
||||
/// // interaction has been successfully performed. We call Performed()
|
||||
/// // which implicitly removes our ongoing timeout.
|
||||
/// if (context.isStarted)
|
||||
/// context.Performed();
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="timerHasExpired"/>
|
||||
public void SetTimeout(float seconds)
|
||||
{
|
||||
m_State.StartTimeout(seconds, ref m_TriggerState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override the default timeout value used by <see cref="InputAction.GetTimeoutCompletionPercentage"/>.
|
||||
/// </summary>
|
||||
/// <param name="seconds">Amount of total successive timeouts TODO</param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
/// <remarks>
|
||||
/// By default, timeout completion will be entirely determine by the timeout that is currently
|
||||
/// running, if any. However, some interactions (such as <see cref="Interactions.MultiTapInteraction"/>)
|
||||
/// will have to run multiple timeouts in succession. Thus, completion of a single timeout is not
|
||||
/// the same as completion of the interaction.
|
||||
///
|
||||
/// You can use this method to account for this.
|
||||
///
|
||||
/// Whenever a timeout completes, the timeout duration will automatically be accumulated towards
|
||||
/// the total timeout completion time.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Let's say we're starting our first timeout and we know that we will run three timeouts
|
||||
/// // in succession of 2 seconds each. By calling SetTotalTimeoutCompletionTime(), we can account for this.
|
||||
/// SetTotalTimeoutCompletionTime(3 * 2);
|
||||
///
|
||||
/// // Start the first timeout. When this timeout expires, it will automatically
|
||||
/// // count one second towards the total timeout completion time.
|
||||
/// SetTimeout(2);
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputAction.GetTimeoutCompletionPercentage"/>
|
||||
public void SetTotalTimeoutCompletionTime(float seconds)
|
||||
{
|
||||
if (seconds <= 0)
|
||||
throw new ArgumentException("Seconds must be a positive value", nameof(seconds));
|
||||
|
||||
m_State.SetTotalTimeoutCompletionTime(seconds, ref m_TriggerState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the value of the binding that triggered processing of the interaction.
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">Type of value to read from the binding. Must match the value type of the control
|
||||
/// or composite in effect for the binding.</typeparam>
|
||||
/// <returns>Value read from the binding.</returns>
|
||||
public TValue ReadValue<TValue>()
|
||||
where TValue : struct
|
||||
{
|
||||
return m_State.ReadValue<TValue>(m_TriggerState.bindingIndex, m_TriggerState.controlIndex);
|
||||
}
|
||||
|
||||
internal InputActionState m_State;
|
||||
internal Flags m_Flags;
|
||||
internal InputActionState.TriggerState m_TriggerState;
|
||||
|
||||
internal int mapIndex => m_TriggerState.mapIndex;
|
||||
|
||||
internal int controlIndex => m_TriggerState.controlIndex;
|
||||
|
||||
internal int bindingIndex => m_TriggerState.bindingIndex;
|
||||
|
||||
internal int interactionIndex => m_TriggerState.interactionIndex;
|
||||
|
||||
[Flags]
|
||||
internal enum Flags
|
||||
{
|
||||
TimerHasExpired = 1 << 1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 12826de7f47b478428a9941b5fb5adcf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b90e4423894dce34f83bb15a3ba411ba
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,147 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using UnityEngine.InputSystem.Controls;
|
||||
using UnityEngine.Scripting;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEngine.InputSystem.Editor;
|
||||
using UnityEngine.UIElements;
|
||||
#endif
|
||||
|
||||
namespace UnityEngine.InputSystem.Interactions
|
||||
{
|
||||
/// <summary>
|
||||
/// Performs the action if the control is pressed and held for at least the
|
||||
/// set duration (which defaults to <see cref="InputSettings.defaultHoldTime"/>).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The action is started when the control is pressed. If the control is released before the
|
||||
/// set <see cref="duration"/>, the action is canceled. As soon as the hold time is reached,
|
||||
/// the action performs. The action then stays performed until the control is released, at
|
||||
/// which point the action cancels.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Action that requires A button on gamepad to be held for half a second.
|
||||
/// var action = new InputAction(binding: "<Gamepad>/buttonSouth", interactions: "hold(duration=0.5)");
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
[DisplayName("Hold")]
|
||||
public class HoldInteraction : IInputInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// Duration in seconds that the control must be pressed for the hold to register.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this is less than or equal to 0 (the default), <see cref="InputSettings.defaultHoldTime"/> is used.
|
||||
///
|
||||
/// Duration is expressed in real time and measured against the timestamps of input events
|
||||
/// (<see cref="LowLevel.InputEvent.time"/>) not against game time (<see cref="Time.time"/>).
|
||||
/// </remarks>
|
||||
public float duration;
|
||||
|
||||
/// <summary>
|
||||
/// Magnitude threshold that must be crossed by an actuated control for the control to
|
||||
/// be considered pressed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this is less than or equal to 0 (the default), <see cref="InputSettings.defaultButtonPressPoint"/> is used instead.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputControl.EvaluateMagnitude()"/>
|
||||
public float pressPoint;
|
||||
|
||||
private float durationOrDefault => duration > 0.0 ? duration : InputSystem.settings.defaultHoldTime;
|
||||
private float pressPointOrDefault => pressPoint > 0.0 ? pressPoint : ButtonControl.s_GlobalDefaultButtonPressPoint;
|
||||
|
||||
private double m_TimePressed;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Process(ref InputInteractionContext context)
|
||||
{
|
||||
if (context.timerHasExpired)
|
||||
{
|
||||
context.PerformedAndStayPerformed();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (context.phase)
|
||||
{
|
||||
case InputActionPhase.Waiting:
|
||||
if (context.ControlIsActuated(pressPointOrDefault))
|
||||
{
|
||||
m_TimePressed = context.time;
|
||||
|
||||
context.Started();
|
||||
context.SetTimeout(durationOrDefault);
|
||||
}
|
||||
break;
|
||||
|
||||
case InputActionPhase.Started:
|
||||
// If we've reached our hold time threshold, perform the hold.
|
||||
// We do this regardless of what state the control changed to.
|
||||
if (context.time - m_TimePressed >= durationOrDefault)
|
||||
{
|
||||
context.PerformedAndStayPerformed();
|
||||
}
|
||||
if (!context.ControlIsActuated())
|
||||
{
|
||||
// Control is no longer actuated so we're done.
|
||||
context.Canceled();
|
||||
}
|
||||
break;
|
||||
|
||||
case InputActionPhase.Performed:
|
||||
if (!context.ControlIsActuated(pressPointOrDefault))
|
||||
context.Canceled();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Reset()
|
||||
{
|
||||
m_TimePressed = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// UI that is displayed when editing <see cref="HoldInteraction"/> in the editor.
|
||||
/// </summary>
|
||||
internal class HoldInteractionEditor : InputParameterEditor<HoldInteraction>
|
||||
{
|
||||
protected override void OnEnable()
|
||||
{
|
||||
m_PressPointSetting.Initialize("Press Point",
|
||||
"Float value that an axis control has to cross for it to be considered pressed.",
|
||||
"Default Button Press Point",
|
||||
() => target.pressPoint, v => target.pressPoint = v, () => ButtonControl.s_GlobalDefaultButtonPressPoint);
|
||||
m_DurationSetting.Initialize("Hold Time",
|
||||
"Time (in seconds) that a control has to be held in order for it to register as a hold.",
|
||||
"Default Hold Time",
|
||||
() => target.duration, x => target.duration = x, () => InputSystem.settings.defaultHoldTime);
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
|
||||
if (!InputSystem.settings.useIMGUIEditorForAssets) return;
|
||||
#endif
|
||||
m_PressPointSetting.OnGUI();
|
||||
m_DurationSetting.OnGUI();
|
||||
}
|
||||
|
||||
#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
|
||||
public override void OnDrawVisualElements(VisualElement root, Action onChangedCallback)
|
||||
{
|
||||
m_PressPointSetting.OnDrawVisualElements(root, onChangedCallback);
|
||||
m_DurationSetting.OnDrawVisualElements(root, onChangedCallback);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
private CustomOrDefaultSetting m_PressPointSetting;
|
||||
private CustomOrDefaultSetting m_DurationSetting;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6bd8ebd167bb9d34cabf0b443f2d1aaa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,237 @@
|
||||
using System;
|
||||
using UnityEngine.InputSystem.Controls;
|
||||
using UnityEngine.Scripting;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEngine.InputSystem.Editor;
|
||||
using UnityEngine.UIElements;
|
||||
using UnityEditor.UIElements;
|
||||
#endif
|
||||
|
||||
////TODO: add ability to respond to any of the taps in the sequence (e.g. one response for single tap, another for double tap)
|
||||
|
||||
////TODO: add ability to perform on final press rather than on release
|
||||
|
||||
////TODO: change this so that the interaction stays performed when the tap count is reached until the button is released
|
||||
|
||||
namespace UnityEngine.InputSystem.Interactions
|
||||
{
|
||||
////REVIEW: Why is this deriving from IInputInteraction<float>? It goes by actuation just like Hold etc.
|
||||
/// <summary>
|
||||
/// Interaction that requires multiple taps (press and release within <see cref="tapTime"/>) spaced no more
|
||||
/// than <see cref="tapDelay"/> seconds apart. This equates to a chain of <see cref="TapInteraction"/> with
|
||||
/// a maximum delay between each tap.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The interaction goes into <see cref="InputActionPhase.Started"/> on the first press and then will not
|
||||
/// trigger again until either the full tap sequence is performed (in which case the interaction triggers
|
||||
/// <see cref="InputActionPhase.Performed"/>) or the multi-tap is aborted by a timeout being hit (in which
|
||||
/// case the interaction will trigger <see cref="InputActionPhase.Canceled"/>).
|
||||
/// </remarks>
|
||||
public class MultiTapInteraction : IInputInteraction<float>
|
||||
{
|
||||
/// <summary>
|
||||
/// The time in seconds within which the control needs to be pressed and released to perform the interaction.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this value is equal to or smaller than zero, the input system will use (<see cref="InputSettings.defaultTapTime"/>) instead.
|
||||
/// </remarks>
|
||||
[Tooltip("The maximum time (in seconds) allowed to elapse between pressing and releasing a control for it to register as a tap.")]
|
||||
public float tapTime;
|
||||
|
||||
/// <summary>
|
||||
/// The time in seconds which is allowed to pass between taps.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this time is exceeded, the multi-tap interaction is canceled.
|
||||
/// If this value is equal to or smaller than zero, the input system will use the duplicate value of <see cref="tapTime"/> instead.
|
||||
/// </remarks>
|
||||
[Tooltip("The maximum delay (in seconds) allowed between each tap. If this time is exceeded, the multi-tap is canceled.")]
|
||||
public float tapDelay;
|
||||
|
||||
/// <summary>
|
||||
/// The number of taps required to perform the interaction.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// How many taps need to be performed in succession. Two means double-tap, three means triple-tap, and so on.
|
||||
/// </remarks>
|
||||
[Tooltip("How many taps need to be performed in succession. Two means double-tap, three means triple-tap, and so on.")]
|
||||
public int tapCount = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Magnitude threshold that must be crossed by an actuated control for the control to
|
||||
/// be considered pressed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this is less than or equal to 0 (the default), <see cref="InputSettings.defaultButtonPressPoint"/> is used instead.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputControl.EvaluateMagnitude()"/>
|
||||
public float pressPoint;
|
||||
|
||||
private float tapTimeOrDefault => tapTime > 0.0 ? tapTime : InputSystem.settings.defaultTapTime;
|
||||
internal float tapDelayOrDefault => tapDelay > 0.0 ? tapDelay : InputSystem.settings.multiTapDelayTime;
|
||||
private float pressPointOrDefault => pressPoint > 0 ? pressPoint : ButtonControl.s_GlobalDefaultButtonPressPoint;
|
||||
private float releasePointOrDefault => pressPointOrDefault * ButtonControl.s_GlobalDefaultButtonReleaseThreshold;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Process(ref InputInteractionContext context)
|
||||
{
|
||||
if (context.timerHasExpired)
|
||||
{
|
||||
// We use timers multiple times but no matter what, if they expire it means
|
||||
// that we didn't get input in time.
|
||||
context.Canceled();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (m_CurrentTapPhase)
|
||||
{
|
||||
case TapPhase.None:
|
||||
if (context.ControlIsActuated(pressPointOrDefault))
|
||||
{
|
||||
m_CurrentTapPhase = TapPhase.WaitingForNextRelease;
|
||||
m_CurrentTapStartTime = context.time;
|
||||
context.Started();
|
||||
|
||||
var maxTapTime = tapTimeOrDefault;
|
||||
var maxDelayInBetween = tapDelayOrDefault;
|
||||
context.SetTimeout(maxTapTime);
|
||||
|
||||
// We'll be using multiple timeouts so set a total completion time that
|
||||
// effects the result of InputAction.GetTimeoutCompletionPercentage()
|
||||
// such that it accounts for the total time we allocate for the interaction
|
||||
// rather than only the time of one single timeout.
|
||||
context.SetTotalTimeoutCompletionTime(maxTapTime * tapCount + (tapCount - 1) * maxDelayInBetween);
|
||||
}
|
||||
break;
|
||||
|
||||
case TapPhase.WaitingForNextRelease:
|
||||
if (!context.ControlIsActuated(releasePointOrDefault))
|
||||
{
|
||||
if (context.time - m_CurrentTapStartTime <= tapTimeOrDefault)
|
||||
{
|
||||
++m_CurrentTapCount;
|
||||
if (m_CurrentTapCount >= tapCount)
|
||||
{
|
||||
context.Performed();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_CurrentTapPhase = TapPhase.WaitingForNextPress;
|
||||
m_LastTapReleaseTime = context.time;
|
||||
context.SetTimeout(tapDelayOrDefault);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Canceled();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case TapPhase.WaitingForNextPress:
|
||||
if (context.ControlIsActuated(pressPointOrDefault))
|
||||
{
|
||||
if (context.time - m_LastTapReleaseTime <= tapDelayOrDefault)
|
||||
{
|
||||
m_CurrentTapPhase = TapPhase.WaitingForNextRelease;
|
||||
m_CurrentTapStartTime = context.time;
|
||||
context.SetTimeout(tapTimeOrDefault);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Canceled();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Reset()
|
||||
{
|
||||
m_CurrentTapPhase = TapPhase.None;
|
||||
m_CurrentTapCount = 0;
|
||||
m_CurrentTapStartTime = 0;
|
||||
m_LastTapReleaseTime = 0;
|
||||
}
|
||||
|
||||
private TapPhase m_CurrentTapPhase;
|
||||
private int m_CurrentTapCount;
|
||||
private double m_CurrentTapStartTime;
|
||||
private double m_LastTapReleaseTime;
|
||||
|
||||
private enum TapPhase
|
||||
{
|
||||
None,
|
||||
WaitingForNextRelease,
|
||||
WaitingForNextPress,
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// UI that is displayed when editing <see cref="HoldInteraction"/> in the editor.
|
||||
/// </summary>
|
||||
internal class MultiTapInteractionEditor : InputParameterEditor<MultiTapInteraction>
|
||||
{
|
||||
protected override void OnEnable()
|
||||
{
|
||||
m_TapTimeSetting.Initialize("Max Tap Duration",
|
||||
"Time (in seconds) within with a control has to be released again for it to register as a tap. If the control is held "
|
||||
+ "for longer than this time, the tap is canceled.",
|
||||
"Default Tap Time",
|
||||
() => target.tapTime, x => target.tapTime = x, () => InputSystem.settings.defaultTapTime);
|
||||
m_TapDelaySetting.Initialize("Max Tap Spacing",
|
||||
"The maximum delay (in seconds) allowed between each tap. If this time is exceeded, the multi-tap is canceled.",
|
||||
"Default Tap Spacing",
|
||||
() => target.tapDelay, x => target.tapDelay = x, () => InputSystem.settings.multiTapDelayTime);
|
||||
m_PressPointSetting.Initialize("Press Point",
|
||||
"The amount of actuation a control requires before being considered pressed. If not set, default to "
|
||||
+ "'Default Button Press Point' in the global input settings.",
|
||||
"Default Button Press Point",
|
||||
() => target.pressPoint, v => target.pressPoint = v,
|
||||
() => InputSystem.settings.defaultButtonPressPoint);
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
|
||||
if (!InputSystem.settings.useIMGUIEditorForAssets) return;
|
||||
#endif
|
||||
target.tapCount = EditorGUILayout.IntField(m_TapCountLabel, target.tapCount);
|
||||
m_TapDelaySetting.OnGUI();
|
||||
m_TapTimeSetting.OnGUI();
|
||||
m_PressPointSetting.OnGUI();
|
||||
}
|
||||
|
||||
#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
|
||||
public override void OnDrawVisualElements(VisualElement root, Action onChangedCallback)
|
||||
{
|
||||
var tapCountField = new IntegerField(m_TapCountLabel.text)
|
||||
{
|
||||
value = target.tapCount,
|
||||
tooltip = m_TapCountLabel.tooltip
|
||||
};
|
||||
tapCountField.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
target.tapCount = evt.newValue;
|
||||
onChangedCallback?.Invoke();
|
||||
});
|
||||
root.Add(tapCountField);
|
||||
|
||||
m_TapDelaySetting.OnDrawVisualElements(root, onChangedCallback);
|
||||
m_TapTimeSetting.OnDrawVisualElements(root, onChangedCallback);
|
||||
m_PressPointSetting.OnDrawVisualElements(root, onChangedCallback);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
private readonly GUIContent m_TapCountLabel = new GUIContent("Tap Count", "How many taps need to be performed in succession. Two means double-tap, three means triple-tap, and so on.");
|
||||
|
||||
private CustomOrDefaultSetting m_PressPointSetting;
|
||||
private CustomOrDefaultSetting m_TapTimeSetting;
|
||||
private CustomOrDefaultSetting m_TapDelaySetting;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c2732a66825034f739cb072c49aa8990
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,257 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using UnityEngine.InputSystem.Controls;
|
||||
using UnityEngine.Scripting;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEngine.InputSystem.Editor;
|
||||
using UnityEngine.UIElements;
|
||||
using UnityEditor.UIElements;
|
||||
#endif
|
||||
|
||||
////TODO: protect against the control *hovering* around the press point; this should not fire the press repeatedly; probably need a zone around the press point
|
||||
////TODO: also, for analog controls, we probably want a deadzone that gives just a tiny little buffer at the low end before the action starts
|
||||
|
||||
////REVIEW: shouldn't it use Canceled for release on PressAndRelease instead of triggering Performed again?
|
||||
|
||||
namespace UnityEngine.InputSystem.Interactions
|
||||
{
|
||||
/// <summary>
|
||||
/// Performs the action at specific points in a button press-and-release sequence according top <see cref="behavior"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default, uses <see cref="PressBehavior.PressOnly"/> which performs the action as soon as the control crosses the
|
||||
/// button press threshold defined by <see cref="pressPoint"/>. The action then will not trigger again until the control
|
||||
/// is first released.
|
||||
///
|
||||
/// Can be set to instead trigger on release (that is, when the control goes back below the button press threshold) using
|
||||
/// <see cref="PressBehavior.ReleaseOnly"/> or can be set to trigger on both press and release using <see cref="PressBehavior.PressAndRelease"/>).
|
||||
///
|
||||
/// Note that using an explicit press interaction is only necessary if the goal is to either customize the press behavior
|
||||
/// of a button or when binding to controls that are not buttons as such (the press interaction compares magnitudes to
|
||||
/// <see cref="pressPoint"/> and thus any type of control that can deliver a magnitude can act as a button). The default
|
||||
/// behavior available out of the box when binding <see cref="InputActionType.Button"/> type actions to button-type controls
|
||||
/// (<see cref="UnityEngine.InputSystem.Controls.ButtonControl"/>) corresponds to using a press modifier with <see cref="behavior"/>
|
||||
/// set to <see cref="PressBehavior.PressOnly"/> and <see cref="pressPoint"/> left at default.
|
||||
/// </remarks>
|
||||
[DisplayName("Press")]
|
||||
public class PressInteraction : IInputInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// Amount of actuation required before a control is considered pressed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If zero (default), defaults to <see cref="InputSettings.defaultButtonPressPoint"/>.
|
||||
/// </remarks>
|
||||
[Tooltip("The amount of actuation a control requires before being considered pressed. If not set, default to "
|
||||
+ "'Default Press Point' in the global input settings.")]
|
||||
public float pressPoint;
|
||||
|
||||
////REVIEW: this should really be named "pressBehavior"
|
||||
/// <summary>
|
||||
/// Determines how button presses trigger the action.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default (PressOnly), the action is performed on press.
|
||||
/// With ReleaseOnly, the action is performed on release. With PressAndRelease, the action is
|
||||
/// performed on press and on release.
|
||||
/// </remarks>
|
||||
[Tooltip("Determines how button presses trigger the action. By default (PressOnly), the action is performed on press. "
|
||||
+ "With ReleaseOnly, the action is performed on release. With PressAndRelease, the action is performed on press and release.")]
|
||||
public PressBehavior behavior;
|
||||
|
||||
private float pressPointOrDefault => pressPoint > 0 ? pressPoint : ButtonControl.s_GlobalDefaultButtonPressPoint;
|
||||
private float releasePointOrDefault => pressPointOrDefault * ButtonControl.s_GlobalDefaultButtonReleaseThreshold;
|
||||
private bool m_WaitingForRelease;
|
||||
|
||||
public void Process(ref InputInteractionContext context)
|
||||
{
|
||||
var actuation = context.ComputeMagnitude();
|
||||
switch (behavior)
|
||||
{
|
||||
case PressBehavior.PressOnly:
|
||||
if (m_WaitingForRelease)
|
||||
{
|
||||
if (actuation <= releasePointOrDefault)
|
||||
{
|
||||
m_WaitingForRelease = false;
|
||||
if (Mathf.Approximately(0f, actuation))
|
||||
context.Canceled();
|
||||
else
|
||||
context.Started();
|
||||
}
|
||||
}
|
||||
else if (actuation >= pressPointOrDefault)
|
||||
{
|
||||
m_WaitingForRelease = true;
|
||||
// Stay performed until release.
|
||||
context.PerformedAndStayPerformed();
|
||||
}
|
||||
else if (actuation > 0 && !context.isStarted)
|
||||
{
|
||||
context.Started();
|
||||
}
|
||||
else if (Mathf.Approximately(0f, actuation) && context.isStarted)
|
||||
{
|
||||
context.Canceled();
|
||||
}
|
||||
break;
|
||||
|
||||
case PressBehavior.ReleaseOnly:
|
||||
if (m_WaitingForRelease)
|
||||
{
|
||||
if (actuation <= releasePointOrDefault)
|
||||
{
|
||||
m_WaitingForRelease = false;
|
||||
context.Performed();
|
||||
context.Canceled();
|
||||
}
|
||||
}
|
||||
else if (actuation >= pressPointOrDefault)
|
||||
{
|
||||
m_WaitingForRelease = true;
|
||||
if (!context.isStarted)
|
||||
context.Started();
|
||||
}
|
||||
else
|
||||
{
|
||||
var started = context.isStarted;
|
||||
if (actuation > 0 && !started)
|
||||
context.Started();
|
||||
else if (Mathf.Approximately(0, actuation) && started)
|
||||
context.Canceled();
|
||||
}
|
||||
break;
|
||||
|
||||
case PressBehavior.PressAndRelease:
|
||||
if (m_WaitingForRelease)
|
||||
{
|
||||
if (actuation <= releasePointOrDefault)
|
||||
{
|
||||
m_WaitingForRelease = false;
|
||||
context.Performed();
|
||||
if (Mathf.Approximately(0, actuation))
|
||||
context.Canceled();
|
||||
}
|
||||
}
|
||||
else if (actuation >= pressPointOrDefault)
|
||||
{
|
||||
m_WaitingForRelease = true;
|
||||
context.PerformedAndStayPerformed();
|
||||
}
|
||||
else
|
||||
{
|
||||
var started = context.isStarted;
|
||||
if (actuation > 0 && !started)
|
||||
context.Started();
|
||||
else if (Mathf.Approximately(0, actuation) && started)
|
||||
context.Canceled();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
m_WaitingForRelease = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines how to trigger an action based on button presses.
|
||||
/// </summary>
|
||||
/// <seealso cref="PressInteraction.behavior"/>
|
||||
public enum PressBehavior
|
||||
{
|
||||
/// <summary>
|
||||
/// Perform the action when the button is pressed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Triggers <see cref="InputAction.performed"/> when a control crosses the button press threshold.
|
||||
/// </remarks>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
PressOnly = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Perform the action when the button is released.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Triggers <see cref="InputAction.started"/> when a control crosses the button press threshold and
|
||||
/// <see cref="InputAction.performed"/> when the control goes back below the button press threshold.
|
||||
/// </remarks>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
ReleaseOnly = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Perform the action when the button is pressed and when the button is released.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Triggers <see cref="InputAction.performed"/> when a control crosses the button press threshold
|
||||
/// and triggers <see cref="InputAction.performed"/> again when it goes back below the button press
|
||||
/// threshold.
|
||||
/// </remarks>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
PressAndRelease = 2,
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// UI that is displayed when editing <see cref="PressInteraction"/> in the editor.
|
||||
/// </summary>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
internal class PressInteractionEditor : InputParameterEditor<PressInteraction>
|
||||
{
|
||||
protected override void OnEnable()
|
||||
{
|
||||
m_PressPointSetting.Initialize("Press Point",
|
||||
"The amount of actuation a control requires before being considered pressed. If not set, default to "
|
||||
+ "'Default Button Press Point' in the global input settings.",
|
||||
"Default Button Press Point",
|
||||
() => target.pressPoint, v => target.pressPoint = v,
|
||||
() => InputSystem.settings.defaultButtonPressPoint);
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
|
||||
if (!InputSystem.settings.useIMGUIEditorForAssets) return;
|
||||
#endif
|
||||
EditorGUILayout.HelpBox(s_HelpBoxText);
|
||||
target.behavior = (PressBehavior)EditorGUILayout.EnumPopup(s_PressBehaviorLabel, target.behavior);
|
||||
m_PressPointSetting.OnGUI();
|
||||
}
|
||||
|
||||
#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
|
||||
public override void OnDrawVisualElements(VisualElement root, Action onChangedCallback)
|
||||
{
|
||||
root.Add(new HelpBox(s_HelpBoxText.text, HelpBoxMessageType.None));
|
||||
|
||||
var behaviourDropdown = new EnumField(s_PressBehaviorLabel.text, target.behavior)
|
||||
{
|
||||
tooltip = s_PressBehaviorLabel.tooltip
|
||||
};
|
||||
behaviourDropdown.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
target.behavior = (PressBehavior)evt.newValue;
|
||||
onChangedCallback?.Invoke();
|
||||
});
|
||||
root.Add(behaviourDropdown);
|
||||
|
||||
m_PressPointSetting.OnDrawVisualElements(root, onChangedCallback);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
private CustomOrDefaultSetting m_PressPointSetting;
|
||||
|
||||
private static readonly GUIContent s_HelpBoxText = EditorGUIUtility.TrTextContent("Note that the 'Press' interaction is only "
|
||||
+ "necessary when wanting to customize button press behavior. For default press behavior, simply set the action type to 'Button' "
|
||||
+ "and use the action without interactions added to it.");
|
||||
|
||||
private static readonly GUIContent s_PressBehaviorLabel = EditorGUIUtility.TrTextContent("Trigger Behavior",
|
||||
"Determines how button presses trigger the action. By default (PressOnly), the action is performed on press. "
|
||||
+ "With ReleaseOnly, the action is performed on release. With PressAndRelease, the action is performed on press and "
|
||||
+ "canceled on release.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 802caa2f96f49470facf48de1e652f16
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using UnityEngine.InputSystem.Controls;
|
||||
using UnityEngine.Scripting;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEngine.InputSystem.Editor;
|
||||
using UnityEngine.UIElements;
|
||||
#endif
|
||||
|
||||
////REVIEW: this is confusing when considered next to HoldInteraction; also it's confusingly named
|
||||
|
||||
namespace UnityEngine.InputSystem.Interactions
|
||||
{
|
||||
/// <summary>
|
||||
/// Performs the action if the control is pressed and held for at least the set
|
||||
/// duration (which defaults to <see cref="InputSettings.defaultSlowTapTime"/>)
|
||||
/// and then released.
|
||||
/// </summary>
|
||||
[DisplayName("Long Tap")]
|
||||
public class SlowTapInteraction : IInputInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// The time in seconds within which the control needs to be pressed and released to perform the interaction.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this value is equal to or smaller than zero, the input system will use (<see cref="InputSettings.defaultSlowTapTime"/>) instead.
|
||||
/// </remarks>
|
||||
public float duration;
|
||||
|
||||
/// <summary>
|
||||
/// The press point required to perform the interaction.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For analog controls (such as trigger axes on a gamepad), the control needs to be engaged by at least this
|
||||
/// value to perform the interaction.
|
||||
/// If this value is equal to or smaller than zero, the input system will use (<see cref="InputSettings.defaultButtonPressPoint"/>) instead.
|
||||
/// </remarks>
|
||||
public float pressPoint;
|
||||
|
||||
////REVIEW: this seems stupid; shouldn't a slow tap just be anything that takes longer than TapTime?
|
||||
private float durationOrDefault => duration > 0.0f ? duration : InputSystem.settings.defaultSlowTapTime;
|
||||
private float pressPointOrDefault => pressPoint > 0 ? pressPoint : ButtonControl.s_GlobalDefaultButtonPressPoint;
|
||||
|
||||
private double m_SlowTapStartTime;
|
||||
|
||||
public void Process(ref InputInteractionContext context)
|
||||
{
|
||||
if (context.isWaiting && context.ControlIsActuated(pressPointOrDefault))
|
||||
{
|
||||
m_SlowTapStartTime = context.time;
|
||||
context.Started();
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.isStarted && !context.ControlIsActuated(pressPointOrDefault))
|
||||
{
|
||||
if (context.time - m_SlowTapStartTime >= durationOrDefault)
|
||||
context.Performed();
|
||||
else
|
||||
////REVIEW: does it matter to cancel right after expiration of 'duration' or is it enough to cancel on button up like here?
|
||||
context.Canceled();
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
m_SlowTapStartTime = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
internal class SlowTapInteractionEditor : InputParameterEditor<SlowTapInteraction>
|
||||
{
|
||||
protected override void OnEnable()
|
||||
{
|
||||
m_DurationSetting.Initialize("Min Tap Duration",
|
||||
"Minimum time (in seconds) that a control has to be held for it to register as a slow tap. If the control is released "
|
||||
+ "before this time, the slow tap is canceled.",
|
||||
"Default Slow Tap Time",
|
||||
() => target.duration, x => target.duration = x, () => InputSystem.settings.defaultSlowTapTime);
|
||||
m_PressPointSetting.Initialize("Press Point",
|
||||
"The amount of actuation a control requires before being considered pressed. If not set, default to "
|
||||
+ "'Default Button Press Point' in the global input settings.",
|
||||
"Default Button Press Point",
|
||||
() => target.pressPoint, v => target.pressPoint = v,
|
||||
() => InputSystem.settings.defaultButtonPressPoint);
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
|
||||
if (!InputSystem.settings.useIMGUIEditorForAssets) return;
|
||||
#endif
|
||||
m_DurationSetting.OnGUI();
|
||||
m_PressPointSetting.OnGUI();
|
||||
}
|
||||
|
||||
#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
|
||||
public override void OnDrawVisualElements(VisualElement root, Action onChangedCallback)
|
||||
{
|
||||
m_DurationSetting.OnDrawVisualElements(root, onChangedCallback);
|
||||
m_PressPointSetting.OnDrawVisualElements(root, onChangedCallback);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
private CustomOrDefaultSetting m_DurationSetting;
|
||||
private CustomOrDefaultSetting m_PressPointSetting;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3afbef7bebf197d43b2352b971a4e15f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using UnityEngine.InputSystem.Controls;
|
||||
using UnityEngine.Scripting;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEngine.InputSystem.Editor;
|
||||
using UnityEngine.UIElements;
|
||||
#endif
|
||||
|
||||
namespace UnityEngine.InputSystem.Interactions
|
||||
{
|
||||
/// <summary>
|
||||
/// Performs the action if the control is pressed held for at least the set
|
||||
/// duration (which defaults to <see cref="InputSettings.defaultTapTime"/>)
|
||||
/// and then released.
|
||||
/// </summary>
|
||||
[DisplayName("Tap")]
|
||||
public class TapInteraction : IInputInteraction
|
||||
{
|
||||
////REVIEW: this should be called tapTime
|
||||
/// <summary>
|
||||
/// The time in seconds within which the control needs to be pressed and released to perform the interaction.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this value is equal to or smaller than zero, the input system will use (<see cref="InputSettings.defaultTapTime"/>) instead.
|
||||
/// </remarks>
|
||||
public float duration;
|
||||
|
||||
/// <summary>
|
||||
/// The press point required to perform the interaction.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For analog controls (such as trigger axes on a gamepad), the control needs to be engaged by at least this
|
||||
/// value to perform the interaction.
|
||||
/// If this value is equal to or smaller than zero, the input system will use (<see cref="InputSettings.defaultButtonPressPoint"/>) instead.
|
||||
/// </remarks>
|
||||
public float pressPoint;
|
||||
|
||||
private float durationOrDefault => duration > 0.0 ? duration : InputSystem.settings.defaultTapTime;
|
||||
private float pressPointOrDefault => pressPoint > 0 ? pressPoint : ButtonControl.s_GlobalDefaultButtonPressPoint;
|
||||
private float releasePointOrDefault => pressPointOrDefault * ButtonControl.s_GlobalDefaultButtonReleaseThreshold;
|
||||
|
||||
private double m_TapStartTime;
|
||||
|
||||
////TODO: make sure 2d doesn't move too far
|
||||
|
||||
public void Process(ref InputInteractionContext context)
|
||||
{
|
||||
if (context.timerHasExpired)
|
||||
{
|
||||
context.Canceled();
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.isWaiting && context.ControlIsActuated(pressPointOrDefault))
|
||||
{
|
||||
m_TapStartTime = context.time;
|
||||
// Set timeout slightly after duration so that if tap comes in exactly at the expiration
|
||||
// time, it still counts as a valid tap.
|
||||
context.Started();
|
||||
context.SetTimeout(durationOrDefault + 0.00001f);
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.isStarted && !context.ControlIsActuated(releasePointOrDefault))
|
||||
{
|
||||
if (context.time - m_TapStartTime <= durationOrDefault)
|
||||
{
|
||||
context.Performed();
|
||||
}
|
||||
else
|
||||
{
|
||||
////REVIEW: does it matter to cancel right after expiration of 'duration' or is it enough to cancel on button up like here?
|
||||
context.Canceled();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
m_TapStartTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
internal class TapInteractionEditor : InputParameterEditor<TapInteraction>
|
||||
{
|
||||
protected override void OnEnable()
|
||||
{
|
||||
m_DurationSetting.Initialize("Max Tap Duration",
|
||||
"Time (in seconds) within with a control has to be released again for it to register as a tap. If the control is held "
|
||||
+ "for longer than this time, the tap is canceled.",
|
||||
"Default Tap Time",
|
||||
() => target.duration, x => target.duration = x, () => InputSystem.settings.defaultTapTime);
|
||||
m_PressPointSetting.Initialize("Press Point",
|
||||
"The amount of actuation a control requires before being considered pressed. If not set, default to "
|
||||
+ "'Default Button Press Point' in the global input settings.",
|
||||
"Default Button Press Point",
|
||||
() => target.pressPoint, v => target.pressPoint = v,
|
||||
() => InputSystem.settings.defaultButtonPressPoint);
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
|
||||
if (!InputSystem.settings.useIMGUIEditorForAssets) return;
|
||||
#endif
|
||||
m_DurationSetting.OnGUI();
|
||||
m_PressPointSetting.OnGUI();
|
||||
}
|
||||
|
||||
#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
|
||||
public override void OnDrawVisualElements(VisualElement root, Action onChangedCallback)
|
||||
{
|
||||
m_DurationSetting.OnDrawVisualElements(root, onChangedCallback);
|
||||
m_PressPointSetting.OnDrawVisualElements(root, onChangedCallback);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
private CustomOrDefaultSetting m_DurationSetting;
|
||||
private CustomOrDefaultSetting m_PressPointSetting;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: caa587f0fb5099040bf6193f93daec5d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
[assembly: AssemblyVersion(InputSystem.kAssemblyVersion)]
|
||||
[assembly: InternalsVisibleTo("Unity.InputSystem.TestFramework")]
|
||||
[assembly: InternalsVisibleTo("Unity.InputSystem.Tests.Editor")]
|
||||
[assembly: InternalsVisibleTo("Unity.InputSystem.Tests")]
|
||||
[assembly: InternalsVisibleTo("Unity.InputSystem.IntegrationTests")]
|
||||
[assembly: InternalsVisibleTo("Unity.InputSystem.ForUI")] // To avoid minor bump
|
||||
|
||||
namespace UnityEngine.InputSystem
|
||||
{
|
||||
public static partial class InputSystem
|
||||
{
|
||||
// Keep this in sync with "Packages/com.unity.inputsystem/package.json".
|
||||
// NOTE: Unfortunately, System.Version doesn't use semantic versioning so we can't include
|
||||
// "-preview" suffixes here.
|
||||
internal const string kAssemblyVersion = "1.13.0";
|
||||
internal const string kDocUrl = "https://docs.unity3d.com/Packages/com.unity.inputsystem@1.13";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 68e8070c85d3c4544a527f58641b95c0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa1e768bf3ad4ae8898571a206251472
|
||||
timeCreated: 1506735435
|
||||
@@ -0,0 +1,39 @@
|
||||
using UnityEngine.InputSystem.Layouts;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
|
||||
////REVIEW: generalize this to AnyButton and add to more devices?
|
||||
|
||||
namespace UnityEngine.InputSystem.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// A control that simply checks the entire state it's been assigned
|
||||
/// for whether there's any non-zero bytes. If there are, the control
|
||||
/// returns 1.0; otherwise it returns 0.0.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This control is used by <see cref="Keyboard.anyKey"/> to create a button
|
||||
/// that is toggled on as long as any of the keys on the keyboard is pressed.
|
||||
/// </remarks>
|
||||
/// <seealso cref="Keyboard.anyKey"/>
|
||||
[InputControlLayout(hideInUI = true)]
|
||||
public class AnyKeyControl : ButtonControl
|
||||
{
|
||||
////TODO: wasPressedThisFrame and wasReleasedThisFrame
|
||||
|
||||
/// <summary>
|
||||
/// Default initialization. Sets state size to 1 bit and format to
|
||||
/// <see cref="InputStateBlock.FormatBit"/>.
|
||||
/// </summary>
|
||||
public AnyKeyControl()
|
||||
{
|
||||
m_StateBlock.sizeInBits = 1; // Should be overridden by whoever uses the control.
|
||||
m_StateBlock.format = InputStateBlock.FormatBit;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override unsafe float ReadUnprocessedValueFromState(void* statePtr)
|
||||
{
|
||||
return this.CheckStateIsAtDefault(statePtr) ? 0.0f : 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6e444fe6609243258e0a7746c163373a
|
||||
timeCreated: 1508369900
|
||||
@@ -0,0 +1,334 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
using UnityEngine.InputSystem.Processors;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
|
||||
////REVIEW: change 'clampToConstant' to simply 'clampToMin'?
|
||||
|
||||
////TODO: if AxisControl fields where properties, we wouldn't need ApplyParameterChanges, maybe it's ok breaking change?
|
||||
|
||||
namespace UnityEngine.InputSystem.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// A floating-point axis control.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Can optionally be configured to perform normalization.
|
||||
/// Stored as either a float, a short, a byte, or a single bit.
|
||||
/// </remarks>
|
||||
public class AxisControl : InputControl<float>
|
||||
{
|
||||
/// <summary>
|
||||
/// Clamping behavior for an axis control.
|
||||
/// </summary>
|
||||
public enum Clamp
|
||||
{
|
||||
/// <summary>
|
||||
/// Do not clamp values.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Clamp values to <see cref="clampMin"/> and <see cref="clampMax"/>
|
||||
/// before normalizing the value.
|
||||
/// </summary>
|
||||
BeforeNormalize = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Clamp values to <see cref="clampMin"/> and <see cref="clampMax"/>
|
||||
/// after normalizing the value.
|
||||
/// </summary>
|
||||
AfterNormalize = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Clamp values any value below <see cref="clampMin"/> or above <see cref="clampMax"/>
|
||||
/// to <see cref="clampConstant"/> before normalizing the value.
|
||||
/// </summary>
|
||||
ToConstantBeforeNormalize = 3,
|
||||
}
|
||||
|
||||
// These can be added as processors but they are so common that we
|
||||
// build the functionality right into AxisControl to save us an
|
||||
// additional object and an additional virtual call.
|
||||
|
||||
// NOTE: A number of the parameters here can be expressed in much simpler form.
|
||||
// E.g. 'scale', 'scaleFactor' and 'invert' could all be rolled into a single
|
||||
// multiplier. And maybe that's what we should do. However, the one advantage
|
||||
// of the current setup is that it allows to set these operations up individually.
|
||||
// For example, a given layout may want to have a very specific scale factor but
|
||||
// then a derived layout needs the value to be inverted. If it was a single setting,
|
||||
// the derived layout would have to know the specific scale factor in order to come
|
||||
// up with a valid multiplier.
|
||||
|
||||
/// <summary>
|
||||
/// Clamping behavior when reading values. <see cref="Clamp.None"/> by default.
|
||||
/// </summary>
|
||||
/// <value>Clamping behavior.</value>
|
||||
/// <remarks>
|
||||
/// When a value is read from the control's state, it is first converted
|
||||
/// to a floating-point number.
|
||||
/// </remarks>
|
||||
/// <seealso cref="clampMin"/>
|
||||
/// <seealso cref="clampMax"/>
|
||||
/// <seealso cref="clampConstant"/>
|
||||
public Clamp clamp;
|
||||
|
||||
/// <summary>
|
||||
/// Lower end of the clamping range when <see cref="clamp"/> is not
|
||||
/// <see cref="Clamp.None"/>.
|
||||
/// </summary>
|
||||
/// <value>Lower bound of clamping range. Inclusive.</value>
|
||||
public float clampMin;
|
||||
|
||||
/// <summary>
|
||||
/// Upper end of the clamping range when <see cref="clamp"/> is not
|
||||
/// <see cref="Clamp.None"/>.
|
||||
/// </summary>
|
||||
/// <value>Upper bound of clamping range. Inclusive.</value>
|
||||
public float clampMax;
|
||||
|
||||
/// <summary>
|
||||
/// When <see cref="clamp"/> is set to <see cref="Clamp.ToConstantBeforeNormalize"/>
|
||||
/// and the value is outside of the range defined by <see cref="clampMin"/> and
|
||||
/// <see cref="clampMax"/>, this value is returned.
|
||||
/// </summary>
|
||||
/// <value>Constant value to return when value is outside of clamping range.</value>
|
||||
public float clampConstant;
|
||||
|
||||
////REVIEW: why not just roll this into scaleFactor?
|
||||
/// <summary>
|
||||
/// If true, the input value will be inverted, i.e. multiplied by -1. Off by default.
|
||||
/// </summary>
|
||||
/// <value>Whether to invert the input value.</value>
|
||||
public bool invert;
|
||||
|
||||
/// <summary>
|
||||
/// If true, normalize the input value to [0..1] or [-1..1] (depending on the
|
||||
/// value of <see cref="normalizeZero"/>. Off by default.
|
||||
/// </summary>
|
||||
/// <value>Whether to normalize input values or not.</value>
|
||||
/// <seealso cref="normalizeMin"/>
|
||||
/// <seealso cref="normalizeMax"/>
|
||||
public bool normalize;
|
||||
|
||||
////REVIEW: shouldn't these come from the control min/max value by default?
|
||||
|
||||
/// <summary>
|
||||
/// If <see cref="normalize"/> is on, this is the input value that corresponds
|
||||
/// to 0 of the normalized [0..1] or [-1..1] range.
|
||||
/// </summary>
|
||||
/// <value>Input value that should become 0 or -1.</value>
|
||||
/// <remarks>
|
||||
/// In other words, with <see cref="normalize"/> on, input values are mapped from
|
||||
/// the range of [normalizeMin..normalizeMax] to [0..1] or [-1..1] (depending on
|
||||
/// <see cref="normalizeZero"/>).
|
||||
/// </remarks>
|
||||
public float normalizeMin;
|
||||
|
||||
/// <summary>
|
||||
/// If <see cref="normalize"/> is on, this is the input value that corresponds
|
||||
/// to 1 of the normalized [0..1] or [-1..1] range.
|
||||
/// </summary>
|
||||
/// <value>Input value that should become 1.</value>
|
||||
/// <remarks>
|
||||
/// In other words, with <see cref="normalize"/> on, input values are mapped from
|
||||
/// the range of [normalizeMin..normalizeMax] to [0..1] or [-1..1] (depending on
|
||||
/// <see cref="normalizeZero"/>).
|
||||
/// </remarks>
|
||||
public float normalizeMax;
|
||||
|
||||
/// <summary>
|
||||
/// Where to put the zero point of the normalization range. Only relevant
|
||||
/// if <see cref="normalize"/> is set to true. Defaults to 0.
|
||||
/// </summary>
|
||||
/// <value>Zero point of normalization range.</value>
|
||||
/// <remarks>
|
||||
/// The value of this property determines where the zero point is located in the
|
||||
/// range established by <see cref="normalizeMin"/> and <see cref="normalizeMax"/>.
|
||||
///
|
||||
/// If <c>normalizeZero</c> is placed at <see cref="normalizeMin"/>, the normalization
|
||||
/// returns a value in the [0..1] range mapped from the input value range of
|
||||
/// <see cref="normalizeMin"/> and <see cref="normalizeMax"/>.
|
||||
///
|
||||
/// If <c>normalizeZero</c> is placed in-between <see cref="normalizeMin"/> and
|
||||
/// <see cref="normalizeMax"/>, normalization returns a value in the [-1..1] mapped
|
||||
/// from the input value range of <see cref="normalizeMin"/> and <see cref="normalizeMax"/>
|
||||
/// and the zero point between the two established by <c>normalizeZero</c>.
|
||||
/// </remarks>
|
||||
public float normalizeZero;
|
||||
|
||||
////REVIEW: why not just have a default scaleFactor of 1?
|
||||
|
||||
/// <summary>
|
||||
/// Whether the scale the input value by <see cref="scaleFactor"/>. Off by default.
|
||||
/// </summary>
|
||||
/// <value>True if inputs should be scaled by <see cref="scaleFactor"/>.</value>
|
||||
public bool scale;
|
||||
|
||||
/// <summary>
|
||||
/// Value to multiple input values with. Only applied if <see cref="scale"/> is <c>true</c>.
|
||||
/// </summary>
|
||||
/// <value>Multiplier for input values.</value>
|
||||
public float scaleFactor;
|
||||
|
||||
/// <summary>
|
||||
/// Apply modifications to the given value according to the parameters configured
|
||||
/// on the control (<see cref="clamp"/>, <see cref="normalize"/>, etc).
|
||||
/// </summary>
|
||||
/// <param name="value">Input value.</param>
|
||||
/// <returns>A processed value (clamped, normalized, etc).</returns>
|
||||
/// <seealso cref="clamp"/>
|
||||
/// <seealso cref="normalize"/>
|
||||
/// <seealso cref="scale"/>
|
||||
/// <seealso cref="invert"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected float Preprocess(float value)
|
||||
{
|
||||
if (scale)
|
||||
value *= scaleFactor;
|
||||
if (clamp == Clamp.ToConstantBeforeNormalize)
|
||||
{
|
||||
if (value < clampMin || value > clampMax)
|
||||
value = clampConstant;
|
||||
}
|
||||
else if (clamp == Clamp.BeforeNormalize)
|
||||
value = Mathf.Clamp(value, clampMin, clampMax);
|
||||
if (normalize)
|
||||
value = NormalizeProcessor.Normalize(value, normalizeMin, normalizeMax, normalizeZero);
|
||||
if (clamp == Clamp.AfterNormalize)
|
||||
value = Mathf.Clamp(value, clampMin, clampMax);
|
||||
if (invert)
|
||||
value *= -1.0f;
|
||||
return value;
|
||||
}
|
||||
|
||||
private float Unpreprocess(float value)
|
||||
{
|
||||
// Does not reverse the effect of clamping (we don't know what the unclamped value should be).
|
||||
|
||||
if (invert)
|
||||
value *= -1f;
|
||||
if (normalize)
|
||||
value = NormalizeProcessor.Denormalize(value, normalizeMin, normalizeMax, normalizeZero);
|
||||
if (scale)
|
||||
value /= scaleFactor;
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default-initialize the control.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Defaults the format to <see cref="InputStateBlock.FormatFloat"/>.
|
||||
/// </remarks>
|
||||
public AxisControl()
|
||||
{
|
||||
m_StateBlock.format = InputStateBlock.FormatFloat;
|
||||
}
|
||||
|
||||
protected override void FinishSetup()
|
||||
{
|
||||
base.FinishSetup();
|
||||
|
||||
// if we don't have any default state, and we are using normalizeZero, then the default value
|
||||
// should not be zero. Generate it from normalizeZero.
|
||||
if (!hasDefaultState && normalize && Mathf.Abs(normalizeZero) > Mathf.Epsilon)
|
||||
m_DefaultState = stateBlock.FloatToPrimitiveValue(normalizeZero);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override unsafe float ReadUnprocessedValueFromState(void* statePtr)
|
||||
{
|
||||
switch (m_OptimizedControlDataType)
|
||||
{
|
||||
case InputStateBlock.kFormatFloat:
|
||||
return *(float*)((byte*)statePtr + m_StateBlock.m_ByteOffset);
|
||||
case InputStateBlock.kFormatByte:
|
||||
return *((byte*)statePtr + m_StateBlock.m_ByteOffset) != 0 ? 1.0f : 0.0f;
|
||||
default:
|
||||
{
|
||||
var value = stateBlock.ReadFloat(statePtr);
|
||||
////REVIEW: this isn't very raw
|
||||
return Preprocess(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override unsafe void WriteValueIntoState(float value, void* statePtr)
|
||||
{
|
||||
switch (m_OptimizedControlDataType)
|
||||
{
|
||||
case InputStateBlock.kFormatFloat:
|
||||
*(float*)((byte*)statePtr + m_StateBlock.m_ByteOffset) = value;
|
||||
break;
|
||||
case InputStateBlock.kFormatByte:
|
||||
*((byte*)statePtr + m_StateBlock.m_ByteOffset) = (byte)(value >= 0.5f ? 1 : 0);
|
||||
break;
|
||||
default:
|
||||
value = Unpreprocess(value);
|
||||
stateBlock.WriteFloat(statePtr, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override unsafe bool CompareValue(void* firstStatePtr, void* secondStatePtr)
|
||||
{
|
||||
var currentValue = ReadValueFromState(firstStatePtr);
|
||||
var valueInState = ReadValueFromState(secondStatePtr);
|
||||
return !Mathf.Approximately(currentValue, valueInState);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override unsafe float EvaluateMagnitude(void* statePtr)
|
||||
{
|
||||
return EvaluateMagnitude(ReadValueFromStateWithCaching(statePtr));
|
||||
}
|
||||
|
||||
private float EvaluateMagnitude(float value)
|
||||
{
|
||||
if (m_MinValue.isEmpty || m_MaxValue.isEmpty)
|
||||
return Mathf.Abs(value);
|
||||
|
||||
var min = m_MinValue.ToSingle();
|
||||
var max = m_MaxValue.ToSingle();
|
||||
|
||||
var clampedValue = Mathf.Clamp(value, min, max);
|
||||
|
||||
// If part of our range is in negative space, evaluate magnitude as two
|
||||
// separate subspaces.
|
||||
if (min < 0)
|
||||
{
|
||||
if (clampedValue < 0)
|
||||
return NormalizeProcessor.Normalize(Mathf.Abs(clampedValue), 0, Mathf.Abs(min), 0);
|
||||
return NormalizeProcessor.Normalize(clampedValue, 0, max, 0);
|
||||
}
|
||||
|
||||
return NormalizeProcessor.Normalize(clampedValue, min, max, 0);
|
||||
}
|
||||
|
||||
protected override FourCC CalculateOptimizedControlDataType()
|
||||
{
|
||||
var noProcessingNeeded =
|
||||
clamp == Clamp.None &&
|
||||
invert == false &&
|
||||
normalize == false &&
|
||||
scale == false;
|
||||
|
||||
if (noProcessingNeeded &&
|
||||
m_StateBlock.format == InputStateBlock.FormatFloat &&
|
||||
m_StateBlock.sizeInBits == 32 &&
|
||||
m_StateBlock.bitOffset == 0)
|
||||
return InputStateBlock.FormatFloat;
|
||||
if (noProcessingNeeded &&
|
||||
m_StateBlock.format == InputStateBlock.FormatBit &&
|
||||
// has to be 8, otherwise we might be mapping to a state which only represents first bit in the byte, while other bits might map to some other controls
|
||||
// like in the mouse where LMB/RMB map to the same byte, just LMB maps to first bit and RMB maps to second bit
|
||||
m_StateBlock.sizeInBits == 8 &&
|
||||
m_StateBlock.bitOffset == 0)
|
||||
return InputStateBlock.FormatByte;
|
||||
return InputStateBlock.FormatInvalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 551b733126e34f71a7294051cf2bfc0b
|
||||
timeCreated: 1506741161
|
||||
@@ -0,0 +1,387 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
using UnityEngine.Scripting;
|
||||
|
||||
////REVIEW: introduce separate base class for ButtonControl and AxisControl instead of deriving ButtonControl from AxisControl?
|
||||
|
||||
namespace UnityEngine.InputSystem.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// An axis that has a trigger point beyond which it is considered to be pressed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default, stored as a single bit. In that format, buttons will only yield 0
|
||||
/// and 1 as values. However, buttons return are <see cref="AxisControl"/>s and
|
||||
/// yield full floating-point values and may thus have a range of values. See
|
||||
/// <see cref="pressPoint"/> for how button presses on such buttons are handled.
|
||||
/// </remarks>
|
||||
public class ButtonControl : AxisControl
|
||||
{
|
||||
private bool m_NeedsToCheckFramePress = false;
|
||||
private uint m_UpdateCountLastPressed = uint.MaxValue;
|
||||
private uint m_UpdateCountLastReleased = uint.MaxValue;
|
||||
private bool m_LastUpdateWasPress;
|
||||
#if UNITY_EDITOR
|
||||
// Editor input updates have a separate block of state memory, so must be checked separately
|
||||
private uint m_UpdateCountLastPressedEditor = uint.MaxValue;
|
||||
private uint m_UpdateCountLastReleasedEditor = uint.MaxValue;
|
||||
private bool m_LastUpdateWasPressEditor;
|
||||
#endif
|
||||
|
||||
internal bool needsToCheckFramePress { get; private set; }
|
||||
|
||||
////REVIEW: are per-control press points really necessary? can we just drop them?
|
||||
/// <summary>
|
||||
/// The minimum value the button has to reach for it to be considered pressed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The button is considered pressed, if it has a value equal to or greater than
|
||||
/// this value.
|
||||
///
|
||||
/// By default, this property is set to -1. If the value of the property is negative,
|
||||
/// <see cref="InputSettings.defaultButtonPressPoint"/> is used.
|
||||
///
|
||||
/// The value can be configured as a parameter in a layout.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// using UnityEngine;
|
||||
/// using UnityEngine.InputSystem.Controls;
|
||||
///
|
||||
/// public class MyDevice : InputDevice
|
||||
/// {
|
||||
/// [InputControl(parameters = "pressPoint=0.234")]
|
||||
/// public ButtonControl button { get; private set; }
|
||||
///
|
||||
/// //...
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
public float pressPoint = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Return <see cref="pressPoint"/> if set, otherwise return <see cref="InputSettings.defaultButtonPressPoint"/>.
|
||||
/// </summary>
|
||||
public float pressPointOrDefault => pressPoint > 0 ? pressPoint : s_GlobalDefaultButtonPressPoint;
|
||||
|
||||
/// <summary>
|
||||
/// Default-initialize the button control.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The default format for the button control is <see cref="InputStateBlock.FormatBit"/>.
|
||||
/// The button control's minimum value is set to 0 and the maximum value to 1.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// using UnityEngine;
|
||||
/// using UnityEngine.InputSystem.Controls;
|
||||
///
|
||||
/// public class ButtonControlExample : MonoBehaviour
|
||||
/// {
|
||||
/// void Start()
|
||||
/// {
|
||||
/// var myButton = new ButtonControl();
|
||||
/// }
|
||||
///
|
||||
/// //...
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <seealso cref="AxisControl"/>
|
||||
public ButtonControl()
|
||||
{
|
||||
m_StateBlock.format = InputStateBlock.FormatBit;
|
||||
m_MinValue = 0f;
|
||||
m_MaxValue = 1f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the given value would be considered pressed for this button.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to check for if the button would be considered pressed or not.</param>
|
||||
/// <returns>True if <paramref name="value"/> crosses the threshold to be considered pressed.</returns>
|
||||
/// <remarks>
|
||||
/// The default format for the control is <see cref="InputStateBlock.FormatBit"/>.
|
||||
/// The control's minimum value is set to 0 and the maximum value to 1.
|
||||
/// See <see cref="InputSettings.defaultButtonPressPoint"/> and <see cref="pressPoint"/>for the (default) press point.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// using UnityEngine;
|
||||
/// using UnityEngine.InputSystem.Controls;
|
||||
///
|
||||
/// public class IsValueConsideredPressedExample : MonoBehaviour
|
||||
/// {
|
||||
/// void Start()
|
||||
/// {
|
||||
/// var myButton = new ButtonControl();
|
||||
/// var valueToTest = 0.5f;
|
||||
///
|
||||
/// if (myButton.IsValueConsideredPressed(valueToTest))
|
||||
/// {
|
||||
/// Debug.Log("myButton is considered pressed at: " + valueToTest.ToString());
|
||||
/// }
|
||||
/// else
|
||||
/// {
|
||||
/// Debug.Log("myButton is not considered pressed at: " + valueToTest.ToString());
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// //...
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public new bool IsValueConsideredPressed(float value)
|
||||
{
|
||||
return value >= pressPointOrDefault;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the button is currently pressed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A button is considered pressed if its value is equal to or greater
|
||||
/// than its button press threshold (<see cref="pressPointOrDefault"/>, <see cref="pressPoint"/>).
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <para>You can use this to read whether specific keys are currently pressed by using isPressed on keys, as shown in the following examples:</para>
|
||||
/// <code>
|
||||
/// <![CDATA[
|
||||
/// // Using KeyControl property directly.
|
||||
/// Keyboard.current.spaceKey.isPressed
|
||||
/// Keyboard.current.aKey.isPressed // etc.
|
||||
///
|
||||
/// // Using Key enum.
|
||||
/// Keyboard.current[Key.Space].isPressed
|
||||
///
|
||||
/// // Using key name.
|
||||
/// ((KeyControl)Keyboard.current["space"]).isPressed
|
||||
/// ]]>
|
||||
/// </code>
|
||||
/// <para>Note: The Input System identifies keys by physical layout, not according to the current language mapping of the keyboard. To query the name of the key according to the language mapping, use <see cref="InputControl.displayName"/>.
|
||||
///
|
||||
/// You can also use this to read mouse buttons, as shown in the following examples:</para>
|
||||
/// <code>
|
||||
/// <![CDATA[
|
||||
/// bool leftPressed = Mouse.current.leftButton.isPressed;
|
||||
/// bool rightPressed = Mouse.current.rightButton.isPressed;
|
||||
/// bool middlePressed = Mouse.current.middleButton.isPressed;
|
||||
/// ]]>
|
||||
/// </code>
|
||||
/// <para>You can also check through all numbered buttons on the mouse: (this example does not cause allocations)</para>
|
||||
/// <code>
|
||||
/// <![CDATA[
|
||||
/// var controls = Mouse.current.allControls;
|
||||
/// for (var i = 0; i < controls.Count; ++i)
|
||||
/// {
|
||||
/// var button = controls[i] as ButtonControl;
|
||||
/// if (button != null && button.isPressed)
|
||||
/// {
|
||||
/// // respond to mouse button press here...
|
||||
/// }
|
||||
/// }
|
||||
/// ]]>
|
||||
/// </code>
|
||||
/// <para>Or you can look up controls by name, like this:</para>
|
||||
/// <code>
|
||||
/// <![CDATA[
|
||||
/// bool leftPressed = ((ButtonControl)Mouse.current["leftButton"]).isPressed;
|
||||
/// ]]>
|
||||
/// </code>
|
||||
/// </example>
|
||||
public bool isPressed
|
||||
{
|
||||
get
|
||||
{
|
||||
// Take the old path if we don't have the speed gain from already testing wasPressedThisFrame/wasReleasedThisFrame.
|
||||
if (!needsToCheckFramePress)
|
||||
return IsValueConsideredPressed(value);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (InputUpdate.s_LatestUpdateType.IsEditorUpdate())
|
||||
return m_LastUpdateWasPressEditor;
|
||||
#endif
|
||||
|
||||
return m_LastUpdateWasPress;
|
||||
}
|
||||
}
|
||||
|
||||
// When we start caring about inter-frame presses, use the info we have to set up the alternate path.
|
||||
// If we don't do this, users could call wasPressedThisFrame/wasReleasedThisFrame twice for the first time in
|
||||
// a single frame, and the returned value may be incorrect until the next frame.
|
||||
private void BeginTestingForFramePresses(bool currentlyPressed, bool pressedLastFrame)
|
||||
{
|
||||
needsToCheckFramePress = true;
|
||||
device.m_ButtonControlsCheckingPressState.Add(this);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (InputUpdate.s_LatestUpdateType.IsEditorUpdate())
|
||||
{
|
||||
m_LastUpdateWasPressEditor = currentlyPressed;
|
||||
if (currentlyPressed && !pressedLastFrame)
|
||||
m_UpdateCountLastPressedEditor = device.m_CurrentUpdateStepCount;
|
||||
else if (pressedLastFrame && !currentlyPressed)
|
||||
m_UpdateCountLastReleasedEditor = device.m_CurrentUpdateStepCount;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
m_LastUpdateWasPress = currentlyPressed;
|
||||
if (currentlyPressed && !pressedLastFrame)
|
||||
m_UpdateCountLastPressed = device.m_CurrentUpdateStepCount;
|
||||
else if (pressedLastFrame && !currentlyPressed)
|
||||
m_UpdateCountLastReleased = device.m_CurrentUpdateStepCount;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the press started this frame.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The first time this function - or wasReleasedThisFrame - are called, it's possible that extremely fast
|
||||
/// inputs (or very slow frame update times) will result in presses/releases being missed.
|
||||
/// Following the next input system update after either have been called, and from then on until the device is
|
||||
/// destroyed, this ceases to be an issue.
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // An example showing the use of this property on a gamepad button and a keyboard key.
|
||||
///
|
||||
/// using UnityEngine;
|
||||
/// using UnityEngine.InputSystem;
|
||||
///
|
||||
/// public class ExampleScript : MonoBehaviour
|
||||
/// {
|
||||
/// void Update()
|
||||
/// {
|
||||
/// bool buttonPressed = Gamepad.current.aButton.wasPressedThisFrame;
|
||||
/// bool spaceKeyPressed = Keyboard.current.spaceKey.wasPressedThisFrame;
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// _Note_: The Input System identifies keys by physical layout, not according to the current language mapping of the keyboard. To query the name of the key according to the language mapping, use <see cref="InputControl.displayName"/>.
|
||||
///
|
||||
/// You can also use this property to read mouse buttons. For example:
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// Mouse.current.leftButton.wasPressedThisFrame
|
||||
/// Mouse.current.rightButton.wasPressedThisFrame
|
||||
/// Mouse.current.middleButton.wasPressedThisFrame
|
||||
/// </code>
|
||||
/// </example>
|
||||
///
|
||||
///
|
||||
/// </remarks>
|
||||
public bool wasPressedThisFrame
|
||||
{
|
||||
get
|
||||
{
|
||||
// Take the old path if this is the first time calling.
|
||||
if (!needsToCheckFramePress)
|
||||
{
|
||||
var currentlyPressed = IsValueConsideredPressed(value);
|
||||
var pressedLastFrame = IsValueConsideredPressed(ReadValueFromPreviousFrame());
|
||||
BeginTestingForFramePresses(currentlyPressed, pressedLastFrame);
|
||||
|
||||
return device.wasUpdatedThisFrame && currentlyPressed && !pressedLastFrame;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (InputUpdate.s_LatestUpdateType.IsEditorUpdate())
|
||||
return InputUpdate.s_UpdateStepCount == m_UpdateCountLastPressedEditor;
|
||||
#endif
|
||||
return InputUpdate.s_UpdateStepCount == m_UpdateCountLastPressed;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the press ended this frame.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// _Note_: The Input System identifies keys by physical layout, not according to the current language mapping of the keyboard. To query the name of the key according to the language mapping, use <see cref="InputControl.displayName"/>.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <para>An example showing the use of this property on a gamepad button and a keyboard key:</para>
|
||||
///
|
||||
/// <code>
|
||||
/// using UnityEngine;
|
||||
/// using UnityEngine.InputSystem;
|
||||
///
|
||||
/// public class ExampleScript : MonoBehaviour
|
||||
/// {
|
||||
/// void Update()
|
||||
/// {
|
||||
/// bool buttonReleased = Gamepad.current.aButton.wasReleasedThisFrame;
|
||||
/// bool spaceKeyReleased = Keyboard.current.spaceKey.wasReleasedThisFrame;
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public bool wasReleasedThisFrame
|
||||
{
|
||||
get
|
||||
{
|
||||
// Take the old path if this is the first time calling.
|
||||
if (!needsToCheckFramePress)
|
||||
{
|
||||
var currentlyPressed = IsValueConsideredPressed(value);
|
||||
var pressedLastFrame = IsValueConsideredPressed(ReadValueFromPreviousFrame());
|
||||
BeginTestingForFramePresses(currentlyPressed, pressedLastFrame);
|
||||
|
||||
return device.wasUpdatedThisFrame && !currentlyPressed && pressedLastFrame;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (InputUpdate.s_LatestUpdateType.IsEditorUpdate())
|
||||
return InputUpdate.s_UpdateStepCount == m_UpdateCountLastReleasedEditor;
|
||||
#endif
|
||||
return InputUpdate.s_UpdateStepCount == m_UpdateCountLastReleased;
|
||||
}
|
||||
}
|
||||
|
||||
internal void UpdateWasPressed()
|
||||
{
|
||||
var isNowPressed = IsValueConsideredPressed(value);
|
||||
|
||||
if (m_LastUpdateWasPress != isNowPressed)
|
||||
{
|
||||
if (isNowPressed)
|
||||
m_UpdateCountLastPressed = device.m_CurrentUpdateStepCount;
|
||||
else
|
||||
m_UpdateCountLastReleased = device.m_CurrentUpdateStepCount;
|
||||
|
||||
m_LastUpdateWasPress = isNowPressed;
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
internal void UpdateWasPressedEditor()
|
||||
{
|
||||
var isNowPressed = IsValueConsideredPressed(value);
|
||||
|
||||
if (m_LastUpdateWasPressEditor != isNowPressed)
|
||||
{
|
||||
if (isNowPressed)
|
||||
m_UpdateCountLastPressedEditor = device.m_CurrentUpdateStepCount;
|
||||
else
|
||||
m_UpdateCountLastReleasedEditor = device.m_CurrentUpdateStepCount;
|
||||
|
||||
m_LastUpdateWasPressEditor = isNowPressed;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // UNITY_EDITOR
|
||||
|
||||
// We make the current global default button press point available as a static so that we don't have to
|
||||
// constantly make the hop from InputSystem.settings -> InputManager.m_Settings -> defaultButtonPressPoint.
|
||||
internal static float s_GlobalDefaultButtonPressPoint;
|
||||
internal static float s_GlobalDefaultButtonReleaseThreshold;
|
||||
|
||||
// We clamp button press points to this value as allowing 0 as the press point causes all buttons
|
||||
// to implicitly be pressed all the time. Not useful.
|
||||
internal const float kMinButtonPressPoint = 0.0001f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d618f7e3781548c28836f39ae65d95e1
|
||||
timeCreated: 1506741155
|
||||
@@ -0,0 +1,207 @@
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
|
||||
namespace UnityEngine.InputSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of common usage string values as reported by <see cref="InputControl.usages"/>.
|
||||
/// </summary>
|
||||
public static class CommonUsages
|
||||
{
|
||||
/// <summary>
|
||||
/// Primary 2D motion control.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Example: Left stick on a gamepad.
|
||||
/// </remarks>
|
||||
public static readonly InternedString Primary2DMotion = new InternedString("Primary2DMotion");
|
||||
|
||||
/// <summary>
|
||||
/// Secondary 2D motion control.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Example: Right stick on a gamepad.
|
||||
/// </remarks>
|
||||
public static readonly InternedString Secondary2DMotion = new InternedString("Secondary2DMotion");
|
||||
|
||||
/// <summary>
|
||||
/// The primary action control on any input device, such as a gamepad, mouse, or keyboard.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Example: Primary mouse button (left button on right-handed configuration, right button on left-handed configuration),
|
||||
/// south-button on a gamepad.
|
||||
/// </remarks>
|
||||
public static readonly InternedString PrimaryAction = new InternedString("PrimaryAction");
|
||||
|
||||
/// <summary>
|
||||
/// Secondary action control on any input device, such as a gamepad, mouse, or keyboard.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Example: Secondary mouse button (right button on right-handed configuration, left button on left-handed configuration),
|
||||
/// east-button on a gamepad.
|
||||
/// </remarks>
|
||||
public static readonly InternedString SecondaryAction = new InternedString("SecondaryAction");
|
||||
|
||||
/// <summary>
|
||||
/// The primary trigger control on input devices with triggers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Example: Right trigger-button on a gamepad.
|
||||
/// </remarks>
|
||||
public static readonly InternedString PrimaryTrigger = new InternedString("PrimaryTrigger");
|
||||
|
||||
/// <summary>
|
||||
/// The secondary trigger control on input devices with triggers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Example: Left trigger-button on a gamepad.
|
||||
/// </remarks>
|
||||
public static readonly InternedString SecondaryTrigger = new InternedString("SecondaryTrigger");
|
||||
|
||||
/// <summary>
|
||||
/// A modifier action control that modifies usage of other controls.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Example: Keyboard modifier keys like CTRL, SHIFT, ALT, OPTION, etc.
|
||||
/// </remarks>
|
||||
public static readonly InternedString Modifier = new InternedString("Modifier");
|
||||
|
||||
/// <summary>
|
||||
/// The spatial position control on input devices with spatial tracking.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Example: User head position in tracking-space using e.g. a head-tracking system. This could for example be a VR tracking system or another user-facing tracking sensor.
|
||||
/// </remarks>
|
||||
public static readonly InternedString Position = new InternedString("Position");
|
||||
|
||||
/// <summary>
|
||||
/// The spatial orientation control on input devices with spatial tracking.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Example: User head-orientation in tracking-space using e.g. a head-tracking system. This could for example be a VR tracking system or another user-facing tracking sensor.
|
||||
/// </remarks>
|
||||
public static readonly InternedString Orientation = new InternedString("Orientation");
|
||||
|
||||
/// <summary>
|
||||
/// The primary hat-switch control on input devices with hat-switches such as joysticks or gamepads.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Example: Joystick or gamepad hat-switch.
|
||||
/// </remarks>
|
||||
public static readonly InternedString Hatswitch = new InternedString("Hatswitch");
|
||||
|
||||
/// <summary>
|
||||
/// Button to navigate to previous location.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Example: Escape on keyboard, B button on gamepad.
|
||||
///
|
||||
/// In general, the "Back" control is used for moving backwards in the navigation history
|
||||
/// of a UI. This is used, for example, in hierarchical menu structures to move back to parent menus
|
||||
/// (e.g. from the "Settings" menu back to the "Main" menu). Consoles generally have stringent requirements
|
||||
/// as to which button has to fulfill this role.
|
||||
/// </remarks>
|
||||
public static readonly InternedString Back = new InternedString("Back");
|
||||
|
||||
/// <summary>
|
||||
/// Button to navigate to next location.
|
||||
/// </summary>
|
||||
public static readonly InternedString Forward = new InternedString("Forward");
|
||||
|
||||
/// <summary>
|
||||
/// Button to bring up menu.
|
||||
/// </summary>
|
||||
public static readonly InternedString Menu = new InternedString("Menu");
|
||||
|
||||
/// <summary>
|
||||
/// Button to confirm the current choice.
|
||||
/// </summary>
|
||||
public static readonly InternedString Submit = new InternedString("Submit");
|
||||
|
||||
////REVIEW: isn't this the same as "Back"?
|
||||
/// <summary>
|
||||
/// Button to not accept the current choice.
|
||||
/// </summary>
|
||||
public static readonly InternedString Cancel = new InternedString("Cancel");
|
||||
|
||||
/// <summary>
|
||||
/// Horizontal motion axis.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Example: X axis on mouse.
|
||||
/// </remarks>
|
||||
public static readonly InternedString Horizontal = new InternedString("Horizontal");
|
||||
|
||||
/// <summary>
|
||||
/// Vertical motion axis.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Example: Y axis on mouse.
|
||||
/// </remarks>
|
||||
public static readonly InternedString Vertical = new InternedString("Vertical");
|
||||
|
||||
/// <summary>
|
||||
/// Rotation around single, fixed axis.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Example: twist on joystick or twist of pen (few pens support that).
|
||||
/// </remarks>
|
||||
public static readonly InternedString Twist = new InternedString("Twist");
|
||||
|
||||
/// <summary>
|
||||
/// Pressure level axis.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Example: pen pressure.
|
||||
/// </remarks>
|
||||
public static readonly InternedString Pressure = new InternedString("Pressure");
|
||||
|
||||
/// <summary>
|
||||
/// Axis to scroll horizontally.
|
||||
/// </summary>
|
||||
public static readonly InternedString ScrollHorizontal = new InternedString("ScrollHorizontal");
|
||||
|
||||
/// <summary>
|
||||
/// Axis to scroll vertically.
|
||||
/// </summary>
|
||||
public static readonly InternedString ScrollVertical = new InternedString("ScrollVertical");
|
||||
|
||||
/// <summary>
|
||||
/// A screen-space point.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Example: Touch contact point.
|
||||
/// </remarks>
|
||||
public static readonly InternedString Point = new InternedString("Point");
|
||||
|
||||
/// <summary>
|
||||
/// Low-frequency haptic motor for force-feedback.
|
||||
/// </summary>
|
||||
public static readonly InternedString LowFreqMotor = new InternedString("LowFreqMotor");
|
||||
|
||||
/// <summary>
|
||||
/// High-frequency haptic motor for force-feedback.
|
||||
/// </summary>
|
||||
public static readonly InternedString HighFreqMotor = new InternedString("HighFreqMotor");
|
||||
|
||||
/// <summary>
|
||||
/// Device in left hand.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Example: left hand XR controller.
|
||||
/// </remarks>
|
||||
public static readonly InternedString LeftHand = new InternedString("LeftHand");
|
||||
|
||||
/// <summary>
|
||||
/// Device in right hand.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Example: right hand XR controller.
|
||||
/// </remarks>
|
||||
public static readonly InternedString RightHand = new InternedString("RightHand");
|
||||
|
||||
/// <summary>
|
||||
/// Axis representing charge of battery (1=full, 0=empty).
|
||||
/// </summary>
|
||||
public static readonly InternedString BatteryStrength = new InternedString("BatteryStrength");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cf624ae4ff5e44cebdcba54ae1ff380a
|
||||
timeCreated: 1507150918
|
||||
@@ -0,0 +1,70 @@
|
||||
using UnityEngine.InputSystem.Layouts;
|
||||
using UnityEngine.Scripting;
|
||||
|
||||
namespace UnityEngine.InputSystem.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// Delta controls are a two-dimensional motion vector that accumulate within a frame
|
||||
/// and reset at the beginning of a frame. You can read the values from a delta control
|
||||
/// using the inherited members from Vector2Control or InputControl.
|
||||
/// </summary>
|
||||
/// <see cref="Pointer.delta"/>
|
||||
/// <seealso cref="Mouse.scroll"/>
|
||||
[Preserve]
|
||||
public class DeltaControl : Vector2Control
|
||||
{
|
||||
/// <summary>
|
||||
/// A synthetic axis representing the upper half of the Y axis value, i.e. the 0 to 1 range.
|
||||
/// </summary>
|
||||
/// <value>Control representing the control's upper half Y axis.</value>
|
||||
/// <remarks>
|
||||
/// The control is marked as <see cref="InputControl.synthetic"/>.
|
||||
/// </remarks>
|
||||
[InputControl(useStateFrom = "y", parameters = "clamp=1,clampMin=0,clampMax=3.402823E+38", synthetic = true, displayName = "Up")]
|
||||
[Preserve]
|
||||
public AxisControl up { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A synthetic axis representing the lower half of the Y axis value, i.e. the 0 to -1 range (inverted).
|
||||
/// </summary>
|
||||
/// <value>Control representing the control's lower half Y axis.</value>
|
||||
/// <remarks>
|
||||
/// The control is marked as <see cref="InputControl.synthetic"/>.
|
||||
/// </remarks>
|
||||
[InputControl(useStateFrom = "y", parameters = "clamp=1,clampMin=-3.402823E+38,clampMax=0,invert", synthetic = true, displayName = "Down")]
|
||||
[Preserve]
|
||||
public AxisControl down { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A synthetic axis representing the left half of the X axis value, i.e. the 0 to -1 range (inverted).
|
||||
/// </summary>
|
||||
/// <value>Control representing the control's left half X axis.</value>
|
||||
/// <remarks>
|
||||
/// The control is marked as <see cref="InputControl.synthetic"/>.
|
||||
/// </remarks>
|
||||
[InputControl(useStateFrom = "x", parameters = "clamp=1,clampMin=-3.402823E+38,clampMax=0,invert", synthetic = true, displayName = "Left")]
|
||||
[Preserve]
|
||||
public AxisControl left { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A synthetic axis representing the right half of the X axis value, i.e. the 0 to 1 range.
|
||||
/// </summary>
|
||||
/// <value>Control representing the control's right half X axis.</value>
|
||||
/// <remarks>
|
||||
/// The control is marked as <see cref="InputControl.synthetic"/>.
|
||||
/// </remarks>
|
||||
[InputControl(useStateFrom = "x", parameters = "clamp=1,clampMin=0,clampMax=3.402823E+38", synthetic = true, displayName = "Right")]
|
||||
[Preserve]
|
||||
public AxisControl right { get; set; }
|
||||
|
||||
protected override void FinishSetup()
|
||||
{
|
||||
base.FinishSetup();
|
||||
|
||||
up = GetChildControl<AxisControl>("up");
|
||||
down = GetChildControl<AxisControl>("down");
|
||||
left = GetChildControl<AxisControl>("left");
|
||||
right = GetChildControl<AxisControl>("right");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 339c9c6bc0554ad79681e127d417ef3d
|
||||
timeCreated: 1630420986
|
||||
@@ -0,0 +1,149 @@
|
||||
using System;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
using UnityEngine.InputSystem.Layouts;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
|
||||
////TODO: hide in UI
|
||||
|
||||
namespace UnityEngine.InputSystem.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// A button that is considered pressed if the underlying state has a value in the specific range.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This control is most useful for handling HID-style hat switches. Unlike <see cref="DpadControl"/>,
|
||||
/// which by default is stored as a bitfield of four bits that each represent a direction on the pad,
|
||||
/// these hat switches enumerate the possible directions that the switch can be moved in. For example,
|
||||
/// the value 1 could indicate that the switch is moved to the left whereas 3 could indicate it is
|
||||
/// moved up.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// [StructLayout(LayoutKind.Explicit, Size = 32)]
|
||||
/// internal struct DualShock4HIDInputReport : IInputStateTypeInfo
|
||||
/// {
|
||||
/// [FieldOffset(0)] public byte reportId;
|
||||
///
|
||||
/// [InputControl(name = "dpad", format = "BIT", layout = "Dpad", sizeInBits = 4, defaultState = 8)]
|
||||
/// [InputControl(name = "dpad/up", format = "BIT", layout = "DiscreteButton", parameters = "minValue=7,maxValue=1,nullValue=8,wrapAtValue=7", bit = 0, sizeInBits = 4)]
|
||||
/// [InputControl(name = "dpad/right", format = "BIT", layout = "DiscreteButton", parameters = "minValue=1,maxValue=3", bit = 0, sizeInBits = 4)]
|
||||
/// [InputControl(name = "dpad/down", format = "BIT", layout = "DiscreteButton", parameters = "minValue=3,maxValue=5", bit = 0, sizeInBits = 4)]
|
||||
/// [InputControl(name = "dpad/left", format = "BIT", layout = "DiscreteButton", parameters = "minValue=5, maxValue=7", bit = 0, sizeInBits = 4)]
|
||||
/// [FieldOffset(5)] public byte buttons1;
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
public class DiscreteButtonControl : ButtonControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Value (inclusive) at which to start considering the button to be pressed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="minValue"/> is allowed to be larger than <see cref="maxValue"/>. This indicates
|
||||
/// a setup where the value wraps around beyond <see cref="minValue"/>, skips <see cref="nullValue"/>,
|
||||
/// and then goes all the way up to <see cref="maxValue"/>.
|
||||
///
|
||||
/// For example, if the underlying state represents a circular D-pad and enumerates its
|
||||
/// 9 possible positions (including null state) going clock-wise from 0 to 8 and with 1
|
||||
/// indicating that the D-pad is pressed to the left, then 1, 2, and 8 would indicate
|
||||
/// that the "left" button is held on the D-pad. To set this up, set <see cref="minValue"/>
|
||||
/// to 8, <see cref="maxValue"/> to 2, and <see cref="nullValue"/> to 0 (the default).
|
||||
/// </remarks>
|
||||
public int minValue;
|
||||
|
||||
/// <summary>
|
||||
/// Value (inclusive) beyond which to stop considering the button to be pressed.
|
||||
/// </summary>
|
||||
public int maxValue;
|
||||
|
||||
/// <summary>
|
||||
/// Value (inclusive) at which the values cut off and wrap back around. Considered to not be set if equal to <see cref="nullValue"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is useful if, for example, an enumeration has more bits than are used for "on" states of controls. For example,
|
||||
/// a bitfield of 4 bits where values 1-7 (i.e. 0001 to 0111) indicate "on" states of controls and value 8 indicating an
|
||||
/// "off" state. In that case, set <see cref="nullValue"/> to 8 and <see cref="wrapAtValue"/> to 7.
|
||||
/// </remarks>
|
||||
public int wrapAtValue;
|
||||
|
||||
/// <summary>
|
||||
/// Value at which the button is considered to not be pressed. Usually zero. Some devices, however,
|
||||
/// use non-zero default states.
|
||||
/// </summary>
|
||||
public int nullValue;
|
||||
|
||||
/// <summary>
|
||||
/// Determines the behavior of <see cref="WriteValueIntoState"/>. By default, attempting to write a <see cref="DiscreteButtonControl"/>
|
||||
/// will result in a <c>NotSupportedException</c>.
|
||||
/// </summary>
|
||||
public WriteMode writeMode;
|
||||
|
||||
protected override void FinishSetup()
|
||||
{
|
||||
base.FinishSetup();
|
||||
|
||||
if (!stateBlock.format.IsIntegerFormat())
|
||||
throw new NotSupportedException(
|
||||
$"Non-integer format '{stateBlock.format}' is not supported for DiscreteButtonControl '{this}'");
|
||||
}
|
||||
|
||||
public override unsafe float ReadUnprocessedValueFromState(void* statePtr)
|
||||
{
|
||||
var valuePtr = (byte*)statePtr + (int)m_StateBlock.byteOffset;
|
||||
// Note that all signed data in state buffers is in excess-K format.
|
||||
var intValue = MemoryHelpers.ReadTwosComplementMultipleBitsAsInt(valuePtr, m_StateBlock.bitOffset, m_StateBlock.sizeInBits);
|
||||
|
||||
var value = 0.0f;
|
||||
if (minValue > maxValue)
|
||||
{
|
||||
// If no wrapping point is set, default to wrapping around exactly
|
||||
// at the point of minValue.
|
||||
if (wrapAtValue == nullValue)
|
||||
wrapAtValue = minValue;
|
||||
|
||||
if ((intValue >= minValue && intValue <= wrapAtValue)
|
||||
|| (intValue != nullValue && intValue <= maxValue))
|
||||
value = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = intValue >= minValue && intValue <= maxValue ? 1.0f : 0.0f;
|
||||
}
|
||||
|
||||
return Preprocess(value);
|
||||
}
|
||||
|
||||
public override unsafe void WriteValueIntoState(float value, void* statePtr)
|
||||
{
|
||||
if (writeMode == WriteMode.WriteNullAndMaxValue)
|
||||
{
|
||||
var valuePtr = (byte*)statePtr + (int)m_StateBlock.byteOffset;
|
||||
var valueToWrite = value >= pressPointOrDefault ? maxValue : nullValue;
|
||||
MemoryHelpers.WriteIntAsTwosComplementMultipleBits(valuePtr, m_StateBlock.bitOffset, m_StateBlock.sizeInBits, valueToWrite);
|
||||
return;
|
||||
}
|
||||
|
||||
// The way these controls are usually used, the state is shared between multiple DiscreteButtons. So writing one
|
||||
// may have unpredictable effects on the value of other buttons.
|
||||
throw new NotSupportedException("Writing value states for DiscreteButtonControl is not supported as a single value may correspond to multiple states");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How <see cref="DiscreteButtonControl.WriteValueIntoState"/> should behave.
|
||||
/// </summary>
|
||||
public enum WriteMode
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="DiscreteButtonControl.WriteValueIntoState"/> will throw <c>NotSupportedException</c>.
|
||||
/// </summary>
|
||||
WriteDisabled = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Write <see cref="DiscreteButtonControl.nullValue"/> for when the button is considered not pressed and
|
||||
/// write <see cref="DiscreteButtonControl.maxValue"/> for when the button is considered pressed.
|
||||
/// </summary>
|
||||
WriteNullAndMaxValue = 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1ab470181bfe4ba891e0d1584730fc4c
|
||||
timeCreated: 1517190786
|
||||
@@ -0,0 +1,30 @@
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
|
||||
namespace UnityEngine.InputSystem.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// A control reading a <see cref="double"/>.
|
||||
/// </summary>
|
||||
public class DoubleControl : InputControl<double>
|
||||
{
|
||||
/// <summary>
|
||||
/// Default-initialize the control.
|
||||
/// </summary>
|
||||
public DoubleControl()
|
||||
{
|
||||
m_StateBlock.format = InputStateBlock.FormatDouble;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override unsafe double ReadUnprocessedValueFromState(void* statePtr)
|
||||
{
|
||||
return m_StateBlock.ReadDouble(statePtr);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override unsafe void WriteValueIntoState(double value, void* statePtr)
|
||||
{
|
||||
m_StateBlock.WriteDouble(statePtr, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 72251f49d44478b4c93462938d7ce4cf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,165 @@
|
||||
using System;
|
||||
using UnityEngine.InputSystem.Layouts;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
|
||||
namespace UnityEngine.InputSystem.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// A control made up of four discrete, directional buttons. Forms a vector
|
||||
/// but can also be addressed as individual buttons.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Is stored as four bits by default.
|
||||
///
|
||||
/// The vector that is aggregated from the button states is normalized. I.e.
|
||||
/// even if pressing diagonally, the vector will have a length of 1 (instead
|
||||
/// of reading something like <c>(1,1)</c> for example).
|
||||
/// </remarks>
|
||||
public class DpadControl : Vector2Control
|
||||
{
|
||||
[InputControlLayout(hideInUI = true)]
|
||||
public class DpadAxisControl : AxisControl
|
||||
{
|
||||
public int component { get; set; }
|
||||
|
||||
protected override void FinishSetup()
|
||||
{
|
||||
base.FinishSetup();
|
||||
component = name == "x" ? 0 : 1;
|
||||
|
||||
// Set the state block to be the parent's state block. We don't use that to read
|
||||
// the axis directly (we call the parent control to do that), but we need to set
|
||||
// it up the actions know to monitor this memory for changes to the control.
|
||||
m_StateBlock = m_Parent.m_StateBlock;
|
||||
}
|
||||
|
||||
public override unsafe float ReadUnprocessedValueFromState(void* statePtr)
|
||||
{
|
||||
var value = ((DpadControl)m_Parent).ReadUnprocessedValueFromState(statePtr);
|
||||
return value[component];
|
||||
}
|
||||
}
|
||||
|
||||
// The DpadAxisControl has it's own logic to read state from the parent dpad.
|
||||
// The useStateFrom argument here is not actually used by that. The only reason
|
||||
// it is set up here is to avoid any state bytes being reserved for the DpadAxisControl.
|
||||
[InputControl(name = "x", layout = "DpadAxis", useStateFrom = "right", synthetic = true)]
|
||||
[InputControl(name = "y", layout = "DpadAxis", useStateFrom = "up", synthetic = true)]
|
||||
|
||||
/// <summary>
|
||||
/// The button representing the vertical upwards state of the D-Pad.
|
||||
/// </summary>
|
||||
[InputControl(bit = (int)ButtonBits.Up, displayName = "Up")]
|
||||
public ButtonControl up { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The button representing the vertical downwards state of the D-Pad.
|
||||
/// </summary>
|
||||
[InputControl(bit = (int)ButtonBits.Down, displayName = "Down")]
|
||||
public ButtonControl down { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The button representing the horizontal left state of the D-Pad.
|
||||
/// </summary>
|
||||
[InputControl(bit = (int)ButtonBits.Left, displayName = "Left")]
|
||||
public ButtonControl left { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The button representing the horizontal right state of the D-Pad.
|
||||
/// </summary>
|
||||
[InputControl(bit = (int)ButtonBits.Right, displayName = "Right")]
|
||||
public ButtonControl right { get; set; }
|
||||
|
||||
////TODO: should have X and Y child controls as well
|
||||
|
||||
public DpadControl()
|
||||
{
|
||||
m_StateBlock.sizeInBits = 4;
|
||||
m_StateBlock.format = InputStateBlock.FormatBit;
|
||||
}
|
||||
|
||||
protected override void FinishSetup()
|
||||
{
|
||||
up = GetChildControl<ButtonControl>("up");
|
||||
down = GetChildControl<ButtonControl>("down");
|
||||
left = GetChildControl<ButtonControl>("left");
|
||||
right = GetChildControl<ButtonControl>("right");
|
||||
base.FinishSetup();
|
||||
}
|
||||
|
||||
public override unsafe Vector2 ReadUnprocessedValueFromState(void* statePtr)
|
||||
{
|
||||
var upIsPressed = up.ReadValueFromStateWithCaching(statePtr) >= up.pressPointOrDefault;
|
||||
var downIsPressed = down.ReadValueFromStateWithCaching(statePtr) >= down.pressPointOrDefault;
|
||||
var leftIsPressed = left.ReadValueFromStateWithCaching(statePtr) >= left.pressPointOrDefault;
|
||||
var rightIsPressed = right.ReadValueFromStateWithCaching(statePtr) >= right.pressPointOrDefault;
|
||||
|
||||
return MakeDpadVector(upIsPressed, downIsPressed, leftIsPressed, rightIsPressed);
|
||||
}
|
||||
|
||||
public override unsafe void WriteValueIntoState(Vector2 value, void* statePtr)
|
||||
{
|
||||
var upIsPressed = up.IsValueConsideredPressed(value.y);
|
||||
var downIsPressed = down.IsValueConsideredPressed(value.y * -1f);
|
||||
var leftIsPressed = left.IsValueConsideredPressed(value.x * -1f);
|
||||
var rightIsPressed = right.IsValueConsideredPressed(value.x);
|
||||
|
||||
up.WriteValueIntoState(upIsPressed && !downIsPressed ? value.y : 0f, statePtr);
|
||||
down.WriteValueIntoState(downIsPressed && !upIsPressed ? value.y * -1f : 0f, statePtr);
|
||||
left.WriteValueIntoState(leftIsPressed && !rightIsPressed ? value.x * -1f : 0f, statePtr);
|
||||
right.WriteValueIntoState(rightIsPressed && !leftIsPressed ? value.x : 0f, statePtr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a direction vector from the given four button states.
|
||||
/// </summary>
|
||||
/// <param name="up">Whether button representing the up direction is pressed.</param>
|
||||
/// <param name="down">Whether button representing the down direction is pressed.</param>
|
||||
/// <param name="left">Whether button representing the left direction is pressed.</param>
|
||||
/// <param name="right">Whether button representing the right direction is pressed.</param>
|
||||
/// <param name="normalize">Whether to normalize the resulting vector. If this is false, vectors in the diagonal
|
||||
/// directions will have a magnitude of greater than 1. For example, up-left will be (-1,1).</param>
|
||||
/// <returns>A 2D direction vector.</returns>
|
||||
public static Vector2 MakeDpadVector(bool up, bool down, bool left, bool right, bool normalize = true)
|
||||
{
|
||||
var upValue = up ? 1.0f : 0.0f;
|
||||
var downValue = down ? -1.0f : 0.0f;
|
||||
var leftValue = left ? -1.0f : 0.0f;
|
||||
var rightValue = right ? 1.0f : 0.0f;
|
||||
|
||||
var result = new Vector2(leftValue + rightValue, upValue + downValue);
|
||||
|
||||
if (normalize)
|
||||
{
|
||||
// If press is diagonal, adjust coordinates to produce vector of length 1.
|
||||
// pow(0.707107) is roughly 0.5 so sqrt(pow(0.707107)+pow(0.707107)) is ~1.
|
||||
const float diagonal = 0.707107f;
|
||||
if (result.x != 0 && result.y != 0)
|
||||
result = new Vector2(result.x * diagonal, result.y * diagonal);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a direction vector from the given axis states.
|
||||
/// </summary>
|
||||
/// <param name="up">Axis value representing the up direction.</param>
|
||||
/// <param name="down">Axis value representing the down direction.</param>
|
||||
/// <param name="left">Axis value representing the left direction.</param>
|
||||
/// <param name="right">Axis value representing the right direction.</param>
|
||||
/// <returns>A 2D direction vector.</returns>
|
||||
public static Vector2 MakeDpadVector(float up, float down, float left, float right)
|
||||
{
|
||||
return new Vector2(-left + right, up - down);
|
||||
}
|
||||
|
||||
internal enum ButtonBits
|
||||
{
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b00a605ae58d438baf0b80388b377972
|
||||
timeCreated: 1506743933
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d59a0dc8508f4cf1819f6ba99250cd8e
|
||||
timeCreated: 1506735383
|
||||
@@ -0,0 +1,352 @@
|
||||
using System;
|
||||
using UnityEngine.InputSystem.Composites;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
|
||||
namespace UnityEngine.InputSystem.Layouts
|
||||
{
|
||||
/// <summary>
|
||||
/// Mark a field or property as representing/identifying an input control in some form.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This attribute is used in different places for different purposes.
|
||||
///
|
||||
/// When creating input control layouts (<see cref="InputControlLayout"/>) in C#, applying the
|
||||
/// attribute to fields in a state struct (see <see cref="IInputStateTypeInfo"/> or <see cref="GamepadState"/>
|
||||
/// for an example) or to properties in an input device (<see cref="InputDevice"/>), will cause an
|
||||
/// <see cref="InputControl"/> to be created from the field or property at runtime. The attribute
|
||||
/// can be applied multiple times to create multiple input controls (e.g. when having an int field
|
||||
/// that represents a bitfield where each bit is a separate button).
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// public class MyDevice : InputDevice
|
||||
/// {
|
||||
/// // Adds an InputControl with name=myButton and layout=Button to the device.
|
||||
/// [InputControl]
|
||||
/// public ButtonControl myButton { get; set; }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
///
|
||||
/// Another use is for marking <c>string</c> type fields that represent input control paths. Applying
|
||||
/// the attribute to them will cause them to automatically use a custom inspector similar to the one
|
||||
/// found in the action editor. For this use, only the <see cref="layout"/> property is taken into
|
||||
/// account.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// public class MyBehavior : MonoBehaviour
|
||||
/// {
|
||||
/// // In the inspector, shows a control selector that is restricted to
|
||||
/// // selecting buttons. As a result, controlPath will be set to path
|
||||
/// // representing the control that was picked (e.g. "<Gamepad>/buttonSouth").
|
||||
/// [InputControl(layout = "Button")]
|
||||
/// public string controlPath;
|
||||
///
|
||||
/// protected void OnEnable()
|
||||
/// {
|
||||
/// // Find controls by path.
|
||||
/// var controls = InputSystem.FindControl(controlPath);
|
||||
/// //...
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
///
|
||||
/// Finally, the attribute is also used in composite bindings (<see cref="InputBindingComposite"/>)
|
||||
/// to mark fields that reference parts of the composite. An example for this is <see cref="AxisComposite.negative"/>.
|
||||
/// In this use, also only the <see cref="layout"/> property is taken into account while other properties
|
||||
/// are ignored.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// public class MyComposite : InputBindingComposite<float>
|
||||
/// {
|
||||
/// // Add a part to the composite called 'firstControl' which expects
|
||||
/// // AxisControls.
|
||||
/// [InputControl(layout = "Axis")]
|
||||
/// public int firstControl;
|
||||
///
|
||||
/// // Add a part to the composite called 'secondControl' which expects
|
||||
/// // Vector3Controls.
|
||||
/// [InputControl(layout = "Vector3")]
|
||||
/// public int secondControl;
|
||||
///
|
||||
/// //...
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputControlLayout"/>
|
||||
/// <seealso cref="InputBindingComposite"/>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
|
||||
public sealed class InputControlAttribute : PropertyAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Layout to use for the control.
|
||||
/// </summary>
|
||||
/// <value>Layout to use for the control.</value>
|
||||
/// <remarks>
|
||||
/// If this is not set, the system tries to infer the layout type from the value type of
|
||||
/// the field or property. If the value type is itself registered as a layout, that layout
|
||||
/// will be used (e.g. when you have a property of type <see cref="Controls.ButtonControl"/>, the layout
|
||||
/// will be inferred to be "Button"). Otherwise, if a layout with the same name as the type is registered,
|
||||
/// that layout will be used (e.g. when you have a field of type <see cref="Vector3"/>, the layout
|
||||
/// will be inferred to be "Vector3").
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputControlLayout"/>
|
||||
public string layout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Layout variant to use for the control.
|
||||
/// </summary>
|
||||
/// <value>Layout variant to use for the control.</value>
|
||||
public string variants { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Name to give to the name. If null or empty, the name of the property or
|
||||
/// field the attribute is applied to will be used.
|
||||
/// </summary>
|
||||
/// <value>Name to give to the control.</value>
|
||||
/// <seealso cref="InputControl.name"/>
|
||||
public string name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Storage format to use for the control. If not set, default storage format
|
||||
/// for the given <see cref="layout"/> is used.
|
||||
/// </summary>
|
||||
/// <value>Memory storage format to use for the control.</value>
|
||||
/// <seealso cref="InputStateBlock.format"/>
|
||||
public string format { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Usage to apply to the control.
|
||||
/// </summary>
|
||||
/// <value>Usage for the control.</value>
|
||||
/// <remarks>
|
||||
/// This property can be used in place of <see cref="usages"/> to set just a single
|
||||
/// usage on the control.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputControl.usages"/>
|
||||
/// <seealso cref="InputControlLayout.ControlItem.usages"/>
|
||||
/// <seealso cref="CommonUsages"/>
|
||||
public string usage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Usages to apply to the control.
|
||||
/// </summary>
|
||||
/// <value>Usages for the control.</value>
|
||||
/// <remarks>
|
||||
/// This property should be used instead of <see cref="usage"/> when a control has multiple usages.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputControl.usages"/>
|
||||
/// <seealso cref="InputControlLayout.ControlItem.usages"/>
|
||||
/// <seealso cref="CommonUsages"/>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "According to MSDN, this message can be ignored for attribute parameters, as there are no better alternatives.")]
|
||||
public string[] usages { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional list of parameters to apply to the control.
|
||||
/// </summary>
|
||||
/// <value>Parameters to apply to the control.</value>
|
||||
/// <remarks>
|
||||
/// An <see cref="InputControl"/> may expose public fields which can be set as
|
||||
/// parameters. An example of this is <see cref="Controls.AxisControl.clamp"/>.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// public struct MyStateStruct : IInputStateTypeInfo
|
||||
/// {
|
||||
/// [InputControl(parameters = "clamp,clampMin=-0.5,clampMax=0.5")]
|
||||
/// public float axis;
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputControlLayout.ControlItem.parameters"/>
|
||||
public string parameters { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional list of processors to add to the control.
|
||||
/// </summary>
|
||||
/// <value>Processors to apply to the control.</value>
|
||||
/// <remarks>
|
||||
/// Each element in the list is a name of a processor (as registered with
|
||||
/// <see cref="InputSystem.RegisterProcessor{T}"/>) followed by an optional
|
||||
/// list of parameters.
|
||||
///
|
||||
/// For example, <c>"normalize(min=0,max=256)"</c> is one element that puts
|
||||
/// a <c>NormalizeProcessor</c> on the control and sets its <c>min</c> field
|
||||
/// to 0 and its its <c>max</c> field to 256.
|
||||
///
|
||||
/// Multiple processors can be put on a control by separating them with a comma.
|
||||
/// For example, <c>"normalize(max=256),scale(factor=2)"</c> puts both a <c>NormalizeProcessor</c>
|
||||
/// and a <c>ScaleProcessor</c> on the control. Processors are applied in the
|
||||
/// order they are listed.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputControlLayout.ControlItem.processors"/>
|
||||
/// <seealso cref="InputBinding.processors"/>
|
||||
public string processors { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// An alternative name that can be used in place of <see cref="name"/> to find
|
||||
/// the control.
|
||||
/// </summary>
|
||||
/// <value>Alternative name for the control.</value>
|
||||
/// <remarks>
|
||||
/// This property can be used instead of <see cref="aliases"/> when there is only a
|
||||
/// single alias for the control.
|
||||
///
|
||||
/// Aliases, like names, are case-insensitive. Any control may have arbitrary many
|
||||
/// aliases.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputControl.aliases"/>
|
||||
/// <seealso cref="InputControlLayout.ControlItem.aliases"/>
|
||||
public string alias { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of alternative names that can be used in place of <see cref="name"/> to
|
||||
/// find the control.
|
||||
/// </summary>
|
||||
/// <value>Alternative names for the control.</value>
|
||||
/// <remarks>
|
||||
/// This property should be used instead of <see cref="alias"/> when a control has
|
||||
/// multiple aliases.
|
||||
///
|
||||
/// Aliases, like names, are case-insensitive. Any control may have arbitrary many
|
||||
/// aliases.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputControl.aliases"/>
|
||||
/// <seealso cref="InputControlLayout.ControlItem.aliases"/>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "According to MSDN, this message can be ignored for attribute parameters, as there are no better alternatives.")]
|
||||
public string[] aliases { get; set; }
|
||||
|
||||
public string useStateFrom { get; set; }
|
||||
|
||||
public uint bit { get; set; } = InputStateBlock.InvalidOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Offset in bytes to where the memory of the control starts. Relative to
|
||||
/// the offset of the parent control (which may be the device itself).
|
||||
/// </summary>
|
||||
/// <value>Byte offset of the control.</value>
|
||||
/// <remarks>
|
||||
/// If the attribute is applied to fields in an <see cref="InputControlLayout"/> and
|
||||
/// this property is not set, the offset of the field is used instead.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// public struct MyStateStruct : IInputStateTypeInfo
|
||||
/// {
|
||||
/// public int buttons;
|
||||
///
|
||||
/// [InputControl] // Automatically uses the offset of 'axis'.
|
||||
/// public float axis;
|
||||
/// }
|
||||
///
|
||||
/// [InputControlLayout(stateType = typeof(MyStateStruct))]
|
||||
/// public class MyDevice : InputDevice
|
||||
/// {
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputControlLayout.ControlItem.offset"/>
|
||||
public uint offset { get; set; } = InputStateBlock.InvalidOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Size of the memory storage for the control in bits.
|
||||
/// </summary>
|
||||
/// <value>Size of the control in bits.</value>
|
||||
/// <remarks>
|
||||
/// If the attribute is applied to fields in an <see cref="InputControlLayout"/> and
|
||||
/// this property is not set, the size is taken from the field.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// public struct MyStateStruct : IInputStateTypeInfo
|
||||
/// {
|
||||
/// public int buttons;
|
||||
///
|
||||
/// [InputControl] // Automatically uses sizeof(float).
|
||||
/// public float axis;
|
||||
/// }
|
||||
///
|
||||
/// [InputControlLayout(stateType = typeof(MyStateStruct))]
|
||||
/// public class MyDevice : InputDevice
|
||||
/// {
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputControlLayout.ControlItem.sizeInBits"/>
|
||||
/// <seealso cref="InputStateBlock.sizeInBits"/>
|
||||
public uint sizeInBits { get; set; }
|
||||
|
||||
public int arraySize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Display name to assign to the control.
|
||||
/// </summary>
|
||||
/// <value>Display name for the control.</value>
|
||||
/// <seealso cref="InputControl.displayName"/>
|
||||
/// <seealso cref="InputControlLayout.ControlItem.displayName"/>
|
||||
public string displayName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Short display name to assign to the control.
|
||||
/// </summary>
|
||||
/// <value>Short display name for the control.</value>
|
||||
/// <seealso cref="InputControl.shortDisplayName"/>
|
||||
/// <seealso cref="InputControlLayout.ControlItem.shortDisplayName"/>
|
||||
public string shortDisplayName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the control is noisy. Off by default.
|
||||
/// </summary>
|
||||
/// <value>Whether control is noisy.</value>
|
||||
/// <seealso cref="InputControl.noisy"/>
|
||||
/// <seealso cref="InputControlLayout.ControlItem.isNoisy"/>
|
||||
public bool noisy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the control is synthetic. Off by default.
|
||||
/// </summary>
|
||||
/// <value>Whether control is synthetic.</value>
|
||||
/// <seealso cref="InputControl.synthetic"/>
|
||||
/// <seealso cref="InputControlLayout.ControlItem.isSynthetic"/>
|
||||
public bool synthetic { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Allows you to specify that a control should not be reset when its device is reset.
|
||||
/// </summary>
|
||||
/// <value>If true, resets of the device will leave the value of the control untouched except if a "hard" reset
|
||||
/// is explicitly enforced.</value>
|
||||
/// <seealso cref="InputSystem.ResetDevice"/>
|
||||
/// <seealso cref="InputControlLayout.ControlItem.dontReset"/>
|
||||
public bool dontReset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Default state to write into the control's memory.
|
||||
/// </summary>
|
||||
/// <value>Default memory state for the control.</value>
|
||||
/// <remarks>
|
||||
/// This is not the default <em>value</em> but rather the default memory state, i.e.
|
||||
/// the raw memory value read and the processed and returned as a value. By default
|
||||
/// this is <c>null</c> and result in a control's memory to be initialized with all
|
||||
/// zeroes.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputControlLayout.ControlItem.defaultState"/>
|
||||
public object defaultState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Lower limit for values of the control.
|
||||
/// </summary>
|
||||
/// <value>Lower limit for values of the control.</value>
|
||||
/// <remarks>
|
||||
/// This is null by default in which case no lower bound is applied to the TODO
|
||||
/// </remarks>
|
||||
public object minValue { get; set; }
|
||||
public object maxValue { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f07d0a558a2d4f5c885936a3a1219008
|
||||
timeCreated: 1506739435
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a8cfbe75f80984c1d9700ef41f6e2aeb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a2515c58b39ea24c8154ebc84f7c157
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
using UnityEngine.Scripting;
|
||||
|
||||
////REVIEW: should this *not* be inherited? inheritance can lead to surprises
|
||||
|
||||
namespace UnityEngine.InputSystem.Layouts
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute to control layout settings of a type used to generate an <see cref="InputControlLayout"/>.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
public sealed class InputControlLayoutAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Associates a state representation with an input device and drives
|
||||
/// the control layout generated for the device from its state rather
|
||||
/// than from the device class.
|
||||
/// </summary>
|
||||
/// <remarks>This is *only* useful if you have a state struct dictating a specific
|
||||
/// state layout and you want the device layout to automatically take offsets from
|
||||
/// the fields annotated with <see cref="InputControlAttribute"/>.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// public struct MyStateStruct : IInputStateTypeInfo
|
||||
/// {
|
||||
/// public FourCC format => new FourCC('M', 'Y', 'D', 'V');
|
||||
///
|
||||
/// [InputControl(name = "button1", layout = "Button", bit = 0)]
|
||||
/// [InputControl(name = "button2", layout = "Button", bit = 0)]
|
||||
/// public int buttons;
|
||||
/// }
|
||||
///
|
||||
/// [InputControlLayout(stateType = typeof(MyStateStruct)]
|
||||
/// public class MyDevice : InputDevice
|
||||
/// {
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="LowLevel.InputStateBlock"/>
|
||||
/// <seealso cref="LowLevel.MouseState"/>
|
||||
public Type stateType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="FourCC"/> identifier for the memory format associated with the layout.
|
||||
/// </summary>
|
||||
/// <seealso cref="LowLevel.InputStateBlock.format"/>
|
||||
public string stateFormat { get; set; }
|
||||
|
||||
////TODO: rename this to just "usages"; "commonUsages" is such a weird name
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "According to MSDN, this message can be ignored for attribute parameters, as there are no better alternatives.")]
|
||||
public string[] commonUsages { get; set; }
|
||||
|
||||
public string variants { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Allows marking a device as noisy regardless of control layout.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Controls can be individually marked as noisy using the <see cref="InputControlAttribute.noisy"/>
|
||||
/// attribute, but this property can be used to mark a device as noisy even when no control has been
|
||||
/// marked as such. This can be useful when a device state layout has only been partially implemented
|
||||
/// i.e. some data in the state memory has not been mapped to a control, and the unimplemented controls
|
||||
/// are noisy. Without doing this, the device will constantly be made current as the system has no way
|
||||
/// to know that the event data contains only noise.
|
||||
/// </remarks>
|
||||
public bool isNoisy { get; set; }
|
||||
|
||||
internal bool? canRunInBackgroundInternal;
|
||||
|
||||
public bool canRunInBackground
|
||||
{
|
||||
get => canRunInBackgroundInternal.Value;
|
||||
set => canRunInBackgroundInternal = value;
|
||||
}
|
||||
|
||||
internal bool? updateBeforeRenderInternal;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the device should receive events in <see cref="LowLevel.InputUpdateType.BeforeRender"/> updates.
|
||||
/// </summary>
|
||||
/// <seealso cref="InputDevice.updateBeforeRender"/>
|
||||
public bool updateBeforeRender
|
||||
{
|
||||
get => updateBeforeRenderInternal.Value;
|
||||
set => updateBeforeRenderInternal = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If true, the layout describes a generic class of devices such as "gamepads" or "mice".
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property also determines how the layout is presented in the UI. All the device layouts
|
||||
/// that are marked as generic kinds of devices are displayed with their own entry at the root level of
|
||||
/// the control picker (<see cref="UnityEngine.InputSystem.Editor.InputControlPicker"/>), for example.
|
||||
/// </remarks>
|
||||
public bool isGenericTypeOfDevice { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gives a name to display in the UI. By default, the name is the same as the class the attribute
|
||||
/// is applied to.
|
||||
/// </summary>
|
||||
public string displayName { get; set; }
|
||||
|
||||
public string description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, don't include the layout when presenting picking options in the UI.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will keep device layouts out of the control picker and will keep control layouts out of
|
||||
/// action type dropdowns.
|
||||
/// </remarks>
|
||||
public bool hideInUI { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5256e0d1ebecc84d98a4001ab63d5b8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace UnityEngine.InputSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Enum used to identity the change type for the <see cref="InputSystem.onLayoutChange"/> event.
|
||||
/// </summary>
|
||||
public enum InputControlLayoutChange
|
||||
{
|
||||
Added,
|
||||
Removed,
|
||||
Replaced
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f5899dcacd4e3004eaa9107c97e75e09
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user