test
This commit is contained in:
@@ -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.IsFeatureEnabled(InputFeatureNames.kUseIMGUIEditorForAssets)) 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,137 @@
|
||||
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, <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.
|
||||
/// </remarks>
|
||||
public bool overrideModifiersNeedToBePressedFirst;
|
||||
|
||||
/// <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 && !overrideModifiersNeedToBePressedFirst)
|
||||
{
|
||||
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 (!overrideModifiersNeedToBePressedFirst)
|
||||
overrideModifiersNeedToBePressedFirst = !InputSystem.settings.shortcutKeysConsumeInput;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 15164829aab964eedaee6bac785c2c05
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,153 @@
|
||||
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, <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> <c>shift</c> keys have 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="modifier1"/>, <see cref="modifier2"/>,
|
||||
/// and <see cref="button"/>. The only requirement is for all of them to concurrently be in pressed state.
|
||||
/// </remarks>
|
||||
public bool overrideModifiersNeedToBePressedFirst;
|
||||
|
||||
/// <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 && !overrideModifiersNeedToBePressedFirst)
|
||||
{
|
||||
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 (!overrideModifiersNeedToBePressedFirst)
|
||||
overrideModifiersNeedToBePressedFirst = !InputSystem.settings.shortcutKeysConsumeInput;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 62dcc18c42c2246bdaed7fe210b77118
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,184 @@
|
||||
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 <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.
|
||||
/// </remarks>
|
||||
public bool overrideModifiersNeedToBePressedFirst;
|
||||
|
||||
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 && !overrideModifiersNeedToBePressedFirst)
|
||||
{
|
||||
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 (!overrideModifiersNeedToBePressedFirst)
|
||||
overrideModifiersNeedToBePressedFirst = !InputSystem.settings.shortcutKeysConsumeInput;
|
||||
}
|
||||
|
||||
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,171 @@
|
||||
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 <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
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
public bool overrideModifiersNeedToBePressedFirst;
|
||||
|
||||
/// <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 && !overrideModifiersNeedToBePressedFirst)
|
||||
{
|
||||
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 (!overrideModifiersNeedToBePressedFirst)
|
||||
overrideModifiersNeedToBePressedFirst = !InputSystem.settings.shortcutKeysConsumeInput;
|
||||
}
|
||||
|
||||
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 of 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.IsFeatureEnabled(InputFeatureNames.kUseIMGUIEditorForAssets)) 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.IsFeatureEnabled(InputFeatureNames.kUseIMGUIEditorForAssets)) 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
|
File diff suppressed because it is too large
Load Diff
@@ -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:
|
File diff suppressed because it is too large
Load Diff
@@ -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:
|
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user