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.
Glob Games
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.
Friday, February 24, 2012
Ping Pong
The next game on the agenda is Ping Pong. It should be familiar to most people who play games. Pong, the most well known incarnation of this game, was the first commercially successful video game. The gameplay is simple. Two paddles move vertically across the screen in an effort to bat a ball toward the opposite paddle. A player scores a point when the other paddle misses the ball and allows it to travel off screen. A victory is achieved if one player reaches a preset score.
Implementation should be straightforward, but the desired result of creating Ping Pong is not simply to have a Pong clone. The core deliverable of the project is the framework. The purpose of Ping Pong is to test and demonstrate the framework's effectiveness. It exists to give context to the framework. Accordingly, the framework will be the focus of this development iteration.
Three modules will be emphasized as I develop Ping Pong: graphics, input, and game management. The graphics module will handle drawing to the canvas. It will include two primary components: a library of functions to handle creating and drawing shapes and a management component that allows the developer to register drawable items without having to worry about drawing directly. Future versions of the module will include the ability to draw images, sprites, and effects, but the current iteration will focus entirely on shapes (such as a line, rectangle, sphere, or polygon).
The input module serves to alleviate the problems associated with Javascript's event system. Javascript's single-threaded nature clashes horribly with the browsers' asynchronous event system. The effect is not as profound in simple web apps, but in a real-time simulation such as a video game, events must be carefully and methodically managed to utilize user input effectively. The current iteration will include support for keyboard input. Mouse input will be added in later development cycles.
The game management module will be at the core of any framework application. It manages the game's flow from initialization to the game loop to termination. It handles timing in the game loop, makes it possible to pause the game, and keeps everything running smoothly and predictably. The single-threaded asynchronous issues described before will be handled by the game management module, as well. Further, navigation from real-time gameplay to menus (perhaps a simple pause screen or a complex configuration menu) or other forms of gameplay (such as moving from an overworld map to a game level) will be managed by the game module. It is intended to be flexible enough to handle any type of activity, whether it be a menu, gameplay loading screen, or anything else.
In addition to these modules, smaller modules may be created for utility functions and other tasks. For instance, object oriented Javascript is made easier by helper functions. These will be encapsulated in a util.lang module. Other utility modules will be added as needed.
Each of the framework modules will be developed alongside the Ping Pong game itself. As the requirements of the game are drawn out, the modules and their functions will be written to aid in the implementation of the game.
Implementation should be straightforward, but the desired result of creating Ping Pong is not simply to have a Pong clone. The core deliverable of the project is the framework. The purpose of Ping Pong is to test and demonstrate the framework's effectiveness. It exists to give context to the framework. Accordingly, the framework will be the focus of this development iteration.
Three modules will be emphasized as I develop Ping Pong: graphics, input, and game management. The graphics module will handle drawing to the canvas. It will include two primary components: a library of functions to handle creating and drawing shapes and a management component that allows the developer to register drawable items without having to worry about drawing directly. Future versions of the module will include the ability to draw images, sprites, and effects, but the current iteration will focus entirely on shapes (such as a line, rectangle, sphere, or polygon).
The input module serves to alleviate the problems associated with Javascript's event system. Javascript's single-threaded nature clashes horribly with the browsers' asynchronous event system. The effect is not as profound in simple web apps, but in a real-time simulation such as a video game, events must be carefully and methodically managed to utilize user input effectively. The current iteration will include support for keyboard input. Mouse input will be added in later development cycles.
The game management module will be at the core of any framework application. It manages the game's flow from initialization to the game loop to termination. It handles timing in the game loop, makes it possible to pause the game, and keeps everything running smoothly and predictably. The single-threaded asynchronous issues described before will be handled by the game management module, as well. Further, navigation from real-time gameplay to menus (perhaps a simple pause screen or a complex configuration menu) or other forms of gameplay (such as moving from an overworld map to a game level) will be managed by the game module. It is intended to be flexible enough to handle any type of activity, whether it be a menu, gameplay loading screen, or anything else.
In addition to these modules, smaller modules may be created for utility functions and other tasks. For instance, object oriented Javascript is made easier by helper functions. These will be encapsulated in a util.lang module. Other utility modules will be added as needed.
Each of the framework modules will be developed alongside the Ping Pong game itself. As the requirements of the game are drawn out, the modules and their functions will be written to aid in the implementation of the game.
Sunday, February 12, 2012
Hangman Postmortem
*Note: In software development, particularly game development, a postmortem is a discussion of the games development after completion. Postmortems enumerate triumphs and pitfalls encountered during development. They're used to reflect on the completed work and make observations on how development in future projects can be improved. Given the morbid nature of Hangman, I thought the meaning of postmortem in this case should be clarified. It is not an attempt at dark humor. It is the vocabulary used by professionals in the game development field.
I chose Hangman for the first game due to its simplicity. Developing a game without complicated control flow or convoluted rules allowed me to focus on refreshing my knowledge of the Javascript language, the DOM API, and application design (particularly design issues specific to Javascript). It also allowed me to familiarize myself with HTML5 syntax, the canvas API, and other new concepts. References I found useful throughout Hangman's development are listed near the end of this post.
Design and Implementation
The design included three modules: graphics, words, and hangman. The hangman module contained the the primary game logic. The graphics module contained functions for drawing to the canvas. Both modules were similar in length. The words module simply contained an object with an array of words that can be used as guess words in the game. The words module served to make adding additional words easy, precluding the need to search for a words array to modify mixed in the game logic code.
The graphics module defined a list of functioned used to draw to the canvas. The module helps separate concerns, keeping all the drawing code separate from the game logic. Each function performs a single drawing task, but it significantly improves the maintainability and readability of the game logic since drawing a shape or a text string requires several steps using the canvas API. If the logic needs to print a message to the canvas, it can simply call printMessage(msg) rather than obnoxiously calling several functions of the canvas API.
The hangman module is the entry point to the game from the browser. It defines the game's control flow and rules. The control flow is simple. It begins by initializing the game state. This step involves choosing a random word from the word list, setting or resetting variables (such as the number of guesses and the list of unguessed letters) to their initial value, and starting the next phase by connecting an event handler to process keyboard events.
The next step is the game loop--it continues to run until a terminating condition is met. Unlike a standard real-time video game, however, an iteration of the Hangman game loop only executes when an event occurs. The game doesn't update unless the user presses a key on the keyboard. This portion of the game flow is contained in an event handler registered on the document. When a key press occurs, the handler processes the event and checks whether a valid key was pressed. Then it checks the letter against what has already been guessed. If the letter is valid and guessable, the logic determines whether it is a good guess or bad. Finally, it updates the canvas to show the new game state.
The final step occurs when a terminating condition is met in the game loop. The player can either win or lose to exit the loop. When all the letters in the mystery word are guessed, the loop exits and prints a win message. When the player exhausts his six guesses and the hangman is complete, the loop prints a lose message and exits. The game then rests in a stopped state until the user presses a key. When any key is pressed, the game loops back to the initialization step and play resumes with a new mystery word.
Lessons Learned
1. Learning the canvas API.
The API is deep and complex, but using its basic functions is easy. By the end of development, I would like to be intimately familiar with the entirety of canvas, but a developer can accomplish a lot just knowing the basics. For Hangman, the rectangle, arc, and path drawing functions were the most useful.
2. The canvas text API is weak.
Unfortunately, the canvas text API is not very powerful. It manages text much as CSS would--you set properties on the context that correspond to CSS font properties to modify the way the text is drawn. Some properties are conspicuously missing, however. The line-spacing property would have been handy, but there was no way to set it. Further, positioning the text accurately is impossible because there is not a way to poll the text's dimensions. The API will return the length in pixels of a string drawn to the canvas, but it will not return the height or other dimensions.
For now, the text API will suffice. Creating my own text library would be an unanticipated and laborious task. However, to make the text manageable in the future, I will need to write some functions to make positioning reasonable, though not perfect. Further, using any non-monospaced font adds horrible complications to text positioning. Only monospaced fonts such as courier should be used in games.
3. Input Processing Is Non-trivial.
Processing user input is difficult. Hangman's input processing was easier because it only used the keyboard, it limited the valid keys to the alphabetic a-z, and the game flow was asynchronous. Even with these simplifications, the program had to determine the key pressed from a key code using a key map, filter invalid keys, and involve the convoluted DOM even handler functions.
The next iteration of development will require an input module. The module will be developed incrementally. The first step will be to simply encapsulate the key map functionality implemented in Hangman. The key map will need to be expanded to include non-alphabetic keys such as numbers and punctuation. Further, the module will need to accommodate the problems that arise when the game flow is not asynchronous. These problems and their solutions will be discussed in more depth in upcoming blog entries.
4. Modules Are Necessary.
I was aware that modules would be necessary prior to beginning development. However, I was unsure of how to provide modules to a language without a built-in module system. I considered rolling my own solution, but that job would have been difficult. I decided to avoid reinventing the wheel by using RequireJS, an AMD-compliant module library. I was afraid it would add unnecessary baggage to the HTML doc that loads the script in the form of additional script tags, but RequireJS is easy to use and unobtrusive.
5. i18n Is Non-Trivial.
I attempted to make Hangman ready for i18n. However, several roadblocks prevented this goal from reaching fruition. First, though I made it possible to easily add alternate-language word lists, I did not make an easy way to swap the lists. Further, I did not consider making the messages printed to the users easily replaceable. Even if the word list was swapped, the international user would be confused by the English instructions and feedback used in the game. In the next iteration, this problem will be fixed by a string table mechanism.
3. Input Processing Is Non-trivial.
Processing user input is difficult. Hangman's input processing was easier because it only used the keyboard, it limited the valid keys to the alphabetic a-z, and the game flow was asynchronous. Even with these simplifications, the program had to determine the key pressed from a key code using a key map, filter invalid keys, and involve the convoluted DOM even handler functions.
The next iteration of development will require an input module. The module will be developed incrementally. The first step will be to simply encapsulate the key map functionality implemented in Hangman. The key map will need to be expanded to include non-alphabetic keys such as numbers and punctuation. Further, the module will need to accommodate the problems that arise when the game flow is not asynchronous. These problems and their solutions will be discussed in more depth in upcoming blog entries.
4. Modules Are Necessary.
I was aware that modules would be necessary prior to beginning development. However, I was unsure of how to provide modules to a language without a built-in module system. I considered rolling my own solution, but that job would have been difficult. I decided to avoid reinventing the wheel by using RequireJS, an AMD-compliant module library. I was afraid it would add unnecessary baggage to the HTML doc that loads the script in the form of additional script tags, but RequireJS is easy to use and unobtrusive.
5. i18n Is Non-Trivial.
I attempted to make Hangman ready for i18n. However, several roadblocks prevented this goal from reaching fruition. First, though I made it possible to easily add alternate-language word lists, I did not make an easy way to swap the lists. Further, I did not consider making the messages printed to the users easily replaceable. Even if the word list was swapped, the international user would be confused by the English instructions and feedback used in the game. In the next iteration, this problem will be fixed by a string table mechanism.
References
HTML5 Canvas by Steve and Jeff Fulton, O'Reilly Press. (available on Safari)
Tuesday, January 24, 2012
Purpose, Names, and Other Things
I created this blog to chronicle the progress of my senior honors thesis. If you're reading, you probably already know something about my thesis. However, I'll start by providing some background for the uninformed.
I am Allen Boone, and I'm a senior studying computer science at Appalachian State University in Boone, NC. To graduate with honors, I'm required to complete an honors thesis my senior year. My thesis is actually more of a project. I am going to create some software, and the actual paper will detail that software--how it was made, the algorithms it employs, the design decisions behind it, how to use it, and so on. I intend this blog to serve many purposes, but one of the foremost is to hold raw writing material for when I formally write the thesis paper. Rather than worry about the paper while coding the software, I can use the blog to keep track of the process, providing a loose rough draft of the paper as I go.
The project itself is to develop an HTML5 Game framework. A more detailed description of the project can be found in the thesis prospectus [link to prospectus -- edit in later]. The short version is that the framework will utilize HTML5's canvas API to make developing awesome browser games a fun and easy experience. It will provide modules to handle graphics, input, control flow, internationalization, and other useful game components.
My goal is to update this blog with a detailed post about the project's progress at least once a week. The progress report will include a description of what I intended to do that week, what I was able to do, any blocks or problems I encountered, and what I plan to do the next week. I'll also post smaller updates periodically as they're warranted. The more detailed posts will serve as a form of accountability for me and as a way for my thesis advisor and other interested parties to stay informed of my progress. The lesser updates will be for record or just for fun.
Of course, it may seem strange that the blog is titled "Glob Games" and not something more academic. I recently realized that I would need a good name for my framework. CS4410ThesisBoone makes for an ugly directory, namespace, and blog title. Something with more brevity works better for those things. Unfortunately, "Glob Games" is the best I could produce in a short timeframe. The name may change, but for the sake of avoiding confusion during development, it will remain "Glob Games" at least until the thesis is finished.
I tried to build an acronym, but I couldn't get a good one to work. I also tried taking the Java approach to naming and chose from a pool of completely unrelated names. I found I couldn't easily get my mind away from existing tech titles. However, somewhere in the midst of that brainstorming "Glob Games" popped into my head, and I decided to go with it. I could easily see coding Javascript that looked like Glob.drawSprite({x: 3, y: 100, anim: lemurRunning}) rather than using some ugly namespace like HONThesisSp2012. Plus, it's alliterative, and I'm fond of alliteration.
If you're interested in my thesis, check back here each week for progress reports. I'm also open to suggestions. The project will certainly change and grow as time marches on, and input will be valuable in that process.
I am Allen Boone, and I'm a senior studying computer science at Appalachian State University in Boone, NC. To graduate with honors, I'm required to complete an honors thesis my senior year. My thesis is actually more of a project. I am going to create some software, and the actual paper will detail that software--how it was made, the algorithms it employs, the design decisions behind it, how to use it, and so on. I intend this blog to serve many purposes, but one of the foremost is to hold raw writing material for when I formally write the thesis paper. Rather than worry about the paper while coding the software, I can use the blog to keep track of the process, providing a loose rough draft of the paper as I go.
The project itself is to develop an HTML5 Game framework. A more detailed description of the project can be found in the thesis prospectus [link to prospectus -- edit in later]. The short version is that the framework will utilize HTML5's canvas API to make developing awesome browser games a fun and easy experience. It will provide modules to handle graphics, input, control flow, internationalization, and other useful game components.
My goal is to update this blog with a detailed post about the project's progress at least once a week. The progress report will include a description of what I intended to do that week, what I was able to do, any blocks or problems I encountered, and what I plan to do the next week. I'll also post smaller updates periodically as they're warranted. The more detailed posts will serve as a form of accountability for me and as a way for my thesis advisor and other interested parties to stay informed of my progress. The lesser updates will be for record or just for fun.
Of course, it may seem strange that the blog is titled "Glob Games" and not something more academic. I recently realized that I would need a good name for my framework. CS4410ThesisBoone makes for an ugly directory, namespace, and blog title. Something with more brevity works better for those things. Unfortunately, "Glob Games" is the best I could produce in a short timeframe. The name may change, but for the sake of avoiding confusion during development, it will remain "Glob Games" at least until the thesis is finished.
I tried to build an acronym, but I couldn't get a good one to work. I also tried taking the Java approach to naming and chose from a pool of completely unrelated names. I found I couldn't easily get my mind away from existing tech titles. However, somewhere in the midst of that brainstorming "Glob Games" popped into my head, and I decided to go with it. I could easily see coding Javascript that looked like Glob.drawSprite({x: 3, y: 100, anim: lemurRunning}) rather than using some ugly namespace like HONThesisSp2012. Plus, it's alliterative, and I'm fond of alliteration.
If you're interested in my thesis, check back here each week for progress reports. I'm also open to suggestions. The project will certainly change and grow as time marches on, and input will be valuable in that process.
Subscribe to:
Posts (Atom)