Player collision system tutorial
Image: The player block (pink) is blocked from moving any further north by the grey block.Visit the app
When programming games and simulations, hit detection is among the most awkward features to implement - particularly when done without the assistance of a library. And yet, the detection of collisions between objects, such as players, walls, balls, bullets, vehicles and so on, is of vital importance to the proper functioning of these applications, and the sense of satisfaction we get from them. Even in traditional games such as tag, tennis, hockey, curling, bowling and boxing, the outcome of a game is intimately dependent on whether or not a certain object made contact with another, and the exact nature of the collision. In fact, it probably could be argued that the concept of collision is central to most games that have a physical component, and most sports.
When using natural language, collisions can be described with a fair degree of accuracy without much effort. For example "The car rear-ended me as I was pulling up to a red light", and "Peter hit me on the arm." However, when describing a collision in a simulation, we need to describe it in accurately, and in absolute terms. In a simulation, we are really describing the intersection of two or more 2D or 3D shapes, which is something that falls into the realm of mathematics. It can become complicated quite quickly...especially for those of us who didn't pay attention to the trigonometry lecture...what, me?! Never!
ThreeJS originally had collision utility helper methods as part of its core, but these methods seem to be becoming deprecated in favour of the more general purpose raycaster libraries. These can save you from writing complex maths, (if such a thing bothers you - I'm personally not a fan if I can avoid it), as will physics libraries intended to be used in conjunction with threejs, such as physijs. It is the former of these two techniques that I will exhibit in the following article.
A while back, I thought it would be interesting to create a VR experience or two using threejs. Inspired by my newly acquired Samsung Gear VR, and by memories of playing point-and-click puzzle games like Myst and Goosebumps, I wanted to create a small but atmospheric 3D environment that users could navigate through and enjoy - but not necessarily interact with to a large degree.
I realised that the first step in creating an app such as this would be the construction of a little system that would afford the following effects:
- A first-person camera set-up.
- Player movement controls.
- Horizontal hit detection that would stop players from walking through walls, sofas, trucks, etc.
- Vertical hit detection that would permit environments with uneven floors, but prevent players from falling through said floors.
- Basic force-based physics that would allow the player to move smoothly, with subtle acceleration and deceleration.
I didn't want to use a fully-fleged physics simulation library (such as physijs) as such libraries are quite resource-intensive, and I had no requirement for such a detailed approximation of classical physics. A simulation running in threejs is already quite resource intensive, owing to its implementation in a high-level language, and I am under the impression that apps using both threejs and physijs are quite susceptible to clunky performance on a sizable share of low-powered devices - even despite the fact that the physics engine runs on a separate thread. I personally feel (and just to clarify, this is more of hunch than an extensively researched conviction) that a Unity-powered app may be a more practical choice for such a set-up, if web browser based rendering is not a requirement.
So here we are. A couple of afternoons playing about, and out pops an application. It carries all of the features listed above, and works fairly solidly. There are a couple of notable omissions. The app does not support sloped ground planes, and mouse-based looking is not present. I will build these in when - and if - I begin working on an application that leverages this code.
Before reading the rest of the article, have a play with the app to get a feel for how it behaves.Visit the app
Images above: Its possible to iterate through 3 different camera angles by pressing the C key. The two remaining angles shown here are the first and third person angles.
The application architecture
init method that allow macroscopic control of when they should run their bootstrapping code, and some have a
update method, which gets called each time the threejs renderer draws the scene.
- Core objects
- App: The object that serves as the entry point for the application, and which encapsulates and manages the other objects listed below. It holds the render function definition (the function that gets called repeatedly; each time a frame is rendered), and also describes a function that is run when the browser window is resized.
- Main objects
- Player: The object that represents the user. Has an
addVelocitymethod, which adds newtonian force to the player, which ultimately gets expressed as a directional movement, when the
updatemethod is called. This
updatemethod, however, is also responsible for blocking the player movement along a particular axis if an environment collision is happening. The player object also contains a cuboid mesh object to represent the players physical shape, (which is used to calculate collision detection), and two cameras - the first person camera, and the third person camera. Both of the cameras, and the cuboid are wrapped together in a threejs
Object3D, which allows them to be manipulated as a group. This gives us a camera tracking effect, whereby the two player cameras maintain the same positional and rotational transformations as the player cube.
- Terrain: A collection of threejs cube and plane meshes that represent walls and the ground.
- Composition: A wrapper for some threejs bits and pieces, including the main camera, the renderer, and axes helper, and the clock - from which we get the
- World: Established to house some non-essential threejs bits an pieces. Currently just holds lights.
- Player: The object that represents the user. Has an
- Lesser objects
- Keys: An object that contains state information about keypresses, and that registers keypress event listeners.
- Camera manager: As there are 3 cameras, which exist in more than one place, I created this object to serve as a single interface for camera management. Specifically, this object contiains methods to specify an active camera, and to returns all cameras as an array.
- Debug: A rather mundane object that just updates the tabulated debug information, shown at the top right of the screen.
This article will now draw attention to the most noteworthy parts of the application code. If you wish to read the code in full, visit the app, and view the source. It has not been compressed or minified.
The application initialisation method implementation
The meaty part of this tutorial is the hit detection code. But before we get to that, cast your eyes over the
app object init method. This is the entry point to the application and its worth a quick look.
The application render method implementation
Now, for the application
render method. This is the method that gets called each time a frame is drawn, which will typically be about 60 times every second. This method propogates the update event by calling the update method on all of the submodules, and will also work out intersections between the player object and the walls and floor planes. Note that these intersections are passed to the player object's
update method as parameters, which give it the ability to discern if it should move the player cuboid, or consider it blocked. The
render method also adds a downward directional force to the player if the player is not grounded, and a horizontal directional force if movement keys are depressed.
The second half of the movement restriction implementation is within the
Player.update method, which adjusts the players x, y, and z coordinates, subject to two conditions: Current player velocity, and the absence of a colliding object.