#if UNITY_EDITOR || UNITY_ANDROID || PACKAGE_DOCS_GENERATION using System; using System.Linq; using System.Runtime.InteropServices; using UnityEngine.InputSystem.Layouts; using UnityEngine.InputSystem.LowLevel; using UnityEngine.InputSystem.Android.LowLevel; using UnityEngine.InputSystem.DualShock; using UnityEngine.InputSystem.Utilities; namespace UnityEngine.InputSystem.Android.LowLevel { /// /// Default state layout for Android game controller. /// [StructLayout(LayoutKind.Sequential)] public unsafe struct AndroidGameControllerState : IInputStateTypeInfo { public const int MaxAxes = 48; public const int MaxButtons = 220; public class Variants { public const string Gamepad = "Gamepad"; public const string Joystick = "Joystick"; public const string DPadAxes = "DpadAxes"; public const string DPadButtons = "DpadButtons"; } internal const uint kAxisOffset = sizeof(uint) * (uint)((MaxButtons + 31) / 32); public static FourCC kFormat = new FourCC('A', 'G', 'C', ' '); [InputControl(name = "dpad", layout = "Dpad", bit = (uint)AndroidKeyCode.DpadUp, sizeInBits = 4, variants = Variants.DPadButtons)] [InputControl(name = "dpad/up", bit = (uint)AndroidKeyCode.DpadUp, variants = Variants.DPadButtons)] [InputControl(name = "dpad/down", bit = (uint)AndroidKeyCode.DpadDown, variants = Variants.DPadButtons)] [InputControl(name = "dpad/left", bit = (uint)AndroidKeyCode.DpadLeft, variants = Variants.DPadButtons)] [InputControl(name = "dpad/right", bit = (uint)AndroidKeyCode.DpadRight, variants = Variants.DPadButtons)] [InputControl(name = "buttonSouth", bit = (uint)AndroidKeyCode.ButtonA, variants = Variants.Gamepad)] [InputControl(name = "buttonWest", bit = (uint)AndroidKeyCode.ButtonX, variants = Variants.Gamepad)] [InputControl(name = "buttonNorth", bit = (uint)AndroidKeyCode.ButtonY, variants = Variants.Gamepad)] [InputControl(name = "buttonEast", bit = (uint)AndroidKeyCode.ButtonB, variants = Variants.Gamepad)] [InputControl(name = "leftStickPress", bit = (uint)AndroidKeyCode.ButtonThumbl, variants = Variants.Gamepad)] [InputControl(name = "rightStickPress", bit = (uint)AndroidKeyCode.ButtonThumbr, variants = Variants.Gamepad)] [InputControl(name = "leftShoulder", bit = (uint)AndroidKeyCode.ButtonL1, variants = Variants.Gamepad)] [InputControl(name = "rightShoulder", bit = (uint)AndroidKeyCode.ButtonR1, variants = Variants.Gamepad)] [InputControl(name = "start", bit = (uint)AndroidKeyCode.ButtonStart, variants = Variants.Gamepad)] [InputControl(name = "select", bit = (uint)AndroidKeyCode.ButtonSelect, variants = Variants.Gamepad)] public fixed uint buttons[(MaxButtons + 31) / 32]; [InputControl(name = "dpad", layout = "Dpad", offset = (uint)AndroidAxis.HatX * sizeof(float) + kAxisOffset, format = "VEC2", sizeInBits = 64, variants = Variants.DPadAxes)] [InputControl(name = "dpad/right", offset = 0, bit = 0, sizeInBits = 32, format = "FLT", parameters = "clamp=3,clampConstant=0,clampMin=0,clampMax=1", variants = Variants.DPadAxes)] [InputControl(name = "dpad/left", offset = 0, bit = 0, sizeInBits = 32, format = "FLT", parameters = "clamp=3,clampConstant=0,clampMin=-1,clampMax=0,invert", variants = Variants.DPadAxes)] [InputControl(name = "dpad/down", offset = ((uint)AndroidAxis.HatY - (uint)AndroidAxis.HatX) * sizeof(float), bit = 0, sizeInBits = 32, format = "FLT", parameters = "clamp=3,clampConstant=0,clampMin=0,clampMax=1", variants = Variants.DPadAxes)] [InputControl(name = "dpad/up", offset = ((uint)AndroidAxis.HatY - (uint)AndroidAxis.HatX) * sizeof(float), bit = 0, sizeInBits = 32, format = "FLT", parameters = "clamp=3,clampConstant=0,clampMin=-1,clampMax=0,invert", variants = Variants.DPadAxes)] [InputControl(name = "leftTrigger", offset = (uint)AndroidAxis.Brake * sizeof(float) + kAxisOffset, parameters = "clamp=1,clampMin=0,clampMax=1.0", variants = Variants.Gamepad)] [InputControl(name = "rightTrigger", offset = (uint)AndroidAxis.Gas * sizeof(float) + kAxisOffset, parameters = "clamp=1,clampMin=0,clampMax=1.0", variants = Variants.Gamepad)] [InputControl(name = "leftStick", variants = Variants.Gamepad)] [InputControl(name = "leftStick/y", variants = Variants.Gamepad, parameters = "invert")] [InputControl(name = "leftStick/up", variants = Variants.Gamepad, parameters = "invert,clamp=1,clampMin=-1.0,clampMax=0.0")] [InputControl(name = "leftStick/down", variants = Variants.Gamepad, parameters = "invert=false,clamp=1,clampMin=0,clampMax=1.0")] ////FIXME: state for this control is not contiguous [InputControl(name = "rightStick", offset = (uint)AndroidAxis.Z * sizeof(float) + kAxisOffset, sizeInBits = ((uint)AndroidAxis.Rz - (uint)AndroidAxis.Z + 1) * sizeof(float) * 8, variants = Variants.Gamepad)] [InputControl(name = "rightStick/x", variants = Variants.Gamepad)] [InputControl(name = "rightStick/y", offset = ((uint)AndroidAxis.Rz - (uint)AndroidAxis.Z) * sizeof(float), variants = Variants.Gamepad, parameters = "invert")] [InputControl(name = "rightStick/up", offset = ((uint)AndroidAxis.Rz - (uint)AndroidAxis.Z) * sizeof(float), variants = Variants.Gamepad, parameters = "invert,clamp=1,clampMin=-1.0,clampMax=0.0")] [InputControl(name = "rightStick/down", offset = ((uint)AndroidAxis.Rz - (uint)AndroidAxis.Z) * sizeof(float), variants = Variants.Gamepad, parameters = "invert=false,clamp=1,clampMin=0,clampMax=1.0")] public fixed float axis[MaxAxes]; public FourCC format { get { return kFormat; } } public AndroidGameControllerState WithButton(AndroidKeyCode code, bool value = true) { fixed(uint* buttonsPtr = buttons) { if (value) buttonsPtr[(int)code / 32] |= 1U << ((int)code % 32); else buttonsPtr[(int)code / 32] &= ~(1U << ((int)code % 32)); } return this; } public AndroidGameControllerState WithAxis(AndroidAxis axis, float value) { fixed(float* axisPtr = this.axis) { axisPtr[(int)axis] = value; } return this; } } // See https://developer.android.com/reference/android/view/InputDevice.html for input source values internal enum AndroidInputSource { Keyboard = 257, Dpad = 513, Gamepad = 1025, Touchscreen = 4098, Mouse = 8194, Stylus = 16386, Trackball = 65540, Touchpad = 1048584, Joystick = 16777232 } [Serializable] internal struct AndroidDeviceCapabilities { public string deviceDescriptor; public int productId; public int vendorId; public bool isVirtual; public AndroidAxis[] motionAxes; public AndroidInputSource inputSources; public string ToJson() { return JsonUtility.ToJson(this); } public static AndroidDeviceCapabilities FromJson(string json) { if (json == null) throw new ArgumentNullException(nameof(json)); return JsonUtility.FromJson(json); } public override string ToString() { return $"deviceDescriptor = {deviceDescriptor}, productId = {productId}, vendorId = {vendorId}, isVirtual = {isVirtual}, motionAxes = {(motionAxes == null ? "" : String.Join(",", motionAxes.Select(i => i.ToString()).ToArray()))}, inputSources = {inputSources}"; } } } namespace UnityEngine.InputSystem.Android { /// /// Represents a gamepad device on Android, providing unified support for various controller types. /// /// /// This layout covers multiple Android-supported gamepads, including but not limited to: /// - ELAN PLAYSTATION(R)3 Controller /// - My-Power CO., LTD. PS(R) Controller Adaptor /// - Sony Interactive Entertainment Wireless (PS4 DualShock) /// - Xbox Wireless Controller (Xbox One) /// - NVIDIA Controller v01.03/v01.04 /// - (More may be added later) /// /// ### Typical Android Axis and Key Mappings /// | Control | Mapping | /// |------------------------|----------------------------------------------------| /// | Left Stick | AXIS_X(0) / AXIS_Y(1) | /// | Right Stick | AXIS_Z(11) / AXIS_RZ(14) | /// | L1 | KEYCODE_BUTTON_L1(102) | /// | R1 | KEYCODE_BUTTON_R1(103) | /// | L2 | AXIS_BRAKE(23) | /// | R2 | AXIS_GAS(22) | /// | Left Thumb | KEYCODE_BUTTON_THUMBL(106) | /// | Right Thumb | KEYCODE_BUTTON_THUMBR(107) | /// | X | KEYCODE_BUTTON_X(99) | /// | Y | KEYCODE_BUTTON_Y(100) | /// | B | KEYCODE_BUTTON_B(97) | /// | A | KEYCODE_BUTTON_A(96) | /// | DPAD | AXIS_HAT_X(15), AXIS_HAT_Y(16) or KEYCODE_DPAD_* | /// /// ### Notes /// - **NVIDIA Shield Console** /// - The L2 and R2 triggers generate both `AXIS_BRAKE` / `AXIS_GAS` and `AXIS_LTRIGGER` / `AXIS_RTRIGGER` events. /// - On most Android phones, only `AXIS_BRAKE` and `AXIS_GAS` are reported; `AXIS_LTRIGGER` and `AXIS_RTRIGGER` are not invoked. /// - For consistency across devices, triggers are mapped exclusively to `AXIS_BRAKE` and `AXIS_GAS`. /// - The Shield also reports `KEYCODE_BACK` instead of `KEYCODE_BUTTON_SELECT`, causing the **Options** (Xbox), **View** (DualShock), or **Select** buttons to be non-functional. /// /// - **PS4 Controller Compatibility** /// - Official PS4 controller support is available starting from **Android 10 and later** /// (see: https://playstation.com/en-us/support/hardware/ps4-pair-dualshock-4-wireless-with-sony-xperia-and-android). /// - On older Android versions, driver implementations vary by manufacturer. Some vendors have partially fixed DualShock support in custom drivers, leading to inconsistent mappings. /// /// - **Driver-Dependent Behavior** /// - Gamepad mappings may differ even between devices running the *same Android version*. /// - For example, on **Android 8.0**: /// - **NVIDIA Shield Console:** buttons map correctly according to `AndroidGameControllerState` (for example, `L1 → ButtonL1`, `R1 → ButtonR1`). /// - **Samsung Galaxy S9 / S8** and **Xiaomi Mi Note2:** mappings are inconsistent (for example, `L1 → ButtonY`, `R1 → ButtonZ`). /// - These discrepancies stem from device-specific **driver differences**, not the Android OS itself. /// /// Because mapping inconsistencies depend on vendor-specific drivers, it’s impractical to maintain per-device remaps. /// [InputControlLayout(stateType = typeof(AndroidGameControllerState), variants = AndroidGameControllerState.Variants.Gamepad)] public class AndroidGamepad : Gamepad { } /// /// Generic controller with Dpad axes /// [InputControlLayout(stateType = typeof(AndroidGameControllerState), hideInUI = true, variants = AndroidGameControllerState.Variants.Gamepad + InputControlLayout.VariantSeparator + AndroidGameControllerState.Variants.DPadAxes)] public class AndroidGamepadWithDpadAxes : AndroidGamepad { } /// /// Generic controller with Dpad buttons /// [InputControlLayout(stateType = typeof(AndroidGameControllerState), hideInUI = true, variants = AndroidGameControllerState.Variants.Gamepad + InputControlLayout.VariantSeparator + AndroidGameControllerState.Variants.DPadButtons)] public class AndroidGamepadWithDpadButtons : AndroidGamepad { } /// /// Joystick on Android. /// [InputControlLayout(stateType = typeof(AndroidGameControllerState), variants = AndroidGameControllerState.Variants.Joystick)] public class AndroidJoystick : Joystick { } /// /// A PlayStation DualShock 4 controller connected to an Android device. /// [InputControlLayout(stateType = typeof(AndroidGameControllerState), displayName = "Android DualShock 4 Gamepad", variants = AndroidGameControllerState.Variants.Gamepad + InputControlLayout.VariantSeparator + AndroidGameControllerState.Variants.DPadAxes)] public class DualShock4GamepadAndroid : DualShockGamepad { } /// /// An XboxOne controller connected to an Android device. /// [InputControlLayout(stateType = typeof(AndroidGameControllerState), displayName = "Android Xbox One Controller", variants = AndroidGameControllerState.Variants.Gamepad + InputControlLayout.VariantSeparator + AndroidGameControllerState.Variants.DPadAxes)] public class XboxOneGamepadAndroid : XInput.XInputController { } } #endif // UNITY_EDITOR || UNITY_ANDROID