Input
Everything a module reads from the player lives in SharpProspero.Input: the controller and its motion
and touch, the rumble motors and light bar it drives back, and a USB keyboard and mouse. A small
InputMap sits on top so game code asks about named actions instead of raw buttons.
On this page
The controller
GamePad opens one controller for a user, reads a sample each frame, and drives its output. Open it
once at startup, read it in the frame loop, and dispose it at shutdown; disposing stops the motors.
using SharpProspero.Input;
using SharpProspero.Interop.Pad;
using var gamePad = GamePad.Open(); // the system profile; pass Users.InitialUserId for one player's pad
GamePadState pad = gamePad.Read();
if (pad.IsPressed(ScePadButton.Cross))
Jump();
Read returns a GamePadState, a decoded snapshot of one sample. When no sample is available it returns
GamePadState.Neutral — a resting value with both sticks centered — so a read never throws mid-frame. It
returns Neutral for an intercepted sample too: the system sets ScePadButton.Intercepted while it has
taken the controller for itself, and the rest of that sample describes what the system is doing with it
rather than what the player is pressing, so the whole sample is dropped.
Inside a ProsperoApp, the host already opens the pad and hands you the current sample on the frame
context, along with the previous one for edge detection. Use that instead of opening a second handle:
protected override void OnFrame(FrameContext context)
{
GamePadState pad = context.Input;
if (context.Pressed(ScePadButton.Options)) // true only on the frame it goes down
TogglePause();
}
See Application for the frame context and Tips for application modules for worked examples.
Buttons, sticks and triggers
GamePadState carries the digital buttons, both analog sticks, and both analog triggers. Buttons are a
ScePadButton flags value (in SharpProspero.Interop.Pad), so test one or several at once:
if (pad.IsPressed(ScePadButton.L1 | ScePadButton.R1)) // both bumpers held
OpenRadialMenu();
Sticks and triggers are raw bytes on the struct — LeftStickX, LeftStickY, RightStickX,
RightStickY, LeftTrigger, RightTrigger, each 0 to 255 with 128 at a stick’s center. The LeftStick
and RightStick properties give the same axes as a (float X, float Y) from -1 to 1, already
recentered and clamped:
(float x, float y) = pad.LeftStick; // -1..1, 0 at rest
player.X += x * speed * context.DeltaSeconds;
float throttle = pad.RightTrigger / 255f; // 0..1
A zero-initialized
GamePadStatereads as full lower-left stick deflection, because a raw stick byte of 0 is a real extreme, not the center. Start fromGamePadState.Neutralwhenever you need a resting value.
Motion and touch
A controller sample also carries the orientation, motion, and touch-pad contacts. GamePadState decodes
these as System.Numerics values, so they drop straight into vector math:
using System.Numerics;
Quaternion facing = pad.Orientation; // accumulated orientation
Vector3 gravity = pad.Acceleration; // in G, per axis
Vector3 spin = pad.AngularVelocity; // radians per second, per axis
if (pad.Touch1.IsActive)
DrawCursor(pad.Touch1.X, pad.Touch1.Y);
Touch1 and Touch2 are the two touch-pad contacts, each a TouchPoint with an X/Y position, a
tracking Id that stays constant while the finger is down, and an IsActive flag. TouchCount reports
how many contacts are live (0 to 2). IsConnected and TimestampMicroseconds describe the sample
itself.
Touch-pad gestures
TouchGestureRecognizer turns the raw contacts into gestures. Feed it each frame’s sample and it returns
the gestures that completed or advanced: a tap, a double tap, a hold, a drag, a flick with its velocity,
and a two-finger pinch that carries both a scale and a rotation.
var gestures = new TouchGestureRecognizer();
// each frame:
foreach (TouchGesture g in gestures.Update(pad))
{
switch (g.Kind)
{
case TouchGestureKind.Tap: Select(g.Position); break;
case TouchGestureKind.Drag: Scroll(g.Delta); break;
case TouchGestureKind.Pinch: Zoom(g.Scale); Rotate(g.Rotation); break;
}
}
The thresholds - how far a tap may move, how long a hold takes, the flick speed - are properties you can
tune. It keeps its own state, so a single recognizer follows a gesture across frames; call Reset to drop
everything in progress, after a screen change or when the pad handle is reopened.
Output: rumble and light bar
GamePad drives the controller’s motors and light bar alongside reading it. Each call returns false when
the controller does not accept the request.
gamePad.SetVibration(largeMotor: 200, smallMotor: 120); // 0 (stop) to 255 each
gamePad.SetLightBar(0x00, 0x80, 0xFF); // r, g, b
gamePad.ResetLightBar(); // back to the default color
gamePad.StopVibration(); // both motors off
The large motor is the low-frequency left motor and the small motor the high-frequency right one. Disposing the pad stops both motors for you.
Several controllers at once
A controller belongs to a signed-in user, and the service routes each user’s samples to the handle opened
for that user. There is no separate index for a second controller — the index argument is reserved and
must be zero — so the way to reach every device is one handle per signed-in user. GamePadSet opens and
keeps that set.
using var pads = GamePadSet.OpenForSignedInUsers();
// In the frame loop:
pads.Refresh(); // picks up a player who has just signed in, drops one who has signed out
pads.ReadAll(); // samples every open controller
foreach (PlayerPad player in pads)
if (player.State.IsPressed(ScePadButton.Cross))
Fire(player.UserId);
Each PlayerPad carries the UserId it belongs to, the UserName read when it was opened, the Pad
itself for rumble and the light bar, and the State from the last ReadAll. ForUser finds one by user
id, and Refresh returns whether the set changed, so a player list is rebuilt only when it must be.
A user signed in with no controller paired to them simply gets none — that is refusal, not failure.
Where something else in the module already holds a user’s controller (the single-pad GamePad.Open, most
likely), the set reaches that open handle instead of failing, and leaves closing it to whoever opened it.
GamePad.IsConnected says whether a device is attached to a handle right now, which is how a controller
that has gone to sleep or run out of charge is noticed; GetInformation reports what the device is.
Named actions
InputMap maps buttons to names, so code asks whether the player jumped rather than whether cross is
pressed — and the bindings stay in one place, ready to rebind. A binding can be one button, a chord
(several buttons combined with |, all of which must be held), or several alternatives (bind the same
action more than once). Bind returns the map so calls chain.
flowchart LR
A[Controller sample] --> B["InputMap.Update(sample)"]
B --> C["WasPressed / IsHeld / WasReleased"]
Feed it this frame’s sample once with Update, then query; it keeps the previous sample itself so it can
tell a press from a hold.
var input = new InputMap()
.Bind("Jump", ScePadButton.Cross)
.Bind("Fire", ScePadButton.R2)
.Bind("Special", ScePadButton.L1 | ScePadButton.R1) // a chord: both held
.Bind("Confirm", ScePadButton.Cross)
.Bind("Confirm", ScePadButton.Options); // an alternative for the same action
// each frame:
input.Update(context.Input);
if (input.WasPressed("Jump")) Jump();
if (input.IsHeld("Fire")) Fire();
if (input.WasReleased("Fire")) StopFiring();
WasPressed and WasReleased count each edge once; IsHeld is true for as long as the action is down.
Unbind clears every binding for an action and IsBound reports whether one exists.
Keyboard and mouse
Keyboard and Mouse read a USB keyboard and mouse — the input a file explorer or a browser wants
beyond the controller. Open each for a user, read it each frame, and dispose it at shutdown.
using var keyboard = Keyboard.Open();
using var mouse = Mouse.Open();
KeyboardState keys = keyboard.Read();
if (keys.Modifiers.HasFlag(KeyModifier.LeftControl)) { }
MouseState m = mouse.Read();
cursorX += m.DeltaX; // the mouse reports movement since the last read, not a position
cursorY += m.DeltaY;
if (m.IsButtonDown(MouseButton.Primary)) { }
KeyboardState.Keys is the set of USB usage codes currently held (newest last); the read keeps only real
codes, so it is empty when nothing is down even though the count that comes back is never less than one.
Modifiers holds the shift/control/alt/gui state, Leds the lock keys that are on (num, caps, scroll),
and Connected reports whether a keyboard is attached. IsKeyDown tests one usage code and answers false
for zero, which is not a key. Producing a character needs Leds as well as Modifiers — caps lock decides
the case of a letter and num lock what the number pad gives — so pass both to the converter below.
MouseState gives the relative DeltaX/DeltaY movement, the Wheel and Tilt scroll, the Buttons
held, and Connected — an application accumulates the deltas into a cursor position of its own. A still
mouse produces nothing to read, so Read repeats the last reading with the four movement values zeroed
rather than reporting the mouse absent: a held button stays held and the cursor stays put. Connected and
Buttons therefore carry over from the last reading the device produced, and a mouse reads as absent only
when a reading says so or the read fails.
MouseButtonlives inSharpProspero.Interop.MouseandKeyModifierinSharpProspero.Interop.Keyboard.KeyboardStateis aref struct: it cannot be stored in a field, boxed, or held across anawait. CopyKeysinto an array of your own to keep it past the current frame.
From key codes to characters
A key code is a position on the keyboard, not a letter. KeycodeConverter turns one into the character it
produces, applying the layout and the held modifiers, so a build can read typed text from a USB keyboard
without the on-screen keyboard. It resolves from a system library at run time, so open it where the module
has access and dispose it when done. TryOpen returns null when the library is unavailable; Open throws.
using KeycodeConverter? converter = KeycodeConverter.TryOpen(); // null without the library
if (converter is not null && keys.Connected)
{
KeyboardLayout layout = converter.GetLayout(); // the user's chosen layout
foreach (ushort keycode in keys.Keys)
{
char c = converter.ToCharacter(keycode, keys.Modifiers, layout, keys.Leds);
if (c != '\0') // '\0' for a key that makes no character
typed += c;
}
}
GetLayout reads the user’s layout — one of the KeyboardLayout values, such as EnglishUs, German,
or JapaneseKana — so a build honors it instead of assuming one; pass a fixed KeyboardLayout to
ToCharacter if you want a specific one. ToVirtualKeycode gives the virtual key code for a physical
key, or -1 when there is none.