Making a 3D web runner game #2: Designing our Game class

Let’s continue our web infinite runner and prepare our game logic!

This article is also available on Medium.

In the first episode of this series, we talked about our game and the Three.js library. Then, we set up our dev environment and we created a basic 3D scene with a green rotating cube.

Today, we’re going to take a step back and focus more on the logic of our game. We will not implement anything brand new, but we will prepare the overall architecture of our game in a dedicated class and get an overview of the game logic.


This tutorial is available either in video format or in text format — see below 🙂

Setting up our class

Why isolate the code in a class?

For now, we’ve coded everything in our main.js script, in the window.onload() function. This is fine when you have a small project but, as things get more complex, it’s better to organize your project and cut it down into multiple functions and possibly into namespaces.

In this article, we’ll see how to create a Javascript class to completely decouple the game logic from the main routine; the idea is that, by isolating each of your systems and reducing dependencies, you make it easier to update each system. You don’t have to go through all your files each time: when working on a given system in your game, you know that you can focus on the file that defines this specific part of the project logic.

In our case, we want to isolate everything that is specific to our Hyperspeed game from the main routine. So, in the end, the main script will only know that it needs to prepare a 3D scene, that it needs to create a game instance and initialise it and finally that it needs to define an animate loop that calls an update() method on the game instance.

But the main routine will be agnostic of all the details about the 3D scene – those will be handled by the game instance itself.

This will allow us to easily change things inside the Game class without having to worry about the main script, or even switch out the game for a completely different one, as long as it has the same interface, the same entry points 🙂

Adding and importing our new script

Before we start actually coding our JS class, let’s create the script and import it properly in our HTML index. We’ll just add a new game.js script inside of our scripts/ folder, and then update the index.html file <head> import tags:

Here, I’ve done two things: I’ve added a reference to our new game.js file and I’ve moved the import of Three.js up so that it’s the first in the block. This could avoid some errors, because it insures that the lib is loaded before our own scripts that use it 😉

Now, let’s open our brand new game.js script and create a basic Game class in it:

By the way – remember that in Javascript, classes are just syntactic sugar – they are an easier way of writing complex object behavior and, more importantly, wrapping it into a namespace so that the functions you define in your class don’t collide with the ones outside of it.

But they are not “real” classes like the ones in Java, C++ or C# for example, so they don’t have all the features such as data encapsulation, public, protected or private variables, accessors and whatnot.

Preparing the 2 main entry points

There are two main entrypoints that we’ll want to define: the constructor and the update method.

The constructor will be called automatically when we create a new instance of this Game class in our main.js script. It will define some useful variables for the instance, prepare the 3D scene and bind our event callbacks:

The update method will be called in our animate loop. It will implement the “game update pattern”, which is a common programming pattern for video games. The idea of this pattern is that, in a video game, you have lots of systems that you’ll want to query or update regularly. So, basically, you’re going to have an infinite loop, called the “update loop,” that gets some info from your systems and then uses this info to update the state of the game.

Generally speaking, your update loop can be cut in two parts: first, you receive the user inputs via events – that’s event handling. You then combine this data with the current state of the game to compute the next state of the game, or in other words the state that the game should transition to at the end of this update loop iteration.

Let’s elaborate on how both these steps work 😉

Adding event handlers

Event handling is actually pretty straightforward in Javascript because we have some ready-made listeners that we can bind our callback functions to. You might have already heard of those – they are the events like “keydown”, “keyup”, “mousemove” and so on…

Declaring the callback functions

Here, we’ll be playing with the keyboard arrow keys, so we need to listen to the “keydown” and “keyup” events. Those events will change the ship course: they’ll impact our speed along the X axis (because remember that we are automatically moving along the Z axis and we don’t control our movement in that direction, but we do have a say in whether the ship goes left or right!).

We can declare our two callback functions right now, the _keydown() and _keyup() functions.

Note: in this tutorial, I’ll keep my old C# habits and prefix these functions with an underscore to remember that they are util functions that are sort of “private” to the Game class… even though there aren’t “real” classes in Javascript, so they won’t truly be encapsulated 😉

Our _keydown() function is going to take in an event that is passed automatically by the browser, and it’s going to check for the value of the key in this event and react appropriately to move the ship. If we press the left arrow key, we’ll translate towards the left of the screen; if we press the right arrow key, we’ll translate the other way, to the right.

Our _keyup() function won’t need the event; it will basically reset us to an “idle” mode, the initial state where we’re just flying forever across the grid along the Z axis with no velocity along the X axis.

Binding the callbacks to the events

Now that we’ve defined the callbacks, we can actually bind those functions to the browser events as of now (even if the functions don’t do anything for now). We’ll do this in our constructor.

To bind a function to an event, we use the built-in document.addEventListener() JS method. This will simply trigger the callback function whenever the document receives this event. In our case, we want to listen to the “keydown” event for example, so you could think of writing the binding like this:

document.addEventListener('keydown', this._keydown);

The issue is that, if you do that, it will work but you will have problems later on if you try to use the this keyword inside of the _keydown() function. That’s because the this keyword in Javascript is contextual, meaning that its value depends on where the function was called from and how it was wrapped when it was called. If we just put the callback function in the document.addEventListener() method, then the this keyword will have lost all meaning inside the _keydown() function.

To fix this, we have to add a little bit at the end using the bind() method, that passes the proper value for the this keyword to the callback function:

document.addEventListener('keydown', this._keydown.bind(this));

Thanks to that change, we’ll now receive our game instance inside of your _keydown() function when it is triggered, and so we will be able to use the this keyword and have it refer the instance as expected 🙂

We can do the same for the “keyup” events, which finally gives the following code:

And, actually, our event handling is done! We don’t have to add anything in the update() method. Thanks to Javascript built-in methods for binding callbacks to events, it’s pretty easy to react to the keyboard events 😉

Defining the update steps

Let’s now move on to the second part of our update() method and think a bit about what we want to do to recompute our game state. The question is: what should our game instance update regularly? What are the different checks and processes we need to run to react to the player’s inputs and re-adjust the game data?

Well, there are basically 3 things that we need to do: make our infinite runner run infinitely, check whether we’ve “touched” an obstacle or a bonus and update the info panel in the top-left corner.

Auto-moving the ship

We obviously need some bit of logic to make our ship fly infinitely across the grid: we want simulate the infinite run and have a non-null velocity along the Z axis.

Except that… we won’t actually be moving 🙂

We’ll see in a couple of tutorials that we won’t really be moving the ship – instead, we’ll move the grid backwards so that it feels like we’re moving forward. It’s a bit like when you’re in a train and it starts moving: at first, you might feel like the train station is moving instead of you. Similarly, making the grid move towards us we’ll be visually equivalent to making the ship moving forward on it, but it will be much more efficient!

We’ll do this in an _updateGrid() function that we can call in our update() method:

Checking for collisions

The second thing we’ll want to do is check for interactions between the ship and the obstacles or the bonuses on the terrain. Remember that we want to avoid obstacles because they damage the ship, and we want to grab bonuses to increase our score: both those interactions are actually checks for collisions.

So we’ll have a _checkCollisions() method that handles both the obstacles and the bonuses and that will compare our current distance to the various objects in the level to a given threshold:

Updating the info panel

Finally, we’ll have to update the little info panel in the top-left corner of the game screen. This UI element shows us the current travelled distance, the score, the ship’s integrity and other relevant data. We’ll need to refresh it regularly because, for example, the distance keeps on increasing since we’re moving automatically along the Z axis.

This method will use various references to DOM elements in our page to easily update their contents and inform the player of the current state of the game.

Let’s add an _updateInfoPanel() method to call from our update() function:

We’re finished preparing all of our main functions. There are just two additional methods that we can write right now to finish introducing our game logic:

  • first, there is the _gameOver() method that will prepare the “end state” – it will show some UI elements and reset the instance state for a new game
  • and then, there is the _initializeScene() function that will be called in the constructor and that will prepare the 3D scene: we’ll see in the next episode how to transfer this logic from the main.js script into our Game class

All in all, this gives us the following code for our full Game class so far:

Conclusion

Today, we’ve defined a basic skeleton for our Game class that lists all of the methods we will need to implement in the future tutorials and that gives us an overview of the behaviour of our class.

In the next episode, we will transfer the scene initialization logic from the main routine into our Game class and we will create a more complex 3D object: our little spaceship!

Leave a Reply

Your email address will not be published.