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
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
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
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.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”:
document.getElementById() to access those DOM elements and store them in our instance; so we’ll create two new variables in our constructor: the
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! 🙂
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…