Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fixing simple collision detection with velocity based 2d movement

In this codePen demo you can move "player" square with arrows, place a light with space and are supposed to be stopped going over blue lines from any direction by being pushed to the opposite direction. "player" uses x and y velocity variables to create movement and multiply them by -1 (+some value) if collision detected.
The problem is that after being pushed away from the wall "player" gets stuck in a position where only moving backward from the wall is possible while appearing stuck on a perpendicular axis to that. (for example - if the wall is on top of player you can move only to bottom and not left or right after hitting the wall)
Theoretically, I would want a smooth sliding collision detection where player stuck at the wall would slowly slide down the left or right side depending if left or right arrow pressed. (playing around I am able to achieve this but always one direction would "flow" making player slide down certain direction)
I thought about using rays or some others way to detect hits, but they seem to require more computational time than just plain approach. Would appreciate any input and any recommendations of building scalable collision detections, Here is my basic code for movement and collision detection from the demo:

let xVelocity = 0;
let yVelocity = 0;
var blockedMapGrid = [[0,30],[0,50],[0,100],[0,150],[0,200],[0,250],
                     [50,0],[100,0],[150,0],[200,0],[250,0],[300,0]];


var animate = function() {



if (keyState[37]) {

    xVelocity -= 1;

}
if (keyState[38]) {

    yVelocity += 1;
}
if (keyState[39]) {
    xVelocity += 1;

}
if (keyState[40]) {
    yVelocity -= 1;

}

 for (var i = 0; i < blockedMapGrid.length; i++) {
     if (Math.abs(player.position.x - blockedMapGrid[i][0]) + 
     Math.abs(player.position.y - blockedMapGrid[i][1]) < 36) {
        xVelocity = -xVelocity * 1.2;
        yVelocity = -yVelocity * 1.2;


        console.log("Blocked by " + blockedMapGrid[i][0])
    };
}


player.position.x = player.position.x + xVelocity;
player.position.y = player.position.y + yVelocity;
yVelocity *= 0.80;
xVelocity *= 0.80;

camera.position.x = player.position.x;
camera.position.y = player.position.y;

requestAnimationFrame(animate);


renderer.render(scene, camera);
};
like image 862
Sanchez Panza Avatar asked Dec 03 '25 04:12

Sanchez Panza


1 Answers

This part of your detector is wrong:

Math.abs(player.position.x - blockedMapGrid[i][0]) + 
   Math.abs(player.position.y - blockedMapGrid[i][1]) < 36

Basically, here, you approximate distance from player to the point on grid by using added absolute values instead of root of sum of squares. The truth is, you don't need such a complex grid (repeating lines) and distance.

It looks like you are doing Axis-Aligned Bounding Box (AABB) detection. There is plenty of resources on internet how to optimize it.

But general approach would be like this. Your grid array should consist of boxes with (x,y,w,h) measures. Could be thin, long, square, anything. Let's also assume your player has a bounding box (player.x, player.y, player.w, player.h), then

for (var i = 0; i < grid.length; i++) {
   if (player.x            < grid[i].x + grid[i].w && 
       player.x + player.w > grid[i].x             &&
       player.y            < grid[i].y + grid[i].h && 
       player.y + player.h > grid[i].y) {
   //collision detected! move player to previous known position
    break;
  }
}

You can vary, what you do when collision is detected, but finding if two boxes overlap using 4 conditions is the key here.

Update

Another problem arising from the code in the question is "bouncing" or "getting stuck" after collision is detected.

As a rule of thumb, you should never use velocity = -velocity after collision without also making sure the character gets back into the "clear", i.e. player's bounding box is not overlapping with any obstacles. Otherwise you will be stuck in infinite loop collision? -> vel = -vel, pos += vel*t -> collision -> ... with velocity bouncing from negative to positive and back without ever allowing player to get out of the wall.

The easiest way to fix it is to calculate new position of the player in temporary variables first, check if new position is not colliding, and only then make it permanent and call render(), otherwise simply ignore it and render without moving the player.

Another way is to remember last known "good" position and only give back control of the character, when it is returned to this previous position, possibly after animation or a bunch of uncontrollable moves.

There are more elaborate ways, mostly involving some kind of physics emulation to let the character bounce of multiple obstacles, assuming control inputs do not overpower inertia - think of a car on a slippery road or a boat hitting multiple trees. But either way, after you detect collision and before calling "render()" you have to place the character to a physically possible position, or it will be famously "stuck in textures".

like image 176
Alex Pakka Avatar answered Dec 06 '25 11:12

Alex Pakka



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!