Basic Threejs Game Tutorial Part 5: Collision Detection

Introduction

In this article I will show you how to add collision detection to your game. This is something that I originally had a little trouble with as the character would sometimes get stuck in an object. You also want to cease movement when a collision is detected which would only put the collision detection in an infinite loop. I finally fixed the issue by slightly pushing the character away from the object. Now the character won't get stuck even if spawned on top of an object.

Getting Started

We need to check for all collisions upon every execution of the render function. Naturally, we will set the collision checks there. First, let's create a new array to hold the parameters of all objects. In the variable section at the top of your file add the following line of code:

var collisions = [];

Easy enough. Now we want to add all solid objects to this array. Let's create a new object to compute the bounding box of a given object using a nice Threejs class called Box3 that will calculate the needed parameters for you. We are going to call this function calculateCollisionPoints(). The reason we do not use the computeBoundingBox() function is that it will not take scale into effect.

/**
 * Calculates collision detection parameters.
 */
function calculateCollisionPoints( mesh, scale, type = 'collision' ) { 
  // Compute the bounding box after scale, translation, etc.
  var bbox = new THREE.Box3().setFromObject(mesh);
 
  var bounds = {
    type: type,
    xMin: bbox.min.x,
    xMax: bbox.max.x,
    yMin: bbox.min.y,
    yMax: bbox.max.y,
    zMin: bbox.min.z,
    zMax: bbox.max.z,
  };
 
  collisions.push( bounds );
}

In the function above we run computeBoundingBox() on the mesh that is passed, place the results in an object, and then we push to the collisions array we created earlier. Now lets add our new function to the end of our createTree() function. We should only pass the trunk as the character shouldn't need to detect collisions on the top of the tree.

calculateCollisionPoints( trunk );

Finally, we add the collision detection to the render function. Add the following code to the bottom of render().

// Detect collisions.
if ( collisions.length > 0 ) {
  detectCollisions();
}

Alright, here it is. The detectCollisions() function that will tell the system when a collision is detected and stop all movement of the character. While the function may look complex at first glance it is all elementary mathematics.

/**
 * Collision detection for every solid object.
 */
function detectCollisions() {
  // Get the user's current collision area.
  var bounds = {
    xMin: rotationPoint.position.x - box.geometry.parameters.width / 2,
    xMax: rotationPoint.position.x + box.geometry.parameters.width / 2,
    yMin: rotationPoint.position.y - box.geometry.parameters.height / 2,
    yMax: rotationPoint.position.y + box.geometry.parameters.height / 2,
    zMin: rotationPoint.position.z - box.geometry.parameters.width / 2,
    zMax: rotationPoint.position.z + box.geometry.parameters.width / 2,
  };
 
  // Run through each object and detect if there is a collision.
  for ( var index = 0; index < collisions.length; index ++ ) {
 
    if (collisions[ index ].type == 'collision' ) {
      if ( ( bounds.xMin <= collisions[ index ].xMax && bounds.xMax >= collisions[ index ].xMin ) &&
         ( bounds.yMin <= collisions[ index ].yMax && bounds.yMax >= collisions[ index ].yMin) &&
         ( bounds.zMin <= collisions[ index ].zMax && bounds.zMax >= collisions[ index ].zMin) ) {
        // We hit a solid object! Stop all movements.
        stopMovement();
 
        // Move the object in the clear. Detect the best direction to move.
        if ( bounds.xMin <= collisions[ index ].xMax && bounds.xMax >= collisions[ index ].xMin ) {
          // Determine center then push out accordingly.
          var objectCenterX = ((collisions[ index ].xMax - collisions[ index ].xMin) / 2) + collisions[ index ].xMin;
          var playerCenterX = ((bounds.xMax - bounds.xMin) / 2) + bounds.xMin;
          var objectCenterZ = ((collisions[ index ].zMax - collisions[ index ].zMin) / 2) + collisions[ index ].zMin;
          var playerCenterZ = ((bounds.zMax - bounds.zMin) / 2) + bounds.zMin;
 
          // Determine the X axis push.
          if (objectCenterX > playerCenterX) {
            rotationPoint.position.x -= 1;
          } else {
            rotationPoint.position.x += 1;
          }
        }
        if ( bounds.zMin <= collisions[ index ].zMax && bounds.zMax >= collisions[ index ].zMin ) {
          // Determine the Z axis push.
          if (objectCenterZ > playerCenterZ) {
          rotationPoint.position.z -= 1;
          } else {
            rotationPoint.position.z += 1;
          }
        }
      }
    }
  }
}

See the Pen Basic Threejs Game Tutorial Part 5: Collision Detection by Bryan Jones (@bartuc) on CodePen.

Add new comment

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.