Static input helper for the game. Maps named input actions (WASD, arrows, numpad) to Direction intents, provides methods to read newly pressed turn intents into a caller-supplied array, and exposes simple boolean checks for Confirm, Pause, Restart, and any-direction presses.
namespace Coilgarden;
/// <summary>
/// Turns key presses into direction intents, and nothing else. The layer boundary the
/// whole project rests on is that the simulation never reads a keyboard - so this class
/// hands out intents and has no idea what is done with them.
/// <para>
/// Every heading has three interchangeable bindings: WASD, the arrow cluster and the
/// numpad. Nothing downstream can tell which one produced an intent, so a player can swap
/// hands mid-run. The bindings live in <c>ProjectSettings/Input.config</c> as named
/// actions rather than as raw scancodes here, which is what will make them rebindable
/// later without touching this file.
/// </para>
/// </summary>
public static class GameInput
{
private static readonly (Direction Direction, string[] Actions)[] Bindings =
{
(Direction.Up, new[] { "Forward", "ArrowUp", "NumpadUp" }),
(Direction.Right, new[] { "Right", "ArrowRight", "NumpadRight" }),
(Direction.Down, new[] { "Backward", "ArrowDown", "NumpadDown" }),
(Direction.Left, new[] { "Left", "ArrowLeft", "NumpadLeft" })
};
/// <summary>How many intents a single frame can possibly produce.</summary>
public const int MaxIntentsPerFrame = 4;
/// <summary>
/// Writes the headings newly pressed this frame into <paramref name="into"/> and
/// returns how many were written.
/// <para>
/// Presses are reported rather than held keys, and all of them rather than the first,
/// because the snake buffers turns: on a frame where a player rounds a corner with two
/// quick taps, dropping one of them is the difference between a turn that works and one
/// that mysteriously does not.
/// </para>
/// </summary>
public static int ReadTurns( Direction[] into )
{
if ( into is null ) return 0;
var count = 0;
foreach ( var binding in Bindings )
{
if ( count >= into.Length ) break;
if ( !AnyPressed( binding.Actions ) ) continue;
into[count++] = binding.Direction;
}
return count;
}
/// <summary>Start a waiting run, or restart a finished one.</summary>
public static bool ConfirmPressed() => Input.Pressed( "Confirm" );
public static bool PausePressed() => Input.Pressed( "Pause" );
public static bool RestartPressed() => Input.Pressed( "Restart" );
/// <summary>
/// Any direction key at all - so a player who never reads instructions still starts the
/// game by pressing the key they were always going to press first.
/// </summary>
public static bool AnyDirectionPressed()
{
foreach ( var binding in Bindings )
{
if ( AnyPressed( binding.Actions ) ) return true;
}
return false;
}
private static bool AnyPressed( string[] actions )
{
foreach ( var action in actions )
{
if ( Input.Pressed( action ) ) return true;
}
return false;
}
}