Friday, March 16, 2012

The Input Module

The input module's job is to make processing keyboard and mouse input easier for game developers. The DOM's event handing system of itself is nightmarish. The special needs of game development further complicate input processing.

In this iteration, the input module only handles keyboard input. Mouse input support will be added later as it is needed. Further, additional input mechanisms such as touch screens and game pads may be utilized in the future. For now, though, detecting whether a key is pressed is the most pressing functionality of the input module.

My first approach to developing the input module was terrible. The event listeners listened for keypress events instead of keyup and keydown events since the keypress event (theoretically) is a keydown followed by a keyup. Further, keypress events include a charCode property that contains the pressed key's unicode value that, when passed to Javascript's String.fromCharCode() function, yields a string representing the character of the key pressed. The alternative is to interpret the keyCode property, which lacks a built-in function for conversion. However, I learned that in practice the keypress event is poorly implemented. Beyond a plethora of cross-platform and cross-OS issues, it does not capture presses of some non-alphanumeric keys such as the arrow keys, which are absolutely necessary for game development. This Quirksmode.org article discusses the keyboard event issues in more depth.

The other big problem with my first approach was its design. The keypress event handler pushed an object containing the event's data, particularly a string representing the key, to a stack of events. An outside module could then use an externally facing function to pop these events one-by-one and process them. This approach, of course, did not handle holding a key well. Because keypress events only fire so often while a key is held, some frames ran in which the input stack was empty. The result was that anything affected by a held key was jittery rather than continuous. For example, the ping pong paddle in the pong game moved semi-sporadically rather than smoothly as desired.

The solution was to implement a design that wasn't mind-numbingly stupid. The first step toward a good design was to abandon the keypress listener for the keydown listener. Everything fell nicely into place from there. Rather than keep a stack of keys pressed, the module held an object in which a string representing the key was mapped to a boolean value that was true if the key was currently being held. The keydown handler set the pressed key's value to true in the map. The keyup handler then set the released key's value back to false. The data is retained from frame to frame, and thus there are no frames that miss the key state, resulting in smoother input processing.

The keyup and keydown events do not contain a property easily identifying the key pressed, unfortunately. The only identifying data is the keyCode property, an integer value that is assigned to each key. As the Quirksmode.org article cited above explains, this mapping is not always consistent among browsers or operating systems. Fortunately, enough of the keys are standardized that it shouldn't pose a huge problem. Still, the issue of identifying each key without having to memorize a bunch of arbitrary key codes remains. The input module handles this with an object that maps a key code to a string that identifies it ("A" for the "A" key, "Left" for the left arrow key, and so on). The public functions of the module are concerned exclusively with the string identifier. The conversion from key code to string and vise versa is all done under the hood using the key code map.

The input module encapsulates two public functions. The first, init, initializes the module by setting up the keydown and keyup listeners. It should only be called after the document has loaded. The second, isDown, accepts a string identifying a key and returns true it's being held and false otherwise.

One lingering problem with the input system is that it prevents using general browser shortcuts because the events are registered on the document. For example, I cannot use command + R on my Mac to refresh the browser (a major annoyance when debugging). I am not sure whether registering events on other elements would fix the issue. Registering on the canvas element results in the event handler never running. The problem is not debilitating. It just makes debugging annoying at the moment since I have to use the mouse to refresh the page.

No comments:

Post a Comment