This commit is contained in:
2025-01-17 13:10:42 +01:00
commit 4536213c91
15115 changed files with 1442174 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 2069418e852748dbaa154cf71b8f033a
folderAsset: yes
timeCreated: 1506735447
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6adbf65a53ff9b04990c2116b3e4d5b7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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", "&lt;Keyboard&gt;/a")
/// .With("Positive", "&lt;Keyboard&gt;/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", "&lt;Keyboard&gt;/a")
/// .With("Positive", "&lt;Keyboard&gt;/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", "&lt;Keyboard&gt;/a")
/// .With("Positive", "&lt;Keyboard&gt;/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
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 61fdc882d66f0f34d90450c001c0078e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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", "&lt;Keyboard&gt;/leftCtrl")
/// .With("Modifier", "&lt;Keyboard&gt;/rightControl")
/// .With("Button", "&lt;Keyboard&gt;/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", "&lt;Gamepad&gt;/buttonSouth")
/// .With("Button", "&lt;Gamepad&gt;/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>&lt;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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 15164829aab964eedaee6bac785c2c05
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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", "&lt;Keyboard&gt;/leftCtrl")
/// .With("Modifier1", "&lt;Keyboard&gt;/rightCtrl")
/// .With("Modifier2", "&lt;Keyboard&gt;/leftShift")
/// .With("Modifier2", "&lt;Keyboard&gt;/rightShift")
/// .With("Button", "&lt;Keyboard&gt;/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", "&lt;Gamepad&gt;/buttonSouth")
/// .With("Modifier2", "&lt;Gamepad&gt;/buttonWest")
/// .With("Button", "&lt;Gamepad&gt;/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>&lt;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>&lt;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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 62dcc18c42c2246bdaed7fe210b77118
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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", "&lt;Keyboard&gt;/ctrl")
/// .With("Binding", "&lt;Keyboard&gt;/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", "&lt;Keyboard&gt;/alt")
/// .With("Binding", "&lt;Mouse&gt;/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>&lt;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;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1a0a9c8a3d9c4893ab5389e009563314
timeCreated: 1588685554

View File

@@ -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", "&lt;Keyboard&gt;/ctrl")
/// .With("Modifier", "&lt;Keyboard&gt;/shift")
/// .With("Binding", "&lt;Keyboard&gt;/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", "&lt;Keyboard&gt;/ctrl")
/// .With("Modifier2", "&lt;Keyboard&gt;/shift")
/// .With("Binding", "&lt;Mouse&gt;/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>&lt;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>&lt;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;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: be272fdf97df4e8daf1393dabdfa4cb8
timeCreated: 1588685577

View File

@@ -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", "&lt;Keyboard&gt;/w")
/// .With("Left", "&lt;Keyboard&gt;/a")
/// .With("Down", "&lt;Keyboard&gt;/s")
/// .With("Right", "&lt;Keyboard&gt;/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", "&lt;Gamepad&gt;/leftStick/up")
/// .With("down", "&lt;Gamepad&gt;/leftStick/down")
/// .With("left", "&lt;Gamepad&gt;/leftStick/left")
/// .With("right", "&lt;Gamepad&gt;/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", "&lt;Gamepad&gt;/leftStick/up")
/// .With("down", "&lt;Gamepad&gt;/leftStick/down")
/// .With("left", "&lt;Gamepad&gt;/leftStick/left")
/// .With("right", "&lt;Gamepad&gt;/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", "&lt;Gamepad&gt;/leftStick/up")
/// .With("down", "&lt;Gamepad&gt;/leftStick/down")
/// .With("left", "&lt;Gamepad&gt;/leftStick/left")
/// .With("right", "&lt;Gamepad&gt;/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
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4e9c0b68df9234a0bad68ad7e0391330
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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", "&lt;Keyboard&gt;/w")
/// .With("Backward", "&lt;Keyboard&gt;/s")
/// .With("Left", "&lt;Keyboard&gt;/a")
/// .With("Right", "&lt;Keyboard&gt;/d")
/// .With("Up", "&lt;Keyboard&gt;/q")
/// .With("Down", "&lt;Keyboard&gt;/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
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cda1b45d3bda468a95dca208b99174da
timeCreated: 1597842391

View File

@@ -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 "&lt;Gamepad&gt;/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 = "&lt;Gamepad&gt;/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);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0e297b4219a224ba5bcb4f9293e26ea9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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("&lt;Gamepad&gt;/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&lt;MyInteraction&gt;();
/// }
///
/// [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&lt;MyCustomInteraction&gt;
/// {
/// 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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9e37175fb5b321444aa88a861f93360a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cfe12f5319f74d9e8b0875e965ac280b
timeCreated: 1506842940

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 39fa3d8997a24136984ca6e2c99902bc
timeCreated: 1509594627

View File

@@ -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,
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c77cd15ef9ca14d3e928d192049c276d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 87354c477cc09c441b957b3aae10d813
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 050df780a05f4754b28e85d32a88da95
timeCreated: 1647030981

View File

@@ -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
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c2db5674bb583c0449389396f935abf8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0c43a6876f1eb4fdf8b9dc2fee926776
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bd166cb4f7947a8498e7ad07a9c04294
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fc1515ab76e54f068e2f2207940fab32
timeCreated: 1509649918

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ae31a855334ecf64f848ae8e0b4e5a24
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ca5a63bf867c240879f2eb2179771ffb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4ff1587ed21f442578f1ad36c4779a95
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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: "&lt;Gamepad&gt;/&lt;Button&gt;");
///
/// 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: "&lt;Gamepad&gt;/leftStick");
///
/// action.started +=
/// ctx =>
/// {
/// Debug.Log("--- Stick Starts ---");
/// };
/// action.performed +=
/// ctx =>
/// {
/// Debug.Log("Stick Value: " + ctx.ReadValue&lt;Vector2D&gt;();
/// };
/// 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,
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b21f8c6bf1fd94d1fb47907209b23367
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 49207568c569440f97a5bfe8b4144a16
timeCreated: 1506842994

View File

@@ -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", "&lt;Keyboard&gt;/w")
/// .With("Down", "&lt;Keyboard&gt;/s")
/// .With("Left", "&lt;Keyboard&gt;/a")
/// .With("Right", "&lt;Keyboard&gt;/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&lt;float&gt;
/// {
/// [InputControl(layout = "Button")]
/// public int button;
///
/// public float scaleFactor = 1;
///
/// public override float ReadValue(ref InputBindingComposite context)
/// {
/// return context.ReadValue&lt;float&gt;(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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6efadb7e1c29b4055adbd232610a4179
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show More