Three.JS Tower Defense

Creating a Tower Defense game with Three.JS – Part 1

Before starting this big project, if you are still a beginner with Three.JS, I highly suggest you first master the basic concepts explained in our training courses :

If you are ready, let’s begin!

Introduction and presentation of the project

In this grand tutorial, we will develop together step-by-step a Tower Defense game with Three.JS!

Tower defense (TD) is a subgenre of strategy games where the goal is to defend a player’s territories or possessions by obstructing the enemy attackers or by stopping enemies from reaching the exits, usually achieved by placing defensive structures on or along their path of attack.

Wikipedia

Thus, in a 3D orthographic styled world, we develop a universe composed of a forest path, which must be defended from waves of enemies by the towers.

The goal of the game is to prevent the enemies from reaching the end of the path.

Our Tower Defense Game

This tutorial is available in video form !

During the different phases of development, we will cover the following themes:

  • Basics of Three.JS
  • Loading 3D models
  • Animating 3D models
  • Raycaster
  • Programming oriented object and basic JavaScript

Creating a foundation universe

Let’s begin the first step of our project! Let’s create an index.html file:

<head>
      <meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0' />
      <style>
    
      body
      {
        margin: 0; touch-action: none;
        background-image: -webkit-gradient(linear, 0 0, 100% 100%, color-stop(0, #877fa8), color-stop(100%, #f9ae91));
        background-image: -webkit-linear-gradient(135deg, #877fa8, #f9ae91);
        background-image: -moz-linear-gradient(45deg, #877fa8, #f9ae91);
        background-image: -ms-linear-gradient(45deg, #877fa8 0, #f9ae91 100%);
        background-image: -o-linear-gradient(45deg, #877fa8, #f9ae91);
        background-image: linear-gradient(135deg, #877fa8, #f9ae91);
      }
      
          canvas { width: 100%; height: 100% ; touch-action: none;}
      </style>
      <script  type="module"></script>
</head>
<body>
</body>

Next, in this foundation of code, let’s attack the concrete in the beacon script !

Let’s begin by importing Three.JS and MapControls into our project and creating some variables:

<script  type="module">
  
    import * as THREE from '../js/build/three.module.js';
    import { MapControls } from '../js/examples/jsm/controls/OrbitControls.js';
    
    // Basic Threejs variables
    var scene;
    var camera;
    var renderer;
    var clock;
    var controls;

    [...]


Next, let’s create an init function. The latter will be called at the end of our JavaScript code; this will be the entry point of our project:

<script  type="module">
[...]

function init()
{

}

init();
</script>

In this function, we will initialize the previously created variables as well as the foundations of our Three.JS universe.

The Basic Actors

As a reminder, do not hesitate to refresh your memory on the basic concepts of Three.JS:

Let’s begin with the variables clock and scene, respectively of the Clock and Scene types :

function init()
{
  clock = new THREE.Clock();
  scene = new THREE.Scene();
}

Next, our 3D rendering engine renderer, of the WebGLRenderer type.
Still in the init function :

// ---------------- RENDERER ----------------
renderer = new THREE.WebGLRenderer( { antialias : true ,  alpha: true} );
renderer.setPixelRatio( window.devicePixelRatio  );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );  // we add the HTML element to the HTML page

Let’s follow with the initialization of the camera variable. In this project we will use OrthographicCamera :

// ---------------- CAMERA ----------------
const aspect = window.innerWidth / window.innerHeight;
const frustumSize = 10;
camera = new THREE.OrthographicCamera( frustumSize * aspect / - 2, frustumSize * aspect / 2, frustumSize / 2, frustumSize / - 2, 1, 1000 );
camera.position.set( -5, 5, -5 );
camera.lookAt(new THREE.Vector3(0,0,0));
scene.add( camera );

Controlling the camera

Next, let’s initialize an instance of MapControls to control the camera. As a reminder, MapControls is a class from the officials add-ons of Three.JS :

// ---------------- CAMERA CONTROLS ----------------
controls = new MapControls( camera, renderer.domElement );
controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
controls.dampingFactor = 0.05;
controls.screenSpacePanning = false;
controls.minDistance = 2;
controls.maxDistance = 20;
controls.maxPolarAngle = Math.PI / 2;

If you would like more information on basic camera control :

Our basic variables are finally initialized! Now let’s get to filling out our scene.

Lighting in the Scene

Let’s begin with the lighting of our scene. As you may know, the lighting of the scene is most of the time indispensable for the 3D objects to be visible.

Still in our init function :

// ---------------- LIGHTS ----------------
var ambientLight = new THREE.AmbientLight( 0xcccccc, 0.2 );
scene.add( ambientLight );
const directionalLight = new THREE.DirectionalLight( 0xffffff, 0.6 );
directionalLight.position.set( - 1, 0.9, 0.4 );
scene.add( directionalLight );

A cube in the center of the scene

Let’s continue the code of our init function with the creation of a cube in the center of the scene:

var cube;

[...]

const material = new THREE.MeshLambertMaterial();
const geometry = new THREE.BoxGeometry( 2, 2, 2 );
cube = new THREE.Mesh( geometry, material );
scene.add( cube );

The principal animation loop

To finish, let’s occupy ourselves with the principal animation loop. Let’s create a basic render function :

function render()
{
    var delta = clock.getDelta();            //get delta time between two frames
    var elapsed = clock.elapsedTime;    //get elapsed time
    controls.update(); // only required if controls.enableDamping = true, or if controls.autoRotate = true
    renderer.render( scene, camera );     // We are rendering the 3D world
    requestAnimationFrame( render );    // we are calling render() again,  to loop
}

Don’t forget to call render at the end of our init function to start the main loop:

function init()
{
    [...]
    // ---------------- STARTING THE GAME MAIN LOOP ----------------
    render();
}

Final Result

Download the final code : Github.

Congratulations, you have arrived at the end of this first part! Here is the state of our project at the end of this first article:

Tower Defense – Part 1

In the next part, we will study how to implement the functionalities linked to the game map; stay tuned!

2 comments

Leave a Reply

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