Input
Every device is read once per tick into one input record; the game reads only that record (as of 2026-11-10).
This page is for developers. For the keys, buttons and gestures a player uses, see Controls; for typed cheat words, Cheats & Diagnostics.
One input record per tick
At the start of each tick GalaxyEggbertGame::CaptureInput() (src/GalaxyEggbertCNA/GalaxyEggbertGame.cpp)
fills tickInput_, a ResumeInput (src/GalaxyEggbert/Game/ResumeLog.hpp):
| Field | Contents |
|---|---|
keys | The XNA Keys values held, sorted: the keyboard with the gamepad merged in as keys (below). |
mouseX, mouseY, wheel, buttons | The mouse; buttons is a bit set of left, middle, right, X1, X2. |
viewportWidth, viewportHeight | The viewport the positions refer to. |
touches | Every finger: id, x, y and state (pressed, moved, released). |
touchControlsEnabled | Whether CNA reports a touch screen (TouchPanel::GetCapabilities()). |
cheatLetter | One typed letter a-z in press order, 0 for none, -1 when typed letters are not captured. |
accelValid, accelReset, accelY | The accelerometer reading, when it is on. |
After that, nothing in the tick asks a device. CurrentKeyboardState() and CurrentMouseState()
rebuild XNA states from the record, and the phase handlers, InputPad and the editor work on those. This is
what lets a recorded list of these records replay a game exactly (Game Loop).
Keyboard
The held keys come from CNA's Keyboard::GetState(). Play turns them into movement in
src/GalaxyEggbert/Game/ControlInput.hpp, where aliases are combined per direction:
// src/GalaxyEggbert/Game/ControlInput.hpp
return {
float(keys.IsKeyDown(Keys::Right) || keys.IsKeyDown(Keys::D)) -
float(keys.IsKeyDown(Keys::Left) || keys.IsKeyDown(Keys::A)),
float(keys.IsKeyDown(Keys::Up) || keys.IsKeyDown(Keys::W)) -
float(keys.IsKeyDown(Keys::Down) || keys.IsKeyDown(Keys::S)),
keys.IsKeyDown(Keys::X) || keys.IsKeyDown(Keys::RightControl)};
In the tank, VehicleFireKeyboard() turns Left Ctrl into F (fire) before anything else reads it. The
remaining play keys (Left Ctrl jump, Space action, Left Shift crouch, Right Shift look up, Esc, F-keys, Tab) are read
by name where they are used.
Typed letters through KeyDownEXT
Held-key snapshots miss a key pressed and released between two updates, and they lose the order of letters.
The game therefore subscribes to CNA's Keyboard::KeyDownEXT event, which delivers each physical press in
order. In Play, letters go into a queue and CaptureInput() puts one of them per tick into
cheatLetter; InputPad matches the typed cheat words from that stream. A recognised word's
prefix keeps its letters from also steering Blupi until they are released (InputPad::FilterGameplayKeyboard()).
This needs no text-input mode or on-screen keyboard, so it works with a keyboard attached to a touch device.
The same event feeds the editor's command keys, the typed names of galaxies and demos, and the demo idle timer.
Building with -DGALAXY_EGGBERT_ENABLE_CHEATS=OFF removes typed cheats and the hidden cheat menu.
Gamepad
src/GalaxyEggbert/Game/GamepadInput.hpp is engine-independent. The host fills a GamepadFrame from
the first connected pad among XNA's PlayerIndex One to Four (ReadGamepadFrame()), and
GamepadKeys() names the keyboard keys it stands for. ReadKeyboardState()
(src/GalaxyEggbertCNA/GameHelpers.hpp) adds those keys to the keyboard's, so the pad drives every phase the
keyboard drives.
| Pad | Becomes | Meaning in play |
|---|---|---|
| Left stick (past 0.5) or D-pad | Arrow keys | walk, turn, turn around |
| A | Left Ctrl and Enter | jump (climb in the helicopter); confirm in menus |
| B or X | Space | action |
| Right trigger | F | fire in the tank or helicopter |
| RB | Right Ctrl | back up |
| LB | Left Shift | crouch |
| Y | Right Shift | look up |
| Start or Back | Escape | pause |
In the main menu and the editor the pad is not turned into keys: there it drives an on-screen cursor.
EditorDeviceControls (src/GalaxyEggbert/Editor/) reads the pad and touch directly and produces the
keyboard, mouse and camera movement the editor understands (sticks fly and look, A places, X removes). Since 2026-11-10 the demo positions, the galaxy screen and the world screen can be
driven with the arrows or the D-pad: FocusNavigator.hpp keeps one button chosen (drawn lit) and hands the
screen a mouse pressed at that button's centre when A or Enter is pressed; moving the mouse or touching ends the choice.
Mouse
- Menus and screens are clicked with the left button.
- In Play, holding the left button and dragging turns the view (0.008 radians per pixel, pitch limited); walking returns the camera behind Blupi. A press that lands on an on-screen control is not used for looking.
- In the editor the cursor aims the placement ray (
VoxelRaycast); see 3D World Editor.
Touch
Each finger from TouchPanel::GetState() is stored in the record. The on-screen pad of the original
(stick, Jump, Action, Pause, a crouch button now shown always, plus Galaxy's back-up button and, in the tank or helicopter, Fire) is drawn, and its mouse hit areas are
active, only when CNA reports a touch screen (since 2026-11-08); on a desktop without one the mouse stays
free for the camera.
InputPad::UpdatePlayTouches()takes several fingers at once: a finger that lands on the stick steers it wherever it moves, Jump is held by any finger on it, Action and Pause fire when their finger lifts.- The stick is discrete like the original's: the finger's offset from its centre becomes -1, 0 or +1 per axis.
- While fingers are down, and 15 ticks after, the mouse is ignored in Play: platforms also turn touches into mouse events, which would press Action twice.
- A hidden tap gesture opens the cheat menu (blocked by
fairplay).
Where there is no keyboard, a name (a galaxy, a demo) is typed on an on-screen keyboard (ScreenKeyboard.hpp,
2026-11-10).
Accelerometer
The Setup's Accelerometer switch (off by default, accelActive=) and sensitivity slider
(accelSensitivity=, 0-100, 50 by default) come from the original. When it is on and CNA reports a sensor
(Microsoft::Devices::Sensors::Accelerometer), AccelTurnInput() turns the device's tilt (its Y
component) into steering with the original's dead zone, and only after the device has once been held level. While
readings arrive, the on-screen stick gives way to Forward and Back buttons (GALAXY_ACCEL_PAD=1 shows them
without a sensor). Replays read the recorded value, never the device.
Menus: the 640×480 layout
The original's screens are 640×480 images. InputPad and Hud keep the original's layout
and scale it with the window's height (scale = height / 480): the 640-wide image is centred and the bars beside it are
filled with the menu's own images. Buttons use the original's rectangles, scaled the same way, and are hit-tested
against the mouse or touch position (QuadBatch::InRect()). As in the original, buttons fire on release,
except Jump on the pad, which acts while held. The F1 help panel's close box shows the mapping:
// src/GalaxyEggbert/Game/ControlInput.hpp, ControlsHelp::OverClose()
const float scale=std::min(width/640.f,height/480.f);
const float ox=(width-640*scale)*.5f,oy=(height-480*scale)*.5f;
return scale>0 && x>=ox+550*scale && x<=ox+620*scale &&
y>=oy+18*scale && y<=oy+54*scale;
Enter (or the pad's A) confirms the Resume question, Pause and the Win/Lost screens; Escape goes back. The help panel freezes the screen and swallows its closing key, so closing it cannot also pause or leave a menu.
Recording input for resume and demos
- Mission resume. After each Play tick the record is appended to the mission's
ResumeLog. Identical consecutive records are stored once with a count. On continue,ReplayBatch()setstickInput_from the log and callsUpdateTick(); the devices are not read. Touch capability, typed letters and the accelerometer come from the record, so a replay does not depend on the device it runs on. - Demos. A recording stores the same records, except F1-F24 and Tab (camera, full screen, help,
game speed): those are dropped before recording. A demo plays through
DemoTick()the same way. - Stopping a demo. While a demo plays, live input is only watched: a new key, mouse button, wheel
step, gamepad button or finger stops it; moving the mouse does not; the input that stopped it never reaches the menu.
The rules are in
DemoReplay.hpp(DemoNewPress(),DemoActivity()). - Autoplay and captures. With
--autoplayor the golden capture modes, live devices are ignored and the script's keys are used.
File formats and where the files go: Save Data & Resume and Demos & Recordings.
Tests
VerifyGEInputPad: the menus' and pad's hit areas and press rules.VerifyGEEditorDevices: the editor's touch and gamepad front end.VerifyDemoReplay: what stops a demo and resets the idle timer.
Related: InputPad, ResumeLog, DemoReplay, GalaxyEggbertGame.