In the course of this 3 part article series, you will learn how to write a simple 3D space shooter game with Three.js.

The game will look like this:

It will introduce the basic concepts of a Three.js application, how to write modular code and the core principles of a game, such as camera, player motion and collision detection.

In Part 1 we set up our package and created the world of our game. In this Part 2, we will add the spaceship and the asteroids for our game. ``````var ObjMtlLoader = require('./objmtlloader')
player = null

mesh.scale.set(0.2, 0.2, 0.2)
mesh.rotation.set(0, Math.PI, 0)
mesh.position.set(0, -25, 0)
player = mesh
})`````` So let’s see what’s going on here.

First of all we’re calling ObjMtlLoader.load with the name of the model file (spaceship.obj) where the polygons are defined and the material file (spaceship.mtl) where colors, textures, transparency and so on are defined. The most important thing is the callback function that returns the loaded mesh.

In the callback we’re scaling the mesh to 20% of its original size, rotate it by 180° (Three.js uses euler angles instead of degrees) for it to face the vortex instead of our camera and then we finally positioned it a bit downwards, so we get a nice angle.

Now with our spaceship in space, it’s time to take off!

### Launch it!

Now let’s make things move!

First things first, let’s get a reference to the camera:

``````var loader = new ObjMtlLoader(),
player = null,
cam    = World.getCamera()``````

and instead of adding the spaceship directly to the world, we will make it a child of the camera and add the camera to the world instead. We should also adjust the position of the spaceship a bit:

``````player.position.set(0, -25, -100)
``````

Now in our render function we move the camera a bit more into the vortex on each frame, like this:

``````function render() {
cam.position.z -= 1;
}
``````

This makes us fly into the vortex… ### Intermission – modularize your code

Now that our code gains size and function is a good time to come up with a strategy to keep it understandable and clean. A little housekeeping, if you will.

The strategy we’re going for in this article is splitting the code up in modules. Every bit of the code that is related to a small, clearly limited piece of functionality will be put into a separate module and interacts with other modules by using the functions those other modules expose.

Our first example of such a module is the player module: Everything that is related to our player, the spaceship and its motion should be captured into a player module.

So we’ll create the js/player.js file.

``````var ObjMtlLoader = require('./objmtlloader')

var spaceship = null

var Player = function(parent) {

mesh.scale.set(0.2, 0.2, 0.2)
mesh.rotation.set(0, Math.PI, 0)
mesh.position.set(0, -25, 0)
spaceship = mesh
self.player = spaceship
})
}

module.exports = Player``````

And we can also move out the tunnel code into a module called tunnel.js:

``````var THREE = require('three')

var Tunnel = function() {
var mesh = new THREE.Mesh(
new THREE.CylinderGeometry(100, 100, 5000, 24, 24, true),
new THREE.MeshBasicMaterial({
tex.wrapS = tex.wrapT = THREE.RepeatWrapping
tex.repeat.set(5, 10)
tex.needsUpdate = true
}),
side: THREE.BackSide
})
)
mesh.rotation.x = -Math.PI/2

this.getMesh = function() {
return mesh
}

return this;
}

module.exports = Tunnel``````

Using these modules, our main.js now looks more tidy:

``````var World  = require('three-world'),
THREE  = require('three'),
Tunnel = require('./tunnel'),
Player = require('./player')

function render() {
cam.position.z -= 1;
}

World.init({ renderCallback: render, clearColor: 0x000022})
var cam = World.getCamera()

var tunnel = new Tunnel()

var player = new Player(cam)

World.getScene().fog = new THREE.FogExp2(0x0000022, 0.00125)

World.start()``````

The advantage is that we can reuse these modules in other projects and the code in main.js is pretty straight forward.

Now when you keep the browser tab with our spaceship in the vortex open long enough, you’ll see that we’re flying out of our vortex quite quickly.

### To infinity and beyond!

Let’s make our vortex infinite. To do so, we’ll do a little trick: We’ll use two tunnels, positioned after each other. Once one of them is behind the camera (i.e. no longer visible), we will move it to the end of the currently visible tunnel.

It’s gonna be a bit like laying the tracks while we’re riding on them. Our code will need a little adjustment for this trick. We’ll add a new update method to our Tunnel class and use a THREE.Object3D to hold both our tunnel parts. Our render loop will then call the new update method with the current camera z-coordinate to check, if a tunnel segment is invisible and can be moved to the end of the tunnel.

In tunnel.js it will look like this:

And the update method of the tunnel:

``````this.update = function(z) {
for(var i=0; i``````

This method may look a bit odd at first. It takes the z position from the camera and then checks both tunnel segments (meshes) if they are invisible.

But what’s that -2500 doing in there? Well, that’s because Three.js uses coordinates in a particular way. The coordinates are in the center of the mesh, which means the tunnel reaches from meshes[i].position.z + 2500 to meshes[i].position.z – 2500. The code is accounting for that by making sure that the camera has gone past the farthest point of the tunnel segment, before moving it to a new position.

It’s being moved 10000 units into the screen, as its current position + 2500 is the beginning of the next tunnel segment. The next tunnel segment ends at the current position + 7500. Then we already know that our tunnel will start at its new position – 2500. So all in all, we’ll move the segment by 10000 to make it seamlessly continue the tunnel.

### Space is full of rocks

Now that’s all a bit boring – so we’ll spice it up with Asteroids!

Let’s write our asteroids.js module:

``````var THREE = require('three'),

var rockMtl = new THREE.MeshLambertMaterial({
})

var Asteroid = function(rockType) {
var mesh = new THREE.Object3D(), self = this

// Speed of motion and rotation
mesh.velocity = Math.random() * 2 + 2
mesh.vRotation = new THREE.Vector3(Math.random(), Math.random(), Math.random())

obj.traverse(function(child) {
if(child instanceof THREE.Mesh) {
child.material = rockMtl
}
})

obj.scale.set(10,10,10)

mesh.position.set(-50 + Math.random() * 100, -50 + Math.random() * 100, -1500 - Math.random() * 1500)
})

this.update = function(z) {
mesh.position.z += mesh.velocity
mesh.rotation.x += mesh.vRotation.x * 0.02;
mesh.rotation.y += mesh.vRotation.y * 0.02;
mesh.rotation.z += mesh.vRotation.z * 0.02;

if(mesh.position.z > z) {
mesh.velocity = Math.random() * 2 + 2
mesh.position.set(
-50 + Math.random() * 100,
-50 + Math.random() * 100,
z - 1500 - Math.random() * 1500
)
}
}

this.getMesh = function() {
return mesh
}

return this
}

module.exports = Asteroid``````

This module is pretty similar to the player module but still a lot of things are going on, so let’s go through it:

``````var loader = new ObjLoader()
var rockMtl = new THREE.MeshLambertMaterial({
})
``````

We’re creating a material with a rocky texture, so our rocks look nice. This material will be shared by all the asteroid models later.

``````var mesh = new THREE.Object3D()

// Speed of motion and rotation
mesh.velocity = Math.random() * 2 + 2
mesh.vRotation = new THREE.Vector3(Math.random(), Math.random(), Math.random())``````

In this part of the code we’re creating a THREE.Object3D to later contain the 3D model from the OBJ file and give it two custom properties:

• velocity – how fast the asteroid should move towards the camera
• vRotation – how fast the asteroid rotates around each of its axes

This gives our asteroids a bit more variety as some are moving faster than others, just as they would in space.

``````obj.traverse(function(child) {
if(child instanceof THREE.Mesh) {
child.material = rockMtl
}
})

obj.scale.set(10,10,10)``````

We’re iterating through all the children of the loaded OBJ to make sure they’re using the material we’ve defined at the beginnin of our module, then we scale the object (and all its children) to be nicely sized in relation to our spaceship.

On to the update method:

``````this.update = function(z) {
mesh.position.z += mesh.velocity
mesh.rotation.x += mesh.vRotation.x * 0.02;
mesh.rotation.y += mesh.vRotation.y * 0.02;
mesh.rotation.z += mesh.vRotation.z * 0.02;

if(mesh.position.z > z) {
mesh.velocity = Math.random() * 2 + 2
mesh.position.set(-50 + Math.random() * 100, -50 + Math.random() * 100, z - 1500 - Math.random() * 1500)
}
}``````

This method, just like the tunnel update method is called in our render loop and given the position.z coordinate of the camera.

Its responsibility is to move and rotate the asteroid and reposition it whenever it flew past the camera.

With this module, we can extend the code in main.js:

``````var World = require('three-world'),
THREE = require('three'),
Tunnel = require('./tunnel'),
Player = require('./player'),
Asteroid = require('./asteroid')

var NUM_ASTEROIDS = 10

function render() {
cam.position.z -= 1
tunnel.update(cam.position.z)
for(var i=0;i``````

So we’re creating 10 asteroids, randomly picking from the 6 available types (Math.random() returns something that is smaller than 1, so flooring will result in a maximum of 5). Now we’ve got the asteroids coming at our ship – but they go straight through… we need a way to fight them and we need them to be an actual danger to us!

In the final Part 3, we will set the collision detection, add weapons to our craft and add a way to score and health management as well.