Making a 3D web runner game #7: Colliding with the objects

Today, let’s continue our 3D runner game and make a basic collision system!

This article is also available on Medium.

Last time, we worked on user inputs and we finally implemented the logic to steer our ship left and right with the arrow keys. We also talked about math interpolation and saw how to create animations using basic handmade lerp objects.

Today, we’re going to implement a core feature of our game: the collisions with the objects in the level.


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

Implementing basic collisions

In this tutorial, we are going to create a simple distance-based collision system.

Remember that we have two types of objects: the obstacles and the bonuses.

Hitting an obstacle will damage our ship: we will loose some hull integrity, that will be comparable to health points. On the other hand, getting close to a bonus will have us pick it up and will increase our current score depending on the price of this bonus.

Checking for distances against thresholds

To know if we are colliding with an object, we will simply compare our distance to the 3D mesh to some threshold – but we’ll also need to remember to take into account the size of the mesh itself!

Ok so, we can actually define this collision threshold as a class variable, at the top of our game.js file:

Note: I chose a threshold of 0.2, but once again you might have to adapt this value depending on your game 😉

Then, let’s go down to the _checkCollisions() method we prepared a while ago and that’s empty for now. This is where we’ll want to compute the distances and check them against our threshold.

We already saw in a previous episode how to iterate through all of our objects: we can use the traverse() method from Three.JS on our this.objectsParent variable to go through all of its hierarchy:

Now, we want to know whether we’re colliding with this object or not.

To begin with, let’s define the threshold distance for collisions: this is the maximal distance our ship can be from the object for the game to consider there is a collision.

This distance will basically be half the size of the mesh, plus the little offset that we defined as a class variable. But note that we’ll have to have two separate variables for the X and Z axes because our obstacles can have different sizes along the two axes.

This way, we get a bounding box for our object that is a bit extended to encompass a larger volume. And if the centre of mass of our ship enters this zone, then we will trigger the code for a collision.

Along the Z axis, we just have to check to see if the object’s world Z position is over the thresholdZ variable (because remember that negative values are in front of us and positive values are behind us). Along the X axis, we have to check for the distance between the object’s position and our current X-translation. We can come from either the left or the right, so we’ll use the Math.abs() function to always get a positive value. And then we’ll just compare it our thresholdX variable:

Reacting to collisions

So now – if we are indeed close enough to the object for a collision, we’ll want to perform a different action depending on the object type. We’ll do the same as in our _updateGrid() method and look at the type property we store in our userData:

If the object is an obstacle, we’ll want to reduce the current health; else for bonuses, we’ll want to increase our score. To do all of this, we’ll need to create some new variables.

Let’s go to our constructor and add the this.health and this.score variables. We’ll start with 100 health points and a score of 0:

Then, when we spawn our bonuses, let’s return the random price from the _setupBonus() function and add it in our userData object. This way, we’ll have access to this value later on, when we collide with the object:

Also, don’t forget that we have to update this value when we “respawn” the bonus objects! In our _updateGrid() method, when we use object pooling to reset our objects, if the object is a bonus, we’ll get the newly computed price from our _setupBonus() function and re-assign it in the userData of the 3D object:

Finally, we can modify the new health or score variables in our _checkCollisions() method depending on the type of object that we hit. For now, we’ll do a little console log of the updated value to see if it works:

We also need to “respawn” our objects so that it looks like the object we hit was “destroyed”. We’ll compute the same parameters as in our _updateGrid() method and apply the proper function to perform the object pooling:

So, now, if I run my game with the console opened, as soon as I hit an object, I’ll get a debug of the updated value of either my health or my score:

But using console debugs is not great! Instead, it would be better if we had some UI on our screen to display this kind of info. We’ll dive more into this next time but, before implementing the full UI, we can actually start today with just a draft version.

Creating a (very) simple UI

Let’s head over to our index.html file and add some new <div>s to our <body> tag. The “info” div will be the container for this UI and then we have two easy-to-refer-to divs, with ids “health” and “score”:

In our Game class, we can use the Javascript built-in method document.getElementById() to access those DOM elements and store them in our instance; so we’ll create two new variables in our constructor: the this.divHealth and this.divScore:

Note: if you want to learn more about parsing and manipulating DOM element with pure vanilla JavaScript, you can check out this other article I wrote a while ago 😉

Now, we can simply update the inner contents of these divs when we have a collision – either the one for health if we hit an obstacle or the one for score if we hit a bonus:

If you save your file and refresh the game, you’ll see that there are some additional divs at the top of your page that regularly update as your health and score variables are updated:

To wrap this up, we can go to our index.css file and add a bit of styling to the “info” container to position it in absolute mode over the scene canvas. This will avoid the div “pushing down” the content and instead have it come as an overlay on top of it. We’ll also make the text white to insure it is clearly readable on the dark background:

Save again and, this time, you’ll get a simple but well-readable display of your current health and score in the top-left corner! 🙂

Conclusion

Today, we saw how to compute simple collisions based on distances and thresholds and we added some new variables to our Game instance. We re-used the hierarchy traversal method we saw a couple of tutorials ago and we started working on a very basic UI.

In the next episode, we’ll continue adding elements to our screen to display more info or show it in a prettier way and we’ll also add a little intro panel to only start running the game when we click on the “Start” button…

Leave a Reply

Your email address will not be published. Required fields are marked *