William Lebel a5fa34f8b0 Add custom control scheme for new input asset
Doesn't work yet with arcade inputs
2023-05-28 16:21:51 -04:00

339 lines
14 KiB
C#

#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
{
/// <summary>
/// A collection of utility functions for working with arrays.
/// </summary>
/// <remarks>
/// The goal of this collection is to make it easy to use arrays directly rather than resorting to
/// <see cref="List{T}"/>.
/// </remarks>
internal static class ArrayHelpers
{
public static int IndexOfReference<TFirst, TSecond>(this TFirst[] array, TSecond value, int count = -1)
where TSecond : class
where TFirst : TSecond
{
return IndexOfReference(array, value, 0, count);
}
public static int IndexOfReference<TFirst, TSecond>(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<TValue>(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<TValue>(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
{
/// <summary>
/// Default state layout for Conjure Arcade controllers.
/// </summary>
/// <seealso cref="ConjureArcadeController"/>
[StructLayout(LayoutKind.Explicit, Size = 28)]
public struct ConjureArcadeControllerState : IInputStateTypeInfo
{
public static FourCC Format => new FourCC('C', 'N', 'J', 'A');
/// <summary>
/// Button bit mask.
/// </summary>
/// <value>Button bit mask.</value>
/// <seealso cref="ConjureArcadeControllerButton"/>
/// <seealso cref="Gamepad.buttonSouth"/>
/// <seealso cref="Gamepad.buttonNorth"/>
/// <seealso cref="Gamepad.buttonWest"/>
/// <seealso cref="Gamepad.buttonSouth"/>
/// <seealso cref="Gamepad.leftShoulder"/>
/// <seealso cref="Gamepad.rightShoulder"/>
/// <seealso cref="Gamepad.startButton"/>
/// <seealso cref="Gamepad.selectButton"/>
[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;
/// <summary>
/// Stick position. Each axis goes from -1 to 1 with
/// 0 being center position.
/// </summary>
/// <value>Left stick position.</value>
/// <seealso cref="ConjureArcadeController.stick"/>
[InputControl(layout = "Stick", usage = "Primary2DMotion", processors = "stickDeadzone", displayName = "Stick", shortDisplayName = "S")]
[FieldOffset(4)]
public Vector2 stick;
/// <summary>
/// State format tag for GamepadState.
/// </summary>
/// <value>Returns "CNJA".</value>
public FourCC format => Format;
/// <summary>
/// Create a gamepad state with the given buttons being pressed.
/// </summary>
/// <param name="buttons">Buttons to put into pressed state.</param>
/// <exception cref="ArgumentNullException"><paramref name="buttons"/> is <c>null</c>.</exception>
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;
}
}
/// <summary>
/// Set the specific buttons to be pressed or unpressed.
/// </summary>
/// <param name="button">A gamepad button.</param>
/// <param name="value">Whether to set <paramref name="button"/> to be pressed or not pressed in
/// <see cref="buttons"/>.</param>
/// <returns>GamepadState with a modified <see cref="buttons"/> mask.</returns>
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; }
/// <summary>
/// Retrieve a gamepad button by its <see cref="ConjureArcadeControllerButton"/> enumeration
/// constant.
/// </summary>
/// <param name="button">Button to retrieve.</param>
/// <exception cref="ArgumentException"><paramref name="button"/> is not a valid gamepad
/// button value.</exception>
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));
}
}
}
/// <summary>
/// The gamepad last used/connected by the player or <c>null</c> if there is no gamepad connected
/// to the system.
/// </summary>
/// <remarks>
/// When added, a device is automatically made current (see <see cref="InputDevice.MakeCurrent"/>), 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 <see cref="InputControl.noisy"/>) 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 <see cref="PlayerInput"/> or <see cref="InputUser"/>.
/// </remarks>
/// <seealso cref="InputDevice.MakeCurrent"/>
/// <seealso cref="all"/>
public static ConjureArcadeController current { get; private set; }
/// <summary>
/// A list of gamepads currently connected to the system.
/// </summary>
/// <value>All currently connected gamepads.</value>
/// <remarks>
/// Does not cause GC allocation.
///
/// Do <em>not</em> 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.
/// </remarks>
/// <seealso cref="current"/>
public new static ReadOnlyArray<ConjureArcadeController> all => new(s_Gamepads, 0, s_GamepadCount);
/// <inheritdoc />
protected override void FinishSetup()
{
buttonA = GetChildControl<ButtonControl>("buttonA");
buttonB = GetChildControl<ButtonControl>("buttonB");
buttonC = GetChildControl<ButtonControl>("buttonC");
button1 = GetChildControl<ButtonControl>("button1");
button2 = GetChildControl<ButtonControl>("button2");
button3 = GetChildControl<ButtonControl>("button3");
buttonStart = GetChildControl<ButtonControl>("buttonStart");
buttonPower = GetChildControl<ButtonControl>("buttonPower");
stick = GetChildControl<StickControl>("stick");
base.FinishSetup();
}
/// <summary>
/// Make the gamepad the <see cref="current"/> gamepad.
/// </summary>
/// <remarks>
/// This is called automatically by the system when there is input on a gamepad.
/// </remarks>
public override void MakeCurrent()
{
base.MakeCurrent();
current = this;
}
/// <summary>
/// Called when the gamepad is added to the system.
/// </summary>
protected override void OnAdded()
{
ArrayHelpers.AppendWithCapacity(ref s_Gamepads, ref s_GamepadCount, this);
}
/// <summary>
/// Called when the gamepad is removed from the system.
/// </summary>
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