Craft·5 min read

Reading the Controls Properly

A game does not respond to a key being pressed. It asks, every frame, which keys are currently down. The difference is everything.

The bedroom level of the Tape Loader birthday game, with the player mid-run between shelves.

Run right and jump at the same time. It is the most basic thing a platformer asks of anybody, and it is where a surprising number of hand-built games fall over.

The reason is that most software is written to react to events, and a game cannot be.

Presses are events, holding is a state

When a key goes down the system announces it once. Hold it and, after a pause, the system starts repeating the announcement at whatever rate the operating system is configured for.

Write a game that moves the character on each announcement and you get exactly what that describes: one step, a pause, then a stuttering series of steps at a rate the player never chose and you cannot control. It feels like typing. It is not running.

The fix is to stop reacting and start recording.

A game keeps a small record of which controls are currently held. Left, right and jump, in ours, as three simple true-or-false entries. When a key goes down, the matching entry is set to true. When it comes up, false. Nothing else happens on either event.

Then, once per frame, the game reads that record. Right held means add to the horizontal speed this frame. Jump held means consider jumping. The controls are polled rather than obeyed, and everything follows from that.

Two keys at once is now free. Right and jump are separate entries and both can be true, with no special handling, because nothing was ever treating them as competing events.

The translation table

Between the key and the record sits a small map, and it is worth having even though it is trivial.

Ours maps the left arrow and both cases of A to the same entry; the right arrow and D to another; the up arrow, W and the space bar to jump. One control, several ways to reach it.

Having that as a table rather than a chain of conditions means adding an alternative key is one line, and it makes the game's actual controls readable in one place. It also draws a clean line: everything before the table is about hardware, everything after is about the game, and the game never mentions a key name again.

That separation is what makes the next part possible.

The same game, with a thumb

Because the game reads a record rather than listening for keys, anything that can write into that record can play the game.

Touch controls are three invisible regions across the bottom of the screen. Press one and it sets the same entry the left arrow would. Release and it clears it. The game does not know and does not need to know.

Our engine template lays them out as three equal columns filling the lower third, deliberately large, with no visible artwork. Large because a thumb is imprecise and a small button is a miss. Invisible because the artwork behind them is the game and a set of drawn buttons over it is a worse picture for no gain.

They are shown only on devices with a coarse pointer, which is how a browser describes a finger, so a laptop never sees them.

The bedroom level of the Tape Loader birthday game, with the player mid-run between shelves.
Running and jumping at once is the entire test. It works because both are entries in a record, not events.

Where touch goes wrong

Two real defects, both found on an actual phone and neither visible in a resized browser window.

The finger that slides off. A thumb presses a control and drifts outside it while still touching the screen. The release event never fires for that control, the entry stays true, and the character runs off on their own with the player holding a thumb on the glass wondering what happened. The fix is to treat the pointer leaving the region, and the system cancelling the interaction, exactly like a release. Two extra listeners.

Controls that are always there. Our tape loader showed its touch pads for the whole session, including on the title screen, where they sat on top of the buttons the player actually needed and made them unpressable. The pads were also sized at a fixed ninety pixels and anchored at fixed percentages, so on any screen under about seven hundred pixels wide the two on each side overlapped each other.

Both were fixed in the delivered file: the pads now scale with the viewport and are spaced off each other, and they appear only while a level is actually being played. Showing them is now part of the state transition, which is one line and cannot be forgotten, for the reasons in the one variable that runs the whole game.

Neither of those is a clever bug. Both shipped because the game had been tested by resizing a browser window, which simulates the size of a phone and nothing else about it.

Stopping the page from helping

One last category, and it is the one that makes a game feel broken on a phone in a way nobody can quite describe.

Browsers do helpful things with input that are wrong inside a game. The space bar scrolls. Arrow keys scroll. A swipe pans the page. A double tap zooms. Dragging past the top bounces the whole document.

Each has to be switched off deliberately: telling the browser not to perform its default action for keys the game uses, marking the drawing surface as handling its own touch, and disabling the elastic overscroll on the document.

None of it is difficult and all of it is easy to forget, and the symptom is a game where jumping also scrolls the page, which reads to a player as the game being badly made rather than as a browser default.

What this means for a commission

Two things at once is a fundamental, not a feature. If a game cannot run and jump, nothing else about it matters.

Touch is not a smaller keyboard. It is a different input with its own failure modes, and the only way to find them is a real device.

And the same build serves both. One game, one file, keyboard or thumb, which is a direct benefit of reading a record rather than listening for events.

◆ Questions

Common questions

How does a game handle holding two keys at once?+

By keeping a record of which keys are currently held rather than reacting to each press. Every frame it reads that record, so running right and jumping at the same time is simply two entries being true together.

Why not just act on the key press event?+

Because a press happens once and holding is continuous. Acting on the event gives one step per press and a repeat rate set by the operating system, which is why a naive implementation feels like typing rather than running.

How do touch controls work in a browser game?+

Invisible regions of the screen map to the same held-key record the keyboard writes into. The game itself never knows whether an input came from a thumb or a keyboard.

What is the most common touch control bug?+

A finger sliding off a control without releasing it, which leaves the game thinking the key is still held and sends the character running off on their own. Handling the pointer leaving, and cancellation, fixes it.

Should touch controls always be visible?+

No. They should appear only when there is something to steer. Ours used to sit over the title screen and blocked the buttons underneath, which was found on a real phone rather than in a browser window.

  • craft
  • code
  • mobile
  • fundamentals
◆ Keep reading

Related from the journal.