using System; using System.ComponentModel; using System.Runtime.InteropServices; using UnityEngine.InputSystem.Controls; using UnityEngine.InputSystem.Haptics; using UnityEngine.InputSystem.Layouts; using UnityEngine.InputSystem.LowLevel; using UnityEngine.InputSystem.Utilities; using UnityEngine.Scripting; ////TODO: come up with consistent naming for buttons; (xxxButton? xxx?) ////REVIEW: should we add a gyro as a standard feature of gamepads? ////TODO: allow to be used for mouse simulation namespace UnityEngine.InputSystem.LowLevel { /// /// Default state layout for gamepads. /// /// /// Be aware that unlike some other devices such as or , /// gamepad devices tend to have wildly varying state formats, i.e. forms in which they internally /// store their input data. In practice, even on the same platform gamepads will often store /// their data in different formats. This means that will often not /// be the format in which a particular gamepad (such as , /// for example) stores its data. /// /// If your gamepad data is arriving in a different format, you should extend the "Gamepad" layout and customize its Controls. /// /// A real-world example of this is the Xbox Controller on macOS, which is supported through HID. Its layout looks like this: /// /// /// /// { /// "name" : "XboxGamepadOSX", /// "extend" : "Gamepad", /// "format" : "HID", /// "device" : { "interface" : "HID", "product" : "Xbox.*Controller" }, /// "controls" : [ /// { "name" : "leftShoulder", "offset" : 2, "bit" : 8 }, /// { "name" : "rightShoulder", "offset" : 2, "bit" : 9 }, /// { "name" : "leftStickPress", "offset" : 2, "bit" : 14 }, /// { "name" : "rightStickPress", "offset" : 2, "bit" : 15 }, /// { "name" : "buttonSouth", "offset" : 2, "bit" : 12 }, /// { "name" : "buttonEast", "offset" : 2, "bit" : 13 }, /// { "name" : "buttonWest", "offset" : 2, "bit" : 14 }, /// { "name" : "buttonNorth", "offset" : 2, "bit" : 15 }, /// { "name" : "dpad", "offset" : 2 }, /// { "name" : "dpad/up", "offset" : 0, "bit" : 8 }, /// { "name" : "dpad/down", "offset" : 0, "bit" : 9 }, /// { "name" : "dpad/left", "offset" : 0, "bit" : 10 }, /// { "name" : "dpad/right", "offset" : 0, "bit" : 11 }, /// { "name" : "start", "offset" : 2, "bit" : 4 }, /// { "name" : "select", "offset" : 2, "bit" : 5 }, /// { "name" : "xbox", "offset" : 2, "bit" : 2, "layout" : "Button" }, /// { "name" : "leftTrigger", "offset" : 4, "format" : "BYTE" }, /// { "name" : "rightTrigger", "offset" : 5, "format" : "BYTE" }, /// { "name" : "leftStick", "offset" : 6, "format" : "VC2S" }, /// { "name" : "leftStick/x", "offset" : 0, "format" : "SHRT", "parameters" : "normalize,normalizeMin=-0.5,normalizeMax=0.5" }, /// { "name" : "leftStick/y", "offset" : 2, "format" : "SHRT", "parameters" : "invert,normalize,normalizeMin=-0.5,normalizeMax=0.5" }, /// { "name" : "rightStick", "offset" : 10, "format" : "VC2S" }, /// { "name" : "rightStick/x", "offset" : 0, "format" : "SHRT", "parameters" : "normalize,normalizeMin=-0.5,normalizeMax=0.5" }, /// { "name" : "rightStick/y", "offset" : 2, "format" : "SHRT", "parameters" : "invert,normalize,normalizeMin=-0.5,normalizeMax=0.5" } /// ] /// } /// /// /// /// The same principle applies if some buttons on your Device are swapped, for example. In this case, you can remap their offsets. /// /// /// /// /// /// // NOTE: Must match GamepadInputState in native. [StructLayout(LayoutKind.Explicit, Size = 28)] public struct GamepadState : IInputStateTypeInfo { public static FourCC Format => new FourCC('G', 'P', 'A', 'D'); // On Sony consoles, we use the platform defaults as the gamepad-wide short default names. #if UNITY_PS4 || UNITY_PS5 internal const string ButtonSouthShortDisplayName = "Cross"; internal const string ButtonNorthShortDisplayName = "Triangle"; internal const string ButtonWestShortDisplayName = "Square"; internal const string ButtonEastShortDisplayName = "East"; #elif UNITY_SWITCH internal const string ButtonSouthShortDisplayName = "B"; internal const string ButtonNorthShortDisplayName = "X"; internal const string ButtonWestShortDisplayName = "Y"; internal const string ButtonEastShortDisplayName = "A"; #else internal const string ButtonSouthShortDisplayName = "A"; internal const string ButtonNorthShortDisplayName = "Y"; internal const string ButtonWestShortDisplayName = "X"; internal const string ButtonEastShortDisplayName = "B"; #endif /// /// Button bit mask. /// /// Button bit mask. /// /// /// /// /// /// /// /// /// /// /// ////REVIEW: do we want the name to correspond to what's actually on the device? [InputControl(name = "dpad", layout = "Dpad", usage = "Hatswitch", displayName = "D-Pad", format = "BIT", sizeInBits = 4, bit = 0)] [InputControl(name = "buttonSouth", layout = "Button", bit = (uint)GamepadButton.South, usages = new[] { "PrimaryAction", "Submit" }, aliases = new[] { "a", "cross" }, displayName = "Button South", shortDisplayName = ButtonSouthShortDisplayName)] [InputControl(name = "buttonWest", layout = "Button", bit = (uint)GamepadButton.West, usage = "SecondaryAction", aliases = new[] { "x", "square" }, displayName = "Button West", shortDisplayName = ButtonWestShortDisplayName)] [InputControl(name = "buttonNorth", layout = "Button", bit = (uint)GamepadButton.North, aliases = new[] { "y", "triangle" }, displayName = "Button North", shortDisplayName = ButtonNorthShortDisplayName)] [InputControl(name = "buttonEast", layout = "Button", bit = (uint)GamepadButton.East, usages = new[] { "Back", "Cancel" }, aliases = new[] { "b", "circle" }, displayName = "Button East", shortDisplayName = ButtonEastShortDisplayName)] ////FIXME: 'Press' naming is inconsistent with 'Button' naming [InputControl(name = "leftStickPress", layout = "Button", bit = (uint)GamepadButton.LeftStick, displayName = "Left Stick Press")] [InputControl(name = "rightStickPress", layout = "Button", bit = (uint)GamepadButton.RightStick, displayName = "Right Stick Press")] [InputControl(name = "leftShoulder", layout = "Button", bit = (uint)GamepadButton.LeftShoulder, displayName = "Left Shoulder", shortDisplayName = "LB")] [InputControl(name = "rightShoulder", layout = "Button", bit = (uint)GamepadButton.RightShoulder, displayName = "Right Shoulder", shortDisplayName = "RB")] ////REVIEW: seems like these two should get less ambiguous names as well [InputControl(name = "start", layout = "Button", bit = (uint)GamepadButton.Start, usage = "Menu", displayName = "Start")] [InputControl(name = "select", layout = "Button", bit = (uint)GamepadButton.Select, displayName = "Select")] [FieldOffset(0)] public uint buttons; /// /// Left stick position. Each axis goes from -1 to 1 with /// 0 being center position. /// /// Left stick position. /// [InputControl(layout = "Stick", usage = "Primary2DMotion", processors = "stickDeadzone", displayName = "Left Stick", shortDisplayName = "LS")] [FieldOffset(4)] public Vector2 leftStick; /// /// Right stick position. Each axis from -1 to 1 with /// 0 being center position. /// /// Right stick position. /// [InputControl(layout = "Stick", usage = "Secondary2DMotion", processors = "stickDeadzone", displayName = "Right Stick", shortDisplayName = "RS")] [FieldOffset(12)] public Vector2 rightStick; ////REVIEW: should left and right trigger get deadzones? /// /// Position of the left trigger. Goes from 0 (not pressed) to 1 (fully pressed). /// /// Position of left trigger. /// [InputControl(layout = "Button", format = "FLT", usage = "SecondaryTrigger", displayName = "Left Trigger", shortDisplayName = "LT")] [FieldOffset(20)] public float leftTrigger; /// /// Position of the right trigger. Goes from 0 (not pressed) to 1 (fully pressed). /// /// Position of right trigger. /// [InputControl(layout = "Button", format = "FLT", usage = "SecondaryTrigger", displayName = "Right Trigger", shortDisplayName = "RT")] [FieldOffset(24)] public float rightTrigger; /// /// State format tag for GamepadState. /// /// Returns "GPAD". public FourCC format => Format; /// /// Create a gamepad state with the given buttons being pressed. /// /// Buttons to put into pressed state. /// is null. public GamepadState(params GamepadButton[] buttons) : this() { if (buttons == null) throw new ArgumentNullException(nameof(buttons)); foreach (var button in buttons) { Debug.Assert((int)button < 32, $"Expected button < 32, so we fit into the 32 bit wide bitmask"); var bit = 1U << (int)button; this.buttons |= bit; } } /// /// Set the specific buttons to be pressed or unpressed. /// /// A gamepad button. /// Whether to set to be pressed or not pressed in /// . /// GamepadState with a modified mask. public GamepadState WithButton(GamepadButton button, bool value = true) { Debug.Assert((int)button < 32, $"Expected button < 32, so we fit into the 32 bit wide bitmask"); var bit = 1U << (int)button; if (value) buttons |= bit; else buttons &= ~bit; return this; } } ////NOTE: The bit positions here based on the enum value are also used in native. /// /// Enum of common gamepad buttons. /// /// /// Can be used as an array indexer on the class to get individual button controls. /// public enum GamepadButton { // Dpad buttons. Important to be first in the bitfield as we'll // point the DpadControl to it. // IMPORTANT: Order has to match what is expected by DpadControl. /// /// The up button on a gamepad's dpad. /// DpadUp = 0, /// /// The down button on a gamepad's dpad. /// DpadDown = 1, /// /// The left button on a gamepad's dpad. /// DpadLeft = 2, /// /// The right button on a gamepad's dpad. /// DpadRight = 3, // Face buttons. We go with a north/south/east/west naming as that // clearly disambiguates where we expect the respective button to be. /// /// The upper action button on a gamepad. /// /// /// Identical to and which are the Xbox and PlayStation controller names for this button. /// North = 4, /// /// The right action button on a gamepad. /// /// /// Identical to and which are the Xbox and PlayStation controller names for this button. /// East = 5, /// /// The lower action button on a gamepad. /// /// /// Identical to and which are the Xbox and PlayStation controller names for this button. /// South = 6, /// /// The left action button on a gamepad. /// /// /// Identical to and which are the Xbox and PlayStation controller names for this button. /// West = 7, /// /// The button pressed by pressing down the left stick on a gamepad. /// LeftStick = 8, /// /// The button pressed by pressing down the right stick on a gamepad. /// RightStick = 9, /// /// The left shoulder button on a gamepad. /// LeftShoulder = 10, /// /// The right shoulder button on a gamepad. /// RightShoulder = 11, /// /// The start button. /// Start = 12, /// /// The select button. /// Select = 13, // For values that are not part of the buttons bitmask in GamepadState, assign large values that are outside // the 32bit bit range. /// /// The left trigger button on a gamepad. /// LeftTrigger = 32, /// /// The right trigger button on a gamepad. /// RightTrigger = 33, /// /// The X button on an Xbox controller. /// /// /// Identical to , which is the generic name of this button. /// X = West, /// /// The Y button on an Xbox controller. /// /// /// Identical to , which is the generic name of this button. /// Y = North, /// /// The A button on an Xbox controller. /// /// /// Identical to , which is the generic name of this button. /// A = South, /// /// The B button on an Xbox controller. /// /// /// Identical to , which is the generic name of this button. /// B = East, /// /// The cross button on a PlayStation controller. /// /// /// Identical to , which is the generic name of this button. /// Cross = South, /// /// The square button on a PlayStation controller. /// /// /// Identical to , which is the generic name of this button. /// Square = West, /// /// The triangle button on a PlayStation controller. /// /// /// Identical to , which is the generic name of this button. /// Triangle = North, /// /// The circle button on a PlayStation controller. /// /// /// Identical to , which is the generic name of this button. /// Circle = East, } } namespace UnityEngine.InputSystem { /// /// An Xbox-style gamepad with two sticks, a D-Pad, four face buttons, two triggers, /// two shoulder buttons, and two menu buttons that usually sit in the midsection of the gamepad. /// /// /// The Gamepad layout provides a standardized layouts for gamepads. Generally, if a specific /// device is represented as a Gamepad, the controls, such as the face buttons, are guaranteed /// to be mapped correctly and consistently. If, based on the set of supported devices available /// to the input system, this cannot be guaranteed, a given device is usually represented as a /// generic or as just a plain instead. /// /// /// /// // Show all gamepads in the system. /// Debug.Log(string.Join("\n", Gamepad.all)); /// /// // Check whether the X button on the current gamepad is pressed. /// if (Gamepad.current.xButton.wasPressedThisFrame) /// Debug.Log("Pressed"); /// /// // Rumble the left motor on the current gamepad slightly. /// Gamepad.current.SetMotorSpeeds(0.2f, 0. /// /// /// [InputControlLayout(stateType = typeof(GamepadState), isGenericTypeOfDevice = true)] public class Gamepad : InputDevice, IDualMotorRumble { /// /// The left face button of the gamepad. /// /// Control representing the X/Square face button. /// /// On an Xbox controller, this is the X button and on the PS4 controller, this is the /// square button. /// /// /// public ButtonControl buttonWest { get; protected set; } /// /// The top face button of the gamepad. /// /// Control representing the Y/Triangle face button. /// /// On an Xbox controller, this is the Y button and on the PS4 controller, this is the /// triangle button. /// /// /// public ButtonControl buttonNorth { get; protected set; } /// /// The bottom face button of the gamepad. /// /// Control representing the A/Cross face button. /// /// On an Xbox controller, this is the A button and on the PS4 controller, this is the /// cross button. /// /// /// public ButtonControl buttonSouth { get; protected set; } /// /// The right face button of the gamepad. /// /// Control representing the B/Circle face button. /// /// On an Xbox controller, this is the B button and on the PS4 controller, this is the /// circle button. /// /// /// public ButtonControl buttonEast { get; protected set; } /// /// The button that gets triggered when is pressed down. /// /// Control representing a click with the left stick. public ButtonControl leftStickButton { get; protected set; } /// /// The button that gets triggered when is pressed down. /// /// Control representing a click with the right stick. public ButtonControl rightStickButton { get; protected set; } /// /// The right button in the middle section of the gamepad (called "menu" on Xbox /// controllers and "options" on PS4 controllers). /// /// Control representing the right button in midsection. public ButtonControl startButton { get; protected set; } /// /// The left button in the middle section of the gamepad (called "view" on Xbox /// controllers and "share" on PS4 controllers). /// /// Control representing the left button in midsection. public ButtonControl selectButton { get; protected set; } /// /// The 4-way directional pad on the gamepad. /// /// Control representing the d-pad. public DpadControl dpad { get; protected set; } /// /// The left shoulder/bumper button that sits on top of . /// /// Control representing the left shoulder button. /// /// On Xbox controllers, this is usually called "left bumper" whereas on PS4 /// controllers, this button is referred to as "L1". /// public ButtonControl leftShoulder { get; protected set; } /// /// The right shoulder/bumper button that sits on top of . /// /// Control representing the right shoulder button. /// /// On Xbox controllers, this is usually called "right bumper" whereas on PS4 /// controllers, this button is referred to as "R1". /// public ButtonControl rightShoulder { get; protected set; } /// /// The left thumbstick on the gamepad. /// /// Control representing the left thumbstick. public StickControl leftStick { get; protected set; } /// /// The right thumbstick on the gamepad. /// /// Control representing the right thumbstick. public StickControl rightStick { get; protected set; } /// /// The left trigger button sitting below . /// /// Control representing the left trigger button. /// /// On PS4 controllers, this button is referred to as "L2". /// public ButtonControl leftTrigger { get; protected set; } /// /// The right trigger button sitting below . /// /// Control representing the right trigger button. /// /// On PS4 controllers, this button is referred to as "R2". /// public ButtonControl rightTrigger { get; protected set; } /// /// Same as . Xbox-style alias. /// /// Same as . public ButtonControl aButton => buttonSouth; /// /// Same as . Xbox-style alias. /// /// Same as . public ButtonControl bButton => buttonEast; /// /// Same as Xbox-style alias. /// /// Same as . public ButtonControl xButton => buttonWest; /// /// Same as . Xbox-style alias. /// /// Same as . public ButtonControl yButton => buttonNorth; /// /// Same as . PS4-style alias. /// /// Same as . public ButtonControl triangleButton => buttonNorth; /// /// Same as . PS4-style alias. /// /// Same as . public ButtonControl squareButton => buttonWest; /// /// Same as . PS4-style alias. /// /// Same as . public ButtonControl circleButton => buttonEast; /// /// Same as . PS4-style alias. /// /// Same as . public ButtonControl crossButton => buttonSouth; /// /// Retrieve a gamepad button by its enumeration /// constant. /// /// Button to retrieve. /// is not a valid gamepad /// button value. public ButtonControl this[GamepadButton button] { get { switch (button) { case GamepadButton.North: return buttonNorth; case GamepadButton.South: return buttonSouth; case GamepadButton.East: return buttonEast; case GamepadButton.West: return buttonWest; case GamepadButton.Start: return startButton; case GamepadButton.Select: return selectButton; case GamepadButton.LeftShoulder: return leftShoulder; case GamepadButton.RightShoulder: return rightShoulder; case GamepadButton.LeftTrigger: return leftTrigger; case GamepadButton.RightTrigger: return rightTrigger; case GamepadButton.LeftStick: return leftStickButton; case GamepadButton.RightStick: return rightStickButton; case GamepadButton.DpadUp: return dpad.up; case GamepadButton.DpadDown: return dpad.down; case GamepadButton.DpadLeft: return dpad.left; case GamepadButton.DpadRight: return dpad.right; default: throw new InvalidEnumArgumentException(nameof(button), (int)button, typeof(GamepadButton)); } } } /// /// The gamepad last used/connected by the player or null if there is no gamepad connected /// to the system. /// /// /// When added, a device is automatically made current (see ), so /// when connecting a gamepad, it will also become current. After that, it will only become current again /// when input change on non-noisy controls (see ) is received. /// /// For local multiplayer scenarios (or whenever there are multiple gamepads that need to be usable /// in a concurrent fashion), it is not recommended to rely on this property. Instead, it is recommended /// to use or . /// /// /// public static Gamepad current { get; private set; } /// /// A list of gamepads currently connected to the system. /// /// All currently connected gamepads. /// /// Does not cause GC allocation. /// /// Do not hold on to the value returned by this getter but rather query it whenever /// you need it. Whenever the gamepad setup changes, the value returned by this getter /// is invalidated. /// /// public new static ReadOnlyArray all => new ReadOnlyArray(s_Gamepads, 0, s_GamepadCount); /// protected override void FinishSetup() { ////REVIEW: what's actually faster/better... storing these in properties or doing the lookup on the fly? buttonWest = GetChildControl("buttonWest"); buttonNorth = GetChildControl("buttonNorth"); buttonSouth = GetChildControl("buttonSouth"); buttonEast = GetChildControl("buttonEast"); startButton = GetChildControl("start"); selectButton = GetChildControl("select"); leftStickButton = GetChildControl("leftStickPress"); rightStickButton = GetChildControl("rightStickPress"); dpad = GetChildControl("dpad"); leftShoulder = GetChildControl("leftShoulder"); rightShoulder = GetChildControl("rightShoulder"); leftStick = GetChildControl("leftStick"); rightStick = GetChildControl("rightStick"); leftTrigger = GetChildControl("leftTrigger"); rightTrigger = GetChildControl("rightTrigger"); base.FinishSetup(); } /// /// Make the gamepad the gamepad. /// /// /// This is called automatically by the system when there is input on a gamepad. /// public override void MakeCurrent() { base.MakeCurrent(); current = this; } /// /// Called when the gamepad is added to the system. /// protected override void OnAdded() { ArrayHelpers.AppendWithCapacity(ref s_Gamepads, ref s_GamepadCount, this); } /// /// Called when the gamepad is removed from the system. /// protected override void OnRemoved() { if (current == this) current = null; // Remove from `all`. var index = ArrayHelpers.IndexOfReference(s_Gamepads, this, s_GamepadCount); if (index != -1) ArrayHelpers.EraseAtWithCapacity(s_Gamepads, ref s_GamepadCount, index); else { Debug.Assert(false, $"Gamepad {this} seems to not have been added but is being removed (gamepad list: {string.Join(", ", all)})"); // Put in else to not allocate on normal path. } } /// /// Pause rumble effects on the gamepad. Resume with . /// /// public virtual void PauseHaptics() { m_Rumble.PauseHaptics(this); } /// /// Resume rumble affects on the gamepad that have been paused with . /// /// public virtual void ResumeHaptics() { m_Rumble.ResumeHaptics(this); } /// /// Reset rumble effects on the gamepad. Puts the gamepad rumble motors back into their /// default state. /// /// public virtual void ResetHaptics() { m_Rumble.ResetHaptics(this); } /// public virtual void SetMotorSpeeds(float lowFrequency, float highFrequency) { m_Rumble.SetMotorSpeeds(this, lowFrequency, highFrequency); } private DualMotorRumble m_Rumble; private static int s_GamepadCount; private static Gamepad[] s_Gamepads; } }