JJ Gerrish
JJ Gerrish

Reputation: 882

Detect very first collision only (Collision detection with raycasting)

I'm building an endless runner style game in which there is character at (0,0) who can only move left and right on the X axis (using the arrow keys or the mouse). There are two types of objects that move from z = -10000 towards the character, and once they reach z = 10000 (i.e. behind the character and offscreen), they are reset to z = -10000 I am using the following raycasting code to detect collision between the character and the two types of objects.

Essentially, rays are fired from each of the characters vertices every frame in the render loop. If the rays are intersected by one of the objects vertices and the distance is close to the character (not sure the exact distance it uses), then a collision is detected.

Here is my code:

This runs inside the render loop:

detectCollisionRaycaster(character, objectsArray);

This is the collision detection function:

let cooldown = false;
let objectMesh;
let collisionResults;

function detectCollisionRaycaster(originObject, collidedObjects) {

let originPoint = originObject.position.clone();

    for (var vertexIndex = 0; vertexIndex < objectMesh.geometry.vertices.length; vertexIndex++) {
        var localVertex = objectMesh.geometry.vertices[vertexIndex].clone();
        var globalVertex = localVertex.applyMatrix4( objectMesh.matrix );
        var directionVector = globalVertex.sub( objectMesh.position );

        var raycaster = new THREE.Raycaster( originPoint, directionVector.clone().normalize() );
        collisionResults = raycaster.intersectObjects( collidedObjects, true );
        if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length() && !cooldown) {

            cooldown = true;

            console.info("Object Collided");

            setTimeout(function(){
                collisionResults.length = 0;
                cooldown = false;
            }, 500);

        }
    }
}

The problem

The collision code is firing more than once. I am pretty certain that this is due to the fact that the objects are moving towards the character, and so the object's vertices collide with the ray for more than 1 single frame, and so the collision code is run for as long as the object is "inside" of the enemy.

What I want:

I want the collision detected code to fire explicitly once, pause collision detection for a period of time until the object is no longer colliding with the character, and then continue detecting collision

What I have tried

I tried setting a timeout function inside the collision detected code, which I thought would stop the collision detection from being run in the render loop until that time was over, however all it seemed to do run the code once, pause for the timeout and then continue running the collision detected code for all of the times the object was "inside" of the character.

Upvotes: 3

Views: 847

Answers (1)

Smilliam
Smilliam

Reputation: 119

Since you aren't terminating your loop when you find a collision, it will continue executing the rest of the iterations even when the if condition is met.

Something like this can help you organize your logic better by separating out some of the responsibility that you've given to your previous collision detection method. With two methods, we have separated your logic into one which simply tells us whether or not there has been a collision (isCollision), and another method which uses isCollision to set the cooldown appropriately when a collision is detected.

let cooldown = false;
let objectMesh;
let collisionResults;
let timeout;

function detectCollisionRaycaster(originObject, collidedObjects) {
    if (this.isCollision(originObject, collidedObject)) {
        cooldown = true;
        if (timeout) {
           clearTimeout(timeout);
        }
        timeout = setTimeout(function () {
            cooldown = false;
        }, 500);
    }
}

function isCollision(originObject, collidedObjects) {
    let originPoint = originObject.position.clone();

    for (var vertexIndex = 0; vertexIndex < objectMesh.geometry.vertices.length; vertexIndex++) {
        var localVertex = objectMesh.geometry.vertices[vertexIndex].clone();
        var globalVertex = localVertex.applyMatrix4( objectMesh.matrix );
        var directionVector = globalVertex.sub( objectMesh.position );

        var raycaster = new THREE.Raycaster( originPoint, directionVector.clone().normalize() );
        collisionResults = raycaster.intersectObjects( collidedObjects, true );
        if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length() && !cooldown) {
            console.info("Object Collided");
            return true;
        }
    }

    return false;
}

Edit: This will hold the cooldown until no object is colliding with the player. If you want to set it up so that you pause until the specific object that triggered the collision is the one being checked against, then you would need to preserve a reference to that object and check against it rather than running against the full set of player vertices + objects. One way to do that would be to return a reference to the colliding object from the isCollision method rather than a boolean, and then having another method to explicitly raycast from that object to the player vertices to determine when the collision has ended.

Upvotes: 3

Related Questions