Building a game with TypeScript. Game Loop 2/2

Greg Solo
Level Up Coding
Published in
7 min readAug 1, 2020

--

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

Logo vector created by freepik

Welcome back! This is the series of articles where we discuss how to build a simple turn-based game with TypeScript and native browser APIs! Chapter II is dedicated to building a game loop for this game, other Chapters are available here:

Last time we end up with the question: how can we start the Game Loop without overcomplicating the constructor. One of the approaches we could take is making it ‘awakable’’.

Feel free to switch to the game-loop-1 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. Into
  2. Entity Awakens
  3. Starting the Loop
  4. Nested Entity
  5. Testing Game Entity
  6. Conclusion

Into

If you recall, we made significant progress setting up the game entity. We even prepared its Update method that recursively updates all components of the Game. However, it is not much of a help since nobody makes an initial call to start the loop!

We also established that it is possible to start the loop with the help of constructor:

A better approach would be to provide a dedicated initialization method. This method can start the loop and do many other setups, while the constructor remains lean. We can call this method whatever we like, for example, Init or Awake. I will use the latest to mimic the Unity3d API.

Entity Awakens

Background vector created by freepik

All Entities and Components will now become “awakeble”. That is: they will have the public Awake method.

Note that this lifecycle method doesn’t have a strong relationship with a constructor. An object can be constructed only ones, while it’s Awake may be executed multiple times per the lifespan of an object. It can fall “asleep” and then being awaken again. One of the typical examples is reusing objects to avoid the cost of memory allocation of instantiating the object (read: executing constructor).

It is tempting to simply add a new method to abstract Entity and interface IComponent. But just as Update, Awake can be part of other elements of our game, not only ECS. It is wiser to create a dedicated IAwake interface and then implement it.

The interface is astonishingly simple. We don’t even expect any particular data to be passed:

All is left is to implement it with the IComponent :

and Entity:

Note, that entity awakes all its components as soon as it awakens itself. It is, right as an Update, is a default behavior. Particular entities are free to extend or change it if necessary.

Also, since we are in this neighborhood anyway, let’s do some housekeeping. I will join Awake and Update under one utility module and call it ”lifecycle”. If we need more events like that we can add them here:

Now we can delete obsolete update.h.ts and awake.h.ts. Also, support necessary barrel files to re-export this module properly:

And then update the consumers of these interfaces:

Ah! But we broke the tests! That is because we are not fulfilling the promise: “every Entity and Component should have Awake method”. And the mocks clearly don’t have it. Let’s fix that really quick:

And we definitely should test Awake. I will utilize the same approach as the one we used while testing Update: I first spy on respective methods, add fake components to fake Entity, execute entity’s Awake method and will expect Component’s ones to be called as well:

If you run npm start at this point, your code should compile without errors. If you run tests by npm t they all should be successful too:

Starting the Loop

With all this in place, we can take advantage of a new lifecycle method in the Game entity:

As soon as the Game and all its components awake, we start the game loop:

One little improvement: I want to make sure the game loop doesn’t start before all components and child entities awaken. I do so by delaying Update until the next frame:

Nested Entity

Did I say “child entities?”. That’s right: Game is a root Entity but not the only one. It is a very convent way to have entities organized in the hierarchy. Among others, this allows us to update all these children from within the Game by invoking Update on every one of them.

We will start adding child entities in the next chapter, but for now, let’s just set up a stage and make sure they will be updated.

Character vector created by brgfx

I add a public property that holds an array of all child entities:

By keeping track of all child entities, we can easily invoke all lifecycle methods on them. Moreover, some of them (or even all of them!) may have their children and call Awake/Update on them too.

First, we should awake all children and do that before starting the loop:

And then update them every iteration:

And now, we finally have a fully functioning Game Loop that iterates every frame and updates all Entities and all Components.

Final touch: let’s start this engine by instantiating and awakening the Game itself:

Awesome! If you run your code by executing npm start, it should compile without issues.

Testing Game Entity

We still have no visuals to prove we succeeded on our quest, so the unit test is the only hope.

Man vector created by freepik

Fortunately, testing the Game entity at this point is straightforward. There are five things we are to test:

And to do that, we need to make a few preps. First, let’s set up fake children and components:

I created and instantiated Components, just as I did for the Entity test in the previous chapter. I also created empty entities, instantiated them, and attached to the Game.

We use requestAnimationFrame in the Game. It is an asynchronous callback, and we have to mock it properly. One of the ways of doing so is substituting it with jest.fn and making sure it invokes callback immediately:

This will allow me to test if the update loop works properly:

Here I simply spy on game.Update, awake the Game and expect game.Update to be indeed executed.

Testing the other four scenarios is quite similar to testing Entity we did in the previous chapter:

I start by spying on Awake or Update of every child/component and expect them to be called after game.Awake or game.Update respectively.

Why would we test Awake/Update of components? We already tested the same thing on an abstract Entity. Why repeat?

The reason is that we could override this functionality in Game’s Awake/Update. Remember, the abstract Entity provides only default behavior, and if we override the method (as we did), then we should make sure we are not losing anything.

If you run your tests with the npm t they should all succeed:

You can find the complete source code of this post in thegame-loop-2 branch of the repository.

Conclusion

You should be proud of yourself, you have accomplished A LOT: learned about Game Loop and how it plays in one team with the Entity and Components, how to execute code every frame, discovered lifecycle methods of Entity and how to use them with the Game Loop, and, of course, build the core Game Entity and covered it with tests! Amazing progress!

Next time we are going to finally draw something on the screen and see the beauty of Components in action.

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! Thank you for reading, and I’ll see you next time!

This is the Chapter II in the series of tutorials “Building a game 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