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.
Friday, March 16, 2012
Thursday, March 15, 2012
Some Resources
The Reality of HTML5 Game Development and Making Money from It
An article that broadly discusses the variety of technologies and standards used in HTML 5 game development. It also sheds light on the platform compatibility problems rampant in web technologies. Finally, the article explains a few ways a developer can make money from HTML 5 games.
HTML5 Game Development Tutorial
A simple tutorial for creating a simple HTML5 game. It isn't even nearly comprehensive, but it is a useful reference.
HTML5 Canvas for Absolute Beginners
A canvas tutorial from the same website as the article just above this one. Again, it's not even somewhat comprehensive, but it's a nice reference nonetheless.
HTML5 Game Dev News
A website that, as the name implies, aggregates HTML5 game development news. It isn't very browsable, but it's handy to check it every few days or so for interesting news articles.
An article that broadly discusses the variety of technologies and standards used in HTML 5 game development. It also sheds light on the platform compatibility problems rampant in web technologies. Finally, the article explains a few ways a developer can make money from HTML 5 games.
HTML5 Game Development Tutorial
A simple tutorial for creating a simple HTML5 game. It isn't even nearly comprehensive, but it is a useful reference.
HTML5 Canvas for Absolute Beginners
A canvas tutorial from the same website as the article just above this one. Again, it's not even somewhat comprehensive, but it's a nice reference nonetheless.
HTML5 Game Dev News
A website that, as the name implies, aggregates HTML5 game development news. It isn't very browsable, but it's handy to check it every few days or so for interesting news articles.
Tuesday, March 6, 2012
The Game Module: Loop
The loop module is simple architecturally. It is nestled in the game namespace, and it contains one publicly exposed function, initLoop. It is perhaps the most fundamental component of the game module.
The loop module provides the mechanism necessary to execute a game loop at a specified frames per second. The function initLoop takes two arguments, a step function and an optional fps. The step function advances the game state forward. It is passed a single argument, the time elapsed from the start of the previous frame to the start of the current frame. The game logic implemented in the step function should use the elapsed time to move the game forward. It ensures that the game advances at a consistent pace by basing the degree of advancement on an objective measurement, time. Basing it instead on the number of executions of the loop could be problematic since slower machines may execute the loop at a different pace than a faster machine, and the loop would be executed at a different pace if the developer chose to use a different frame rate.
The algorithm for implementing the game loop is simple in concept, but the actual implementation is messy since it requires a lot of value juggling among variables. A high level description follows:
Given step function S and frame rate FPS,
The loop module provides the mechanism necessary to execute a game loop at a specified frames per second. The function initLoop takes two arguments, a step function and an optional fps. The step function advances the game state forward. It is passed a single argument, the time elapsed from the start of the previous frame to the start of the current frame. The game logic implemented in the step function should use the elapsed time to move the game forward. It ensures that the game advances at a consistent pace by basing the degree of advancement on an objective measurement, time. Basing it instead on the number of executions of the loop could be problematic since slower machines may execute the loop at a different pace than a faster machine, and the loop would be executed at a different pace if the developer chose to use a different frame rate.
The algorithm for implementing the game loop is simple in concept, but the actual implementation is messy since it requires a lot of value juggling among variables. A high level description follows:
Given step function S and frame rate FPS,
- Obtain the current time Ti.
- Subtract Ti from the time when the last frame began execution, Tpi, to get the elapsed time, deltaT. If this is the first frame execution, deltaT = 0 since Tpi is undefined.
- Run S with input deltaT.
- Subtract the current time from Ti to obtain the run time of S, deltaTS.
- Subtract deltaTS from the desired frame period, 1/FPS (the period is the inverse of the frame rate), to obtain the time until the next frame should run, Tw.
- Wait Tw seconds. If Tw <= 0, wait a small default time period.
- Set Tpi to Ti.
- Perform steps 1 through 8.
The algorithm assumes each time variable is in seconds. In practice, Javascript returns time in milliseconds, and conversions must be made as necessary. The implementation uses Javascript's Date object and its getTime method to obtain the current time. It uses the browser API's setTimeout function to delay the execution of the loop the specified time period.
The initLoop function implements the algorithm in an internal function. The variable Tpi (the time stamp at the start of the previous frame) is in the scope of initLoop and thus also accessible to the inner loop function. Being in the scope of the outer function allows the variable to maintain its value between executions of the inner function. An alternative way of maintaining this state is to pass the value as a parameter to the loop function every time it is called again. However, using a closure is the cleaner and more semantic approach.
When starting the loop, there is not a previous frame from which to derive a proper deltaT. The implementation runs the loop once with a deltaT of 0 by setting the variable holding the time stamp of the previous frame to the current time right before executing the loop. Then Ti for the first frame will be essentially the same as that variable, creating a deltaT of 0 (or so close to 0 that it's negligible). The loop will then wait long enough before being executed again to have a proper deltaT, and each frame from that point should likewise have a usable elapsed time.
The initLoop function is public. A developer needs only to require the glob.game.loop module to use it. In future development, however, the looping mechanism will be more often used indirectly. The step function will be defined by a game management module, and it will take care of updating various other modules, such as input and graphics. Rather than defining the step function, the user will configure the other modules' behaviors, and the game management module will create its own step function based on those configurations. The details of this system are still in the works, however. The game management module will be developed as other modules are finished the plans for the overall architecture are more concrete.
Subscribe to:
Posts (Atom)