Handling Game Input
The obvious way to handle a keypress, act on it the moment the event fires, is the wrong way for games. It makes movement jerky and fights the game loop. The right way is to record what is currently held in an input state, and let the update step read that state every frame. This article covers keyboard, mouse, and touch input, and the input state pattern that makes controls feel smooth.
Both the Breakout and multiplayer Pong demos use this pattern.
What you'll learn
Why events are not enough
Reacting directly to key events produces motion that does not match how games feel.
The repeat delay problem
If you move a player only when a keydown event fires, you inherit the operating system key repeat: a press, a pause, then rapid repeats. Movement stutters and stops when another key is pressed. Games avoid this by treating input as a state you sample every frame, not a stream of one off events.
The input state pattern
The fix is a small object that remembers what is held right now.
Record now, read in update
var keys = {};
document.addEventListener("keydown", function (e) { keys[e.key] = true; });
document.addEventListener("keyup", function (e) { keys[e.key] = false; });
function update() {
if (keys["ArrowLeft"]) player.x -= speed;
if (keys["ArrowRight"]) player.x += speed;
}
Events only flip flags in the keys object. The update step reads those flags every frame, so a held key moves the player smoothly and releasing it stops instantly.
Keyboard input
The keyboard is the most common game input and the simplest to read this way.
Keys and their names
Each key event carries a key property naming the key, such as ArrowLeft, w, or a space. Storing these in the state object by name keeps the code readable. For game keys like the arrows and space, call preventDefault while playing so the page does not scroll, which both demos do.
Mouse and touch
Pointer input needs a small conversion from screen coordinates to canvas coordinates.
From screen to canvas
canvas.addEventListener("mousemove", function (e) {
var rect = canvas.getBoundingClientRect();
var x = (e.clientX - rect.left) * (canvas.width / rect.width);
paddle.x = x - paddle.w / 2;
});
The event gives a position in page pixels, so you subtract the canvas position and scale by the ratio of canvas resolution to display size. The same approach with touchmove adds touch support, which is how the Breakout paddle follows a finger.
Two players on one keyboard
Local multiplayer is just two input states fed by two key sets.
Splitting the keys
Give each player its own keys, for example W and S for one and the arrow keys for the other, and store each in its own state. The update step then moves two paddles from two states in the same frame. This is exactly how the multiplayer Pong demo lets two people play at once, and the broader idea of decoupled input is event-driven architecture in miniature.
Common pitfalls
Input has a few classic traps.
What usually goes wrong
The most common is acting on keydown directly, which inherits key repeat and feels jerky; sample state instead. The second is forgetting to scale pointer coordinates, so the target is offset when the canvas is displayed at a different size. The third is letting arrow keys and space scroll the page during play, fixed with preventDefault on those keys while the game runs. The loop that reads this input is in game loop fundamentals.
Frequently Asked Questions
Why do my game controls feel jerky?
You are probably acting on keydown events directly, which inherits the operating system key repeat: a press, a pause, then rapid repeats. Record which keys are held and read that state every frame instead.
What is the input state pattern?
It is a small object that records which keys are currently held. Key events only set flags in it, and the game loop reads those flags every frame, so a held key moves the player smoothly and releasing it stops instantly.
How do I convert mouse position to canvas coordinates?
Subtract the canvas position from the event's page coordinates, then scale by the ratio of canvas resolution to display size. This gives the correct position even when the canvas is shown at a different size than its resolution.
How do two players share one keyboard?
Give each player its own key set, such as W and S for one and the arrow keys for the other, and store each in its own input state. The update step then moves both from their states in the same frame.
Read next: collision detection, or the Game Development hub.