DartGameDevs

Bleeding edge tutorials and news for game developers working with Dart

Keyboard Input

A good keyboard input class is important for browser based games. It must support temporal queries, for example, was the key just pressed or is it being held? Read on to learn how the Javelin library processes keyboard events providing a simple, efficient, and powerful keyboard API designed for games.

Recently there was a question on stackoverflow asking how to properly use keyboard events for games . A good answer was provided but with a just bit more code your keyboard class can allow for temporal queries, making it possible to detect a variety of keyboard events including holds, releases, presses.

I’m going to start at the lowest level, the internal state of our keyboard class.

NOTE: I’ll be highlighting code snippets, the complete code can be found here

1
2
3

final List<Map<int, bool>> _keyboardStates = [new Map<int, bool>(),
                                              new Map<int, bool>()];

We keep two maps from keyboard code to boolean (true corresponding to key down). One encodes the current frame’s state and the other encodes the previous frame’s state.

At the start of each frame the following code executes:

1
2
3
4
5
6
7
8
void frame() {
  // Clear current frame's state.
  _keyboardStates[_currentIndex].clear();
  // Start current frame's state at same point as previous frame.
  _keyboardStates[_previousIndex].forEach((k, v) {
    _keyboardStates[_currentIndex][k] = v;
  });
}

The above code swaps the current frame’s keyboard state for the previous frame’s. At this point the two keyboard states match. Immediately afterwards, the current frame’s keyboard input is processed:

1
2
3
void keyboardEvent(KeyboardEvent event, bool down) {
  _keyboardStates[_currentIndex][event.keyCode] = down;
}

We now have both the previous and current frame’s keyboard state. We need one internal query:

1
2
3
4
5
6
7
8
bool _isDown(Map<int, bool> keyboardState, int keyCode) {
  bool r = keyboardState[keyCode];
  if (r == null) {
    // Never seen, it can't be down.
    return false;
  }
  return r;
}

This returns true if and only if keyCode is down for the given keyboardState.

We can build on _isDown and add two methods:

1
2
3
4
5
6
7
8
9
/** Is [keyCode] down this frame? */
bool isDown(int keyCode) {
  return _isDown(_keyboardStates[_currentIndex], keyCode);
}

/** Was [keyCode] down in the previous frame? */
bool wasDown(int keyCode) {
  return _isDown(_keyboardStates[_previousIndex], keyCode);
}

Conversely, isUp and wasUp are introduced. Given the above methods we can now make more complex, temporal, keyboard queries:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/** Was [keyCode] down in the previous frame and up in this frame? */
bool wasReleased(int keyCode) {
  return wasDown(keyCode) && isUp(keyCode);
}

/** Was [keyCode] up in the previous frame and down in this frame? */
bool wasPressed(int keyCode) {
  return wasUp(keyCode) && isDown(keyCode);
}

/** Is [keyCode] being held down? */
bool isHeld(int KeyCode) {
  return wasDown(keyCode) && isDown(keyCode);
}

We’ve gone from just isPressed to many possible queries. Next time I write about keyboard input, I will discuss detecting chords (simultaneous key presses) and sequences.