-
Notifications
You must be signed in to change notification settings - Fork 0
Controls
Universal Retro Arcade supports three input methods: keyboard, hardware gamepad (Xbox / PlayStation), and touch (Android / tablet). All input handling is performed in the browser layer via Phaser 4 and the HTML5 Gamepad API.
| Action | Key |
|---|---|
| Move selection up |
↑ Arrow Up |
| Move selection down |
↓ Arrow Down |
| Confirm / Select game |
Space or Enter
|
| Back / Close modal | Esc |
| Toggle CRT Shader | Ctrl + Shift + C |
| B-I-O-S Easter Egg | Type B, I, O, S in sequence |
| Action | Key |
|---|---|
| Return to Lobby | Esc |
| Restart (on Game Over) |
Space (most games) |
| Game | Move | Action / Fire | Rotate | Hard Drop |
|---|---|---|---|---|
| Snake Evolution | ↑ ↓ ← → |
— | — | — |
| Neon Pong |
W (up) / S (down) |
— | — | — |
| Astro Drift |
W (thrust) / A D (rotate) |
Space (fire) |
— | — |
| Brick Breaker |
← → or Mouse |
Space (launch) |
— | — |
| Froggie Crosser | ↑ ↓ ← → |
— | — | — |
| Space Defenders | ← → |
Space (fire) |
— | — |
| Tetris Pulse |
← → (move) / ↓ (soft drop) |
Space (hard drop) |
↑ (rotate) |
Space |
| Minesweeper | Mouse only | Left Click (reveal) / Right Click (flag) | — | — |
| Pixel Runner | — |
Space (jump / double jump) |
— | — |
| Brave Bird | — |
Space (flap) |
— | — |
| Cyber Chasm | ↑ ↓ ← → |
— | — | — |
The HTML5 Gamepad API (navigator.getGamepads()) is polled every frame inside Phaser 4's update() loop. Connect your controller before launching the app (or while on the lobby screen) for reliable detection. A 🎮 GAMEPAD CONNECTED banner will briefly appear in the lobby header upon connection.
Only one controller is supported at a time (index 0).
| Physical Button | Phaser Gamepad Property | Function |
|---|---|---|
| Left Stick (Y-axis < -0.5) | pad.leftStick.y < -0.5 |
Navigate Up / Move Up |
| Left Stick (Y-axis > 0.5) | pad.leftStick.y > 0.5 |
Navigate Down / Move Down |
| D-Pad Up | pad.up |
Navigate Up / Move Up |
| D-Pad Down | pad.down |
Navigate Down / Move Down |
| D-Pad Left | pad.left |
Move Left |
| D-Pad Right | pad.right |
Move Right |
| A Button | pad.A |
Confirm / Jump / Fire / Flap |
| B Button | pad.B |
Back to Lobby |
| X Button | pad.X |
Confirm (secondary) |
| Y Button | pad.Y |
Confirm (secondary) |
| LB / L1 | pad.L1 |
Back to Lobby (alternative) |
| Right Trigger | pad.R2 |
Fire (Astro Drift) |
PlayStation controllers are automatically mapped to the same Phaser gamepad slots through the browser's standard gamepad mapping:
| Physical Button | Xbox Equivalent | Function |
|---|---|---|
| Cross (✕) | A | Confirm / Jump / Flap |
| Circle (○) | B | Back to Lobby |
| Square (□) | X | Confirm (secondary) |
| Triangle (△) | Y | Confirm (secondary) |
| L1 | L1 | Back to Lobby |
| R2 | Right Trigger | Fire |
| Left Stick | Left Stick | Navigation / Movement |
| D-Pad | D-Pad | Navigation / Movement |
Note: The
standardgamepad mapping is used. Non-standard controllers (e.g., older generic USB gamepads) may require remapping via your OS or third-party software.
The lobby polls the gamepad each frame with edge detection to prevent held-button repeat:
const pad = this.input.gamepad.getPad(0);
const up = pad.up || pad.leftStick.y < -0.5;
const down = pad.down || pad.leftStick.y > 0.5;
const button = !!(pad.A || pad.B || pad.X || pad.Y);
const back = !!(pad.B || pad.L1);
// Edge detection — only trigger on state change
if (up && !this.padLastState.up) this.handleUp();
if (down && !this.padLastState.down) this.handleDown();
if (button && !this.padLastState.button) this.handleSpace();
if (back && !this.padLastState.back) this.handleEsc();
this.padLastState = { up, down, button, back };On Android, the game uses on-screen virtual controls rendered as Phaser GameObjects overlaid on the game canvas. Phaser 4's Pointer API maps touch events to the corresponding game actions.
┌─────────────────────────────────────────────┐
│ │
│ [ Game Canvas Area ] │
│ │
│ ┌──────────────────┐ ┌─────────────────┐ │
│ │ D-PAD │ │ ACTION BTNS │ │
│ │ [▲] │ │ [A] [B] │ │
│ │ [◄] [►] │ │ [X] │ │
│ │ [▼] │ │ │ │
│ └──────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────┘
| Game | Touch Action |
|---|---|
| Minesweeper | Single tap = reveal; long press (500ms) = flag |
| Brick Breaker | Drag finger horizontally to move paddle |
| Brave Bird | Tap anywhere on screen to flap |
| Pixel Runner | Tap anywhere to jump; tap again mid-air to double jump |
Touch input is handled via Phaser 4's unified InputPlugin, which normalizes mouse clicks and touch events into Pointer objects:
this.input.on('pointerdown', (pointer: Phaser.Input.Pointer) => {
// Works identically for mouse click and finger tap
handleTap(pointer.x, pointer.y);
});When multiple input methods are detected simultaneously:
- Keyboard — highest priority; all keyboard events are processed first.
-
Gamepad — polled on every
update()frame, edge-detected. - Touch / Mouse — handled via event listeners, lowest priority but always active.
There is no conflict resolution needed since the input methods control the same game state — pressing D-Pad Up and ↑ at the same time will simply trigger the move twice in the same frame (harmless due to direction-lock in Snake, etc.).