#if ENABLE_INPUT_SYSTEM using System; using System.ComponentModel; using System.Runtime.InteropServices; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.InputSystem.Layouts; using UnityEngine.InputSystem.LowLevel; using UnityEngine.InputSystem.Utilities; using ConjureOS.Input.LowLevel; using UnityEngine.InputSystem.Users; using System.Collections.Generic; using ArrayHelpers = ConjureOS.Input.Utilities.ArrayHelpers; namespace ConjureOS.Input.Utilities { /// /// A collection of utility functions for working with arrays. /// /// /// The goal of this collection is to make it easy to use arrays directly rather than resorting to /// . /// internal static class ArrayHelpers { public static int IndexOfReference(this TFirst[] array, TSecond value, int count = -1) where TSecond : class where TFirst : TSecond { return IndexOfReference(array, value, 0, count); } public static int IndexOfReference(this TFirst[] array, TSecond value, int startIndex, int count) where TSecond : class where TFirst : TSecond { if (array == null) return -1; if (count < 0) count = array.Length - startIndex; for (var i = startIndex; i < startIndex + count; ++i) if (ReferenceEquals(array[i], value)) return i; return -1; } public static int AppendWithCapacity(ref TValue[] array, ref int count, TValue value, int capacityIncrement = 10) { if (array == null) { array = new TValue[capacityIncrement]; array[0] = value; ++count; return 0; } var capacity = array.Length; if (capacity == count) { capacity += capacityIncrement; Array.Resize(ref array, capacity); } var index = count; array[index] = value; ++count; return index; } public static void EraseAtWithCapacity(this TValue[] array, ref int count, int index) { Debug.Assert(array != null); Debug.Assert(count <= array.Length); Debug.Assert(index >= 0 && index < count); // If we're erasing from the beginning or somewhere in the middle, move // the array contents down from after the index. if (index < count - 1) { Array.Copy(array, index + 1, array, index, count - index - 1); } array[count - 1] = default; // Tail has been moved down by one. --count; } } } namespace ConjureOS.Input.LowLevel { /// /// Default state layout for Conjure Arcade controllers. /// /// [StructLayout(LayoutKind.Explicit, Size = 28)] public struct ConjureArcadeControllerState : IInputStateTypeInfo { public static FourCC Format => new FourCC('C', 'N', 'J', 'A'); /// /// Button bit mask. /// /// Button bit mask. /// /// /// /// /// /// /// /// /// [InputControl(name = "buttonA", layout = "Button", bit = (uint)ConjureArcadeControllerButton.ButtonA, displayName = "Button A", shortDisplayName = "A")] [InputControl(name = "buttonB", layout = "Button", bit = (uint)ConjureArcadeControllerButton.ButtonB, displayName = "Button B", shortDisplayName = "B")] [InputControl(name = "buttonC", layout = "Button", bit = (uint)ConjureArcadeControllerButton.ButtonC, displayName = "Button C", shortDisplayName = "C")] [InputControl(name = "button1", layout = "Button", bit = (uint)ConjureArcadeControllerButton.Button1, displayName = "Button 1", shortDisplayName = "1")] [InputControl(name = "button2", layout = "Button", bit = (uint)ConjureArcadeControllerButton.Button2, displayName = "Button 2", shortDisplayName = "2")] [InputControl(name = "button3", layout = "Button", bit = (uint)ConjureArcadeControllerButton.Button3, displayName = "Button 3", shortDisplayName = "3")] [InputControl(name = "buttonStart", layout = "Button", bit = (uint)ConjureArcadeControllerButton.ButtonStart, usages = new[] { "Join", "Pause" }, displayName = "Button Start", shortDisplayName = "Start")] [InputControl(name = "buttonPower", layout = "Button", bit = (uint)ConjureArcadeControllerButton.ButtonPower, usages = new[] { "Power", "Exit" }, displayName = "Button Power", shortDisplayName = "Power")] [FieldOffset(0)] public uint buttons; /// /// 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 = "Stick", shortDisplayName = "S")] [FieldOffset(4)] public Vector2 stick; /// /// State format tag for GamepadState. /// /// Returns "CNJA". public FourCC format => Format; /// /// Create a gamepad state with the given buttons being pressed. /// /// Buttons to put into pressed state. /// is null. public ConjureArcadeControllerState(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 ConjureArcadeControllerState 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; } } public enum ConjureArcadeControllerButton { ButtonA = 0, ButtonB = 1, ButtonC = 2, Button1 = 3, Button2 = 4, Button3 = 5, ButtonStart = 6, ButtonPower = 7, } } namespace ConjureOS.Input { [InputControlLayout(stateType = typeof(ConjureArcadeControllerState), displayName = "Conjure Arcade Controller")] public class ConjureArcadeController : InputDevice { public ButtonControl buttonA { get; protected set; } public ButtonControl buttonB { get; protected set; } public ButtonControl buttonC { get; protected set; } public ButtonControl button1 { get; protected set; } public ButtonControl button2 { get; protected set; } public ButtonControl button3 { get; protected set; } public ButtonControl buttonStart { get; protected set; } public ButtonControl buttonPower { get; protected set; } public StickControl stick { get; protected set; } /// /// Retrieve a gamepad button by its enumeration /// constant. /// /// Button to retrieve. /// is not a valid gamepad /// button value. public ButtonControl this[ConjureArcadeControllerButton button] { get { switch (button) { case ConjureArcadeControllerButton.ButtonA: return buttonA; case ConjureArcadeControllerButton.ButtonB: return buttonB; case ConjureArcadeControllerButton.ButtonC: return buttonC; case ConjureArcadeControllerButton.Button1: return button1; case ConjureArcadeControllerButton.Button2: return button2; case ConjureArcadeControllerButton.Button3: return button3; case ConjureArcadeControllerButton.ButtonStart: return buttonStart; case ConjureArcadeControllerButton.ButtonPower: return buttonPower; 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 ConjureArcadeController 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(s_Gamepads, 0, s_GamepadCount); /// protected override void FinishSetup() { buttonA = GetChildControl("buttonA"); buttonB = GetChildControl("buttonB"); buttonC = GetChildControl("buttonC"); button1 = GetChildControl("button1"); button2 = GetChildControl("button2"); button3 = GetChildControl("button3"); buttonStart = GetChildControl("buttonStart"); buttonPower = GetChildControl("buttonPower"); stick = GetChildControl("stick"); 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. } } private static int s_GamepadCount; private static ConjureArcadeController[] s_Gamepads; } } #endif