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 }