Transposer/InputManager.cs
namespace Sandbox.Transposer;
/// <summary>
/// Input wrapper that provides static held/just-pressed booleans
/// for the Transposer game controls.
///
/// Must be updated once per frame before entity updates — call
/// <see cref="Update"/> from the game entry point component.
///
/// Uses the engine action system (Input.Down / Input.Pressed) for WASD+Space
/// which gives proper held-state detection. Arrow keys are not bound by default
/// but can be added via the Input.config action bindings.
/// Analog stick (controller) input is also supported via Input.AnalogMove.
/// </summary>
public static class InputManager
{
// Analog stick deadzone — below this magnitude the stick is treated as neutral.
private const float ANALOG_DEADZONE = 0.3f;
// Previous-frame analog directions, used to generate JustPressed edges.
private static bool _prevAnalogLeft;
private static bool _prevAnalogRight;
private static bool _prevAnalogUp;
private static bool _prevAnalogDown;
// ── Analog speed scale (1.0 for keyboard, 0–1 proportional for controller) ──
public static float MoveSpeedScale { get; private set; }
// ── Held state ───────────────────────────────────────────────────────
public static bool LeftPressed { get; private set; }
public static bool RightPressed { get; private set; }
public static bool UpPressed { get; private set; }
public static bool DownPressed { get; private set; }
public static bool SpacePressed { get; private set; }
// ── Just-pressed edge detection ──────────────────────────────────────
public static bool LeftJustPressed { get; private set; }
public static bool RightJustPressed { get; private set; }
public static bool UpJustPressed { get; private set; }
public static bool DownJustPressed { get; private set; }
public static bool SpaceJustPressed { get; private set; }
/// <summary>
/// Poll input and update all state.
/// Uses the action system for WASD+Space, raw keyboard for arrow keys,
/// and Input.AnalogMove for controller left stick.
/// Call once per frame before any game logic runs.
/// </summary>
public static void Update()
{
// Controller left stick — map to cardinal directions via deadzone.
var stick = Input.AnalogMove;
bool analogLeft = stick.y > ANALOG_DEADZONE;
bool analogRight = stick.y < -ANALOG_DEADZONE;
bool analogUp = stick.x > ANALOG_DEADZONE;
bool analogDown = stick.x < -ANALOG_DEADZONE;
// Speed scale: remap stick magnitude from [deadzone, 1] → [0, 1].
// Keyboard always gives full speed; controller scales with stick pressure.
bool anyAnalog = analogLeft || analogRight || analogUp || analogDown;
if ( Input.UsingController && anyAnalog )
{
float raw = stick.Length;
MoveSpeedScale = MathX.Clamp( (raw - ANALOG_DEADZONE) / (1f - ANALOG_DEADZONE), 0f, 1f );
}
else
{
MoveSpeedScale = 1f;
}
// Held state: action system (WASD) + raw keyboard (arrows) + analog stick.
LeftPressed = Input.Down( "Left" ) || Input.Keyboard.Down( "LEFTARROW" ) || analogLeft;
RightPressed = Input.Down( "Right" ) || Input.Keyboard.Down( "RIGHTARROW" ) || analogRight;
UpPressed = Input.Down( "Up" ) || Input.Keyboard.Down( "UPARROW" ) || analogUp;
DownPressed = Input.Down( "Down" ) || Input.Keyboard.Down( "DOWNARROW" ) || analogDown;
SpacePressed = Input.Down( "Start" );
// Just-pressed: action system (WASD) + raw keyboard (arrows) + analog stick rising edge.
LeftJustPressed = Input.Pressed( "Left" ) || Input.Keyboard.Pressed( "LEFTARROW" ) || (analogLeft && !_prevAnalogLeft);
RightJustPressed = Input.Pressed( "Right" ) || Input.Keyboard.Pressed( "RIGHTARROW" ) || (analogRight && !_prevAnalogRight);
UpJustPressed = Input.Pressed( "Up" ) || Input.Keyboard.Pressed( "UPARROW" ) || (analogUp && !_prevAnalogUp);
DownJustPressed = Input.Pressed( "Down" ) || Input.Keyboard.Pressed( "DOWNARROW" ) || (analogDown && !_prevAnalogDown);
SpaceJustPressed = Input.Pressed( "Start" );
_prevAnalogLeft = analogLeft;
_prevAnalogRight = analogRight;
_prevAnalogUp = analogUp;
_prevAnalogDown = analogDown;
}
}