Dodging bullets is fun, but we need to start communicating information to the player. One of the most basic ways to do this is with text. In this post I'll add old school mono-spaced pixel font support to the game, and use that to show some simple placeholder info.
It all starts with a font sheet:
It all starts with a font sheet:
The font sheet is just a normal PNG image with transparency, but it has some special size properties. You'll notice that each character is 16x16 pixels in size, and that there are 16 characters per line and 8 rows. That gives us a total of 128 characters - enough to cover the entire ASCII character set. There are a few characters missing from the image, but most of these are control codes or special characters that we are not worried about. If you want, you could place your own special characters there instead.
Once the font image is loaded, it's easy to use drawImage() to cut individual characters out and draw them to the canvas. Step through a text string one character at a time, printing each one to the screen while advancing a virtual cursor and you're done. All of this functionality is wrapped up in a simple print() function that takes an x/y position and a string. There's a little bit more to do though - firstly I pick up and handle newline characters in the string, and secondly I added the ability to align blocks of text.
You'll see this helper function in use in the font class later on. In the meantime, there's some more info on emulating enumerated types in JavaScript here - which is where I got the implementation idea from.
Once the font image is loaded, it's easy to use drawImage() to cut individual characters out and draw them to the canvas. Step through a text string one character at a time, printing each one to the screen while advancing a virtual cursor and you're done. All of this functionality is wrapped up in a simple print() function that takes an x/y position and a string. There's a little bit more to do though - firstly I pick up and handle newline characters in the string, and secondly I added the ability to align blocks of text.
Alignment
Text alignment can be separated into two types: horizontal and vertical. Horizontal alignment can be either left, middle or right. For left alignment all lines of text start at the supplied x position, for right alignment all lines of text end at the supplied x position and for middle all lines are centered on the x position. Vertical alignment is similar, with top, middle and bottom options.
To make alignment work, I need to know the length of each line and the length of the longest line (for horizontal alignment) and the total height of the text (for vertical alignment). This is pretty easy to figure out from the text string itself, especially since I am using fixed width characters. I wrote a function in the Font class called getExtents() which gets the extents of a piece of text and returns them as an object - we'll see the code for that a little later on.
To make alignment work, I need to know the length of each line and the length of the longest line (for horizontal alignment) and the total height of the text (for vertical alignment). This is pretty easy to figure out from the text string itself, especially since I am using fixed width characters. I wrote a function in the Font class called getExtents() which gets the extents of a piece of text and returns them as an object - we'll see the code for that a little later on.
Enumerations
The other interesting thing you might spot in the Font class is the use of enumerated types. Enumerated types are a way of defining our own special, reserved names instead of using magic numbers in the code. So I could say that the alignment types left/middle/right have a values of 0/1/2, pass one of those values into the print function to specify which type of alignment I want, and then check for these values in the print code and preform the requested type of alignment. I would have to remember those numbers and always use the right ones, and if I ever changed them I would need to go back and find and change all references in the code. It would be better if I could just use the words LEFT/MIDDLE/RIGHT instead. These would make much more sense to me as a programmer, which makes the code easier to read and maintain - and that's exactly what enumerated types are for. Another key thing about an enumerated type is that they cannot be changed - imagine the chaos if I accidentally re-assigned the value of one of the types at runtime? The final trick up the sleeve of enumerated types is that, in a language like C++, we can catch at compile time if the programmer accidentally misspells a value when trying to use it.
Unfortunately, JavaScript doesn't support enumerated types but they can be easily added with some code. I add a Enum class that stores the values inside an object and the freezes them so that they cannot be changed. Another function, disallowUndefinedProperties(), is used to wrap the enum class so that it throws an error if the programmer attempts to read a value that wasn't defined. Finally, because there's a lot to remember here, there's a helper function called makeEnum() that takes an object and turns it into an enumerated type object in one easy step:
The Font Class
Here's the whole font code. You can see the enumerated types for horizontal and vertical alignment at the top, and then the Font class itself underneath.
There are a couple of tricks to look out for in this code. Firstly, the constructor, on line 16, takes as an argument the number of characters wide (charsWide) the font image is. Because we know that there are 128 characters in total, we can use this number along with the width and height of the source image to figure out the size of each character (charWidth and charHeight). The constructor also takes x and y padding values (xSpacing and ySpacing), and when characters are printed I insert this many extra pixels between each character. Without this padding the characters tend to bunch up a bit too close to each other and become hard to read. Finally, look at how the alignment values (xAlign and yAlign) are used to alter the start positions of each line of text, based on the values returned from the getExtents() function.
There are a couple of tricks to look out for in this code. Firstly, the constructor, on line 16, takes as an argument the number of characters wide (charsWide) the font image is. Because we know that there are 128 characters in total, we can use this number along with the width and height of the source image to figure out the size of each character (charWidth and charHeight). The constructor also takes x and y padding values (xSpacing and ySpacing), and when characters are printed I insert this many extra pixels between each character. Without this padding the characters tend to bunch up a bit too close to each other and become hard to read. Finally, look at how the alignment values (xAlign and yAlign) are used to alter the start positions of each line of text, based on the values returned from the getExtents() function.
Wrapping Up
Quite a long post this time. I added font support to the game and then used that to show some basic text on the screen. I also added some utility functions for creating enumerated types.
As always, the code is available here and the playable online version is here.
As always, the code is available here and the playable online version is here.
Next..
Well, it seems that the text doesn't always display - you might need to refresh the page before it shows up. I believe this is due to a small delay in loading the image resources. The first time the page is loaded the font image takes a little while to decode, and I try to generate the font before it's ready to be used. On subsequent page loads the resource is already cached so it's immediately ready to be turned into a font.
This is a defect that needs fixing, so I'll be doing some bug investigation and fixing that in the next post.
This is a defect that needs fixing, so I'll be doing some bug investigation and fixing that in the next post.
Comments
Post a Comment