Keyboard Input

Let's change the frames per second of our loop and then we'll add controls to move our square around. Keyboard input in games is somewhat different than web developers are used to. I like using an object and setting each key to an up or down state that we can check later on in the loop when we want to move with that input. The difference is we're not moving on the keydown event, we're just setting a boolean to true or false on the KEYS_PRESSED object. This is much quicker than all the processing that would go into handling it right from the event. This makes for a more performant loop.

let KEYS_PRESSED = {}
const setup = () => {
  window.addEventListener('keydown', keyDown, false)
  window.addEventListener('keyup', keyUp, false)
}

const keyDown = (e) => {
  KEYS_PRESSED[e.key] = true;
}

const keyUp = (e) => {
  delete KEYS_PRESSED[e.key];
}

const keyPressed = (key) => {
  return KEYS_PRESSED[key]
}

So for our first trick. I'd like a little more control over the loop we created in the last article. When we press the bracket keys, I'd like to turn up or down the FPS of the render. The start function looks like this:

let ANIMATION = {}
start(fps) {
  cancelAnimationFrame(ANIMATION.id);
  ANIMATION.fps = fps || 60
  ANIMATION.fpsInterval = 1000 / ANIMATION.fps;
  ANIMATION.then = Date.now();
  ANIMATION.startTime = ANIMATION.then;
  ANIMATION.frameCount = 0;
  ANIMATION.id = requestAnimationFrame(loop);
}

So we just need to pass the fps to the start function and the loop will calculate the rest. I'd also like to toggle the loop if I hit CapsLock. That's a simple

const handleInput = () => {
    if (KEYS_PRESSED["]"]) {
      ANIMATION.fps *= 2
      start(ANIMATION.fps)
    }
    if (KEYS_PRESSED["["]) {
      ANIMATION.fps /= 2
      start(ANIMATION.fps)
    }
    if (KEYS_PRESSED["CapsLock"]) {
      ANIMATION.stop = !ANIMATION.stop;
        start(ANIMATION.fps)
    }
}

At some point in the loop, we need to call the handleInput function. We can do so in the doOneFrame function that's called in the loop.

const doOneFrame = () => {
    handleInput(e)
    move()
    draw()
}

We call a move function after the handleInput to make sure we have the latest. Then our move function can just check the boolean of any key pressed to affect it, or multiple keys down at the same time.

We need to move our box with the keyboard input. By adding to the velocity on x or y and then adding that to the box's current position. Simulating real physics, at least in the vacuum of space. In order to stop, you have to pressed the buttons opposite the ones you've pressed already and for just as long.

let HERO;
const ACCEL = .2
const move = () => {
    if (KEYS_PRESSED["ArrowUp"]) {
      HERO.vy -= ACCEL;
    }
    if (KEYS_PRESSED["ArrowLeft"]) {
      HERO.vx -= ACCEL;
    }
    if (KEYS_PRESSED["ArrowRight"]) {
      HERO.vx += ACCEL;
    }
    if (KEYS_PRESSED["ArrowDown"]) {
      HERO.vy += ACCEL;
    }
    HERO.x += HERO.vx;
    HERO.y += HERO.vy;
}

Now for the sake of clarity, let's draw this box character. We have HERO as a module scoped global variable so we can grab it's current x and y after all the moving and draw a simple red circle to represent it.

const circle = (color, x, y, radius) => {
    CONTEXT.fillStyle = color;
    CONTEXT.beginPath();
    CONTEXT.arc(x, y, radius, 0, 2 * Math.PI);
    CONTEXT.fill();
}

const draw = () => {
  circle('red', HERO.x, HERO.y, 10);  // Not much of a hero for now, but we'll fix that soon.
}

In Sum

It doesn't take a lot to get keyboard input working for a simple game in the browser. The trick is to seperate it from the movement and rendering in the loop. This way we can have multiple buttons down at the same time. We have a long way to go before we've discussed input tho. We need to do game pad and mouse input, as well as further dicussion of the forces involved in making movement feel good and what makes input and movement's reponse feel wrong. But this moving a character by X and Y based on input is the same basic concept in every game ever written, even 3D.