All chapters · Chapter 4/9

Take control

Starting

Click the game and press ← and →. It's yours now.

const keys = useKeyboard();
 
useUpdate((dt) => {
    if (keys.isDown('ArrowLeft')) paddle.transform.x -= PADDLE_SPEED * dt;
    if (keys.isDown('ArrowRight')) paddle.transform.x += PADDLE_SPEED * dt;
 
    paddle.transform.x = Math.min(Math.max(paddle.transform.x, 0), WIDTH - PADDLE_W);
});

The keyboard is asked, not listened to

useKeyboard doesn't notify you when a key goes down. It hands you an object you question whenever it suits you:

keys.isDown('ArrowLeft')   // is it held RIGHT NOW?

This is called polling, and it's what you want in a game. With events you'd have to record somewhere that the key is down, clear it on release, and remember to clear it again if the window loses focus. Asking, the answer is always the truth of the frame you're drawing.

The names are the browser's: 'ArrowLeft', 'ArrowRight', 'Enter', ' ' for space. Single letters ignore case: isDown('a') is the same as isDown('A').

Two more questions you'll want soon: justPressed(key), true for exactly one frame per press, and justReleased(key). With isDown, a shot would fire sixty times a second while you lean on the button; with justPressed, once per press.

The line that doesn't look important

paddle.transform.x = Math.min(Math.max(paddle.transform.x, 0), WIDTH - PADDLE_W);

Delete it and hold the left arrow: the paddle leaves the screen and never comes back. It's the kind of bug that breaks nothing, raises no error, and ruins the game.

Read it inside out:

And the right edge is WIDTH - PADDLE_W, not WIDTH, for the chapter-2 reason: x is the paddle's left corner, so you have to leave room for its own width.

This is called clamping a value, and you'll write it a great many times.

What's an `if`?

An if runs something only when a condition holds:

if (keys.isDown('ArrowLeft')) paddle.transform.x -= 5;

It reads literally: if the left arrow is down, subtract 5 from x. If it isn't, that line is skipped and nothing happens.

Notice there are two separate ifs here, not an if/else. That's deliberate: if someone presses both arrows at once, both run and the paddle sits still, which is reasonable. With else, left would always win.

Your turn

Swap 'ArrowLeft' and 'ArrowRight' for 'a' and 'd'. Add a third if on the spacebar (' ') that makes the paddle wider while it's held.

And delete the Math.min/Math.max line once, just to watch it run away.