Score Manager
Now that we're clearing pipes, we need something to keep track of the score and display it.
Breakdown
We'll be using bitmap images of numbers to draw the score. You could use a font and the canvas fillText()
method, but there are issues in Chrome with pixelated font rendering, so we'll opt for this route. It's a pretty common technique, and it can be more performant than using a font.
The sprite sheet and sprite map are already setup for this. You'll notice the sprite-map.ts
file has a text
sub-property. This is storing the sprite information for all the digits 0 through 9. No different than our other entities.
The new challenge here will be taking a score such as 10
and drawing it to the screen. We'll need to break down the score to it's individual digits, and draw each digit to the screen. We'll also want some padding between the digits. To do this we'll build up an in memory canvas that we draw the digits to, than finally draw that canvas onto our main canvas.
This may seem a little odd. We're going to create a canvas but not add it to the DOM. This is a very useful technique, and is commonly refer to as a buffer canvas. Think of it like constructing an image dynamically in memory, than finally drawing it. In that sense it's no different than what we've been doing all along, only we don't load this image from disk.
Score Manager
Let's create the score manager:
Let's break down some of the new properties that set the stage for our methods.
previousScore
is the score since we last ranupdate()
. We'll use this to check if the score has changed and redraw the score if it has. We don't need to waste time regenerating and redrawing the score if it hasn't changed.scorePosition
is the position of the score on the screen. Centered in x and 24 pixels down from the top.textCanvas
is our in memory canvas and we create it using the commondocument.createElement
method.textCanvas.height
will never change because all our characters are the same height. It's the widht we need to change dynamically.textContext
is obtained fromtextCanvas
. Recall we need a context object to actually draw to a canvas.- We call
convertNumberToImage
in the constructor for the first time to make sure we render a score of0
to start.
Dynamic Score Construction
Let's focus on convertNumberToImage(score: number)
method and it's associated logic.
First we convert the numberic score to a string using .toString()
. This is done because we can iterate over a string easily. characters
is our array of text characters associate with some sprite data.
As we iterate over each character in the string, we push a new object containing:
- The character (digit) itself.
- This won't be used again, but if you need to debug any of this code it's alot easier to know character this data is associated with.
- The associated sprite data for that character, from the sprite map.
- The width of the sprite data for that character. We'll need this to help resize the
textCanvas
.
After we've iterated over the string, we calculate the width of the canvas. We use reduce
to sum the width of each character and the padding between each character and get a final width. The padding comes from characterPadding
class property, which we set to 1
. Something to note is we start the accumulator at -1
to account for the extra padding that will be added to the final character. This will negate that.
Lastly, we iterate our new characters array and draw each character while applying the appropriate padding.
Rendering the Score
Looking at the draw()
method, we see a few noteworthy things:
- If the
previousScore
is different than thescore
, we callconvertNumberToImage(score: number)
to regenerate the score. - We draw the
textCanvas
to thecontext
- which belongs to the game canvas, at thescorePosition
position. - We floor
scorePosition.x
to account for the possiblity that the combination of character widths and padding would produce a fractional pixel position.
Tying it Together
Lets make the appropriate changes to main.ts
to tie everything together.
There you have it! We now have score keeping and what feels like a full game cycle, start to finish.