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
{
///
/// A 2D planar motion vector computed from an up+down button pair and a left+right
/// button pair.
///
///
/// 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 ).
///
/// 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.
///
///
///
/// // Set up WASD style keyboard controls.
/// action.AddCompositeBinding("2DVector")
/// .With("Up", "<Keyboard>/w")
/// .With("Left", "<Keyboard>/a")
/// .With("Down", "<Keyboard>/s")
/// .With("Right", "<Keyboard>/d");
///
///
///
///
[DisplayStringFormat("{up}/{left}/{down}/{right}")] // This results in WASD.
[DisplayName("Up/Down/Left/Right Composite")]
public class Vector2Composite : InputBindingComposite
{
///
/// Binding for the button that represents the up (that is, (0,1)) direction of the vector.
///
///
/// This property is automatically assigned by the input system.
///
// ReSharper disable once MemberCanBePrivate.Global
// ReSharper disable once FieldCanBeMadeReadOnly.Global
[InputControl(layout = "Axis")] public int up;
///
/// Binding for the button represents the down (that is, (0,-1)) direction of the vector.
///
///
/// This property is automatically assigned by the input system.
///
// ReSharper disable once MemberCanBePrivate.Global
// ReSharper disable once FieldCanBeMadeReadOnly.Global
[InputControl(layout = "Axis")] public int down;
///
/// Binding for the button represents the left (that is, (-1,0)) direction of the vector.
///
///
/// This property is automatically assigned by the input system.
///
// ReSharper disable once MemberCanBePrivate.Global
// ReSharper disable once FieldCanBeMadeReadOnly.Global
[InputControl(layout = "Axis")] public int left;
///
/// Binding for the button that represents the right (that is, (1,0)) direction of the vector.
///
///
/// This property is automatically assigned by the input system.
///
[InputControl(layout = "Axis")] public int right;
[Obsolete("Use Mode.DigitalNormalized with 'mode' instead")]
public bool normalize = true;
///
/// How to synthesize a Vector2 from the values read from , ,
/// , and .
///
/// Determines how X and Y of the resulting Vector2 are formed from input values.
///
///
///
/// var action = new InputAction();
///
/// // DigitalNormalized composite (the default). Turns gamepad left stick into
/// // control equivalent to the D-Pad.
/// action.AddCompositeBinding("2DVector(mode=0)")
/// .With("up", "<Gamepad>/leftStick/up")
/// .With("down", "<Gamepad>/leftStick/down")
/// .With("left", "<Gamepad>/leftStick/left")
/// .With("right", "<Gamepad>/leftStick/right");
///
/// // Digital composite. Turns gamepad left stick into control equivalent
/// // to the D-Pad except that diagonals will not be normalized.
/// action.AddCompositeBinding("2DVector(mode=1)")
/// .With("up", "<Gamepad>/leftStick/up")
/// .With("down", "<Gamepad>/leftStick/down")
/// .With("left", "<Gamepad>/leftStick/left")
/// .With("right", "<Gamepad>/leftStick/right");
///
/// // Analog composite. In this case results in setup that behaves exactly
/// // the same as leftStick already does. But you could use it, for example,
/// // to swap directions by binding "up" to leftStick/down and "down" to
/// // leftStick/up.
/// action.AddCompositeBinding("2DVector(mode=2)")
/// .With("up", "<Gamepad>/leftStick/up")
/// .With("down", "<Gamepad>/leftStick/down")
/// .With("left", "<Gamepad>/leftStick/left")
/// .With("right", "<Gamepad>/leftStick/right");
///
///
///
public Mode mode;
///
public override Vector2 ReadValue(ref InputBindingCompositeContext context)
{
var mode = this.mode;
if (mode == Mode.Analog)
{
var upValue = context.ReadValue(up);
var downValue = context.ReadValue(down);
var leftValue = context.ReadValue(left);
var rightValue = context.ReadValue(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);
}
///
public override float EvaluateMagnitude(ref InputBindingCompositeContext context)
{
var value = ReadValue(ref context);
return value.magnitude;
}
///
/// Determines how a Vector2 is synthesized from part controls.
///
public enum Mode
{
///
/// 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).
///
Analog = 2,
///
/// 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 new Vector2(-1,1).normalized) is returned instead.
/// The resulting 2D area is diamond-shaped.
///
DigitalNormalized = 0,
///
/// 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.
///
Digital = 1
}
}
#if UNITY_EDITOR
internal class Vector2CompositeEditor : InputParameterEditor
{
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
}