Building a Game With TypeScript. Drawing Grid 5/5

Greg Solo
ITNEXT
Published in
9 min readSep 9, 2020

--

Chapter III in the series of tutorials on how to build a game from scratch with TypeScript and native browser APIs

People vector created by freepik

Welcome to the final part of Chapter III, “Drawing Grid”! The previous post was about implementing and testing a humble yet powerful rendering system. In this final article of the chapter, we will tie up all loose ends.

Having the rendering system is great, but how can NodeDrawComponent access it? Do we have to pass the instance of Canvas to the Node? If so, then we have the same problem as before: we couple Node with a drawing context. The only difference being that we would have to pass a reference to Canvas rather than native context, which doesn’t minimize the problem. Something is off.

In Chapter III “Drawing Grid”, we are implementing a fundamental piece of the turn-based game: we are drawing the grid of nodes. Other Chapters are available here:

Feel free to switch to the drawing-grid-4 branch of the repository. It contains the working result of the previous posts and is a great starting point for this one.

Table of Contents

  1. Introduction
  2. Canvas Layers
  3. Testing CanvasLayer
  4. Drawing with Layers
  5. Re-Drawing
  6. Conclusion

Canvas Layers

Background photo created by freepik

There is also another question we have to answer. We agreed to draw all nodes on the same canvas. But what about other elements, for example, ships? Take a look at this gif:

This is a demo of the gameplay we are looking to achieve

This is the game we are trying to implement. These circles are “Ships”, and we will talk about them in the next chapter. But for now, we should keep in mind that these “Ships” have to be on top of the Grid. Always.

To address this, we can introduce a notion of layers” of the canvases, similar to the layer system you may have seen in image editors like Gimp. Having layers, we can put one canvas on top of another. For example, we can place Grid along with any decorations on one, the “bottom” (“background”) layer. And then put Ships on another, “top” (“foreground”) layer. This approach can guarantee Ships are always drawn on top of the Grid.

At this moment, native Canvas API has no easy way to manage “layers”. There is globalCompositeOperation, of course, but as the name suggests, it’s a global setting. Working with it has a plethora of limitations. To give ourselves a room for maneuver, we will define our layering system.

I start by defining the CanvasLayer class. It will manage access to every layer we decide to add to the game:

Let’s not forget about the barrel file:

This class will ensure the game always has only one canvas of a specific type. Though neither Canvas nor CanvasLayer are singletons, static CanvasLayer ensures Canvas is instantiated only ones:

First, I defined a static field for background Canvas and a standard public getter with one caveat. This getter first checks if the field is empty. If so, it means background canvas has not yet been instantiated, and getter has to construct it first.

We are going to use CanvasLayer in static context only, so I make constructor private to prevent anyone from accidental instantiation.

Testing CanvasLayer

Background vector created by freepik

Before we move any further, let’s take a second to cover CanvasLayer with tests.

We should verify that Background is always the same, no matter how many times we access it:

I start by importing and mocking the Canvas. I also make sure it was never called before:

Then I request the Background layer a couple of times and check how many times Canvas is instantiated:

Great! At this moment your code should successfully compile with npm start, and all test should pass with npm t:

Drawing with Layers

Watercolor vector created by macrovector

Now, finally, we can clean up NodeDrawComponent and utilize our awesome layer system:

Your code should compile with npm startand render:

Moreover, if you check the dev tools, you should now see only one canvas:

Excellent, exactly what we were after! Yet, there is one more thing left to cover. At this point, we draw a node only once: when NodeDrawComponent awakens. This works and is super efficient but, unfortunately, not very flexible.

It is easy to fall into the trap and assume that the grid is somewhat static and should be drawn only once per lifetime. After all, it’s just a background of the game, right? Well, yes and no.

It sure is a background in terms of positioning on the screen. But that doesn’t mean it’s static. We have to interact with this layer: click on the node should be signal to a player’s ship to move to this node:

This is a demo of the gameplay we are looking to achieve

This is the reason we made all this journey to create Grid and Node entities rather than simply draw them on canvas. But if you take a closer look at the gif above, you may notice something else interesting. Color of Node should be able to change to help players understand where they can click.

How can we achieve this behavior? The way browser canvas works, after the shape has been drawn, you lose all the control of it. There is no reference to color property or something. To change the color of a rectangle, NodeDrawComponent has to redraw: clean up and draw again but with a new color.

Re-Drawing

Icon vector created by rawpixel.com

There are a few ways we can do this. One of them is utilizing the Update method. If you recall, each Component has Update, a method which is called by the Entity this Component belongs to.

NodeDrawComponent is no exception and has its own Update method, which we kept empty for a while:

Instead of drawing once NodeDrawComponent awakes, we will draw on every Update. This way, we can be sure that if something changes, Node will have the most relevant representation.

To do so, we can move the Draw method call from Awake to Update:

Of course, we should clean up the respective area first. Fortunately, we already prepared a proper API in our little rendering engine, the ClearRect method:

First, we set up a private Clear method. Its responsibility is to clean up anything that has been drawn on a specific area of the canvas, precisely the spot where we used to draw a rectangle. Then, on every frame, we clean up and draw again.

“We draw again every frame??? Is that even performant?!”, you may ask. We could indeed implement some smart system that checks if there even was any change. And, if so, only then trigger the redraw. This would be more performant, of course.

But, as you probably have noticed, I keep using an incremental approach in this tutorial. We start simple, adding more complexity as we go. The current solution is performant enough for us: the number of nodes is limited, and the drawing of each node is quite cheap. Our architecture allows us to revisit and improve if we’ll face performance bottlenecks in the future.

And again, your code should successfully compile with npm start and all test should pass with npm t:

Before we say goodbye to this chapter, let’s add a cherry on top of this beautiful cake. Let’s cover NodeDrawComponent with some tests!

Testing NodeDrawComponent

School vector created by freepik

Initial setup should look familiar:

However, we will have plenty of other Node components. And for every single one of them, we would have to make the same setup. Let’s save us some time and effort and create a mock factory for the Node, which we can reuse over and over again.

Mock factory is simply a function that builds a Node for us with the specified params:

To make it slightly more convenient, we can set up a default arguments:

Don’t forget to update the barrel file:

Now, back in the NodeDrawComponent test I will use the mock factory instead of calling Node’s constructor directly:

Nice! All is left is to check if NodeDrawComponent executes proper methods of CanvasLayer. For example, as soon as NodeDrawComponent awakes, it should clean up the canvas:

Similarly, NodeDrawComponent should cleanup and redraw on every update:

Yet again, your code should successfully compile with npm start and all test should pass with npm t:

You can find the complete source code of this post in the drawing-grid-5 branch of the repository.

Conclusion

Awesome! This concludes Chapter III, “Drawing the Grid”. We accomplished a lot in this final part of the Chapter! We discussed the notion of layers of canvases in our game and set up a provider of the Background layer. We used it within NodeDrawComponent, which now continuously redraws Node every frame, ready to react on any change.

This chapter was dedicated to drawing the Grid: the fundamental piece of our game. Let’s stop for a moment and appreciate the path we have passed.

We started our first unsure steps by drawing the grid directly and “dirty” with the browser’s canvas API. We then established a structural hierarchy of the game by defining Grid and Node entities. We found it reasonable to make drawing logic a specific component of the Node: NodeDrawComponent. We make it possible to easily pass tuple data, like coordinates and sizes, thanks to the Vector2d structure. Finally, we created a small rendering engine and layer system. It sure has been a long journey, but I hope you enjoined it!

Next time we start a new chapter. We will take a look at the core game mechanics and introduce even more entities and components. I cannot wait to see you there!

If you have any comments, suggestions, questions, or any other feedback, don’t hesitate to send me a private message or leave a comment below! If you like this series, please share it with others. It really helps me keep working on it. Thank you for reading, and I’ll see you next time!

This is Chapter III in the series of tutorials “Gamedev Patterns and Algorithms in Action with TypeScript”. Other Chapters are available here:

--

--

Software Engineer. Immigrant. Entrepreneur. I have been telling stories through software for 15 years in the hope to craft a better future