Javascript: Collision Detection

JavaScript: Collision detection

The first thing to have is the actual function that will detect whether you have a collision between the ball and the object.

For the sake of performance it will be great to implement some crude collision detecting technique, e.g., bounding rectangles, and a more accurate one if needed in case you have collision detected, so that your function will run a little bit quicker but using exactly the same loop.

Another option that can help to increase performance is to do some pre-processing with the objects you have. For example you can break the whole area into cells like a generic table and store the appropriate object that are contained within the particular cells. Therefore to detect the collision you are detecting the cells occupied by the ball, get the objects from those cells and use your collision-detecting function.

To speed it up even more you can implement 2d-tree, quadtree or R-tree.

JavaScript - How to have collision with character and an object?

You will want call another function if your current one determines that they are colliding. Then you will set the player's x and/or y value. So if you collide from the player's left side to the object right side you would set player.x to the object.x + object.w and set the player's velocity.x to 0.

Here's a function that works. You can see it in action on here where I recently answered another question. Just go down to the answer and run the snippet.

2d Square to rectangle collision detection and action(physics)

The collisionDetection() below is different from your in that if any value is true then there cannot be a collision and it just return. Otherwise there is one and it calls the narrowPhase().

function collisionDetection(obj) {
if (player.x + player.w < obj.x ||
player.x > obj.x + obj.w ||
player.y + player.h < obj.y ||
player.y > obj.y + obj.h) {
return
}
narrowPhase(obj);
}

function narrowPhase(obj) {
let playerTop_ObjBottom = Math.abs(player.y - (obj.y + obj.h));
let playerRight_ObjLeft = Math.abs((player.x + player.w) - obj.x);
let playerLeft_ObjRight = Math.abs(player.x - (obj.x + obj.w));
let playerBottom_ObjTop = Math.abs((player.y + player.h) - obj.y);

if ((player.y <= obj.y + obj.h && player.y + player.h > obj.y + obj.h) && (playerTop_ObjBottom < playerRight_ObjLeft && playerTop_ObjBottom < playerLeft_ObjRight)) {
player.y = obj.y + obj.h;
player.vy = 0;
}
if ((player.y + player.h >= obj.y && player.y < obj.y) && (playerBottom_ObjTop < playerRight_ObjLeft && playerBottom_ObjTop < playerLeft_ObjRight)) {
player.y = obj.y - player.h;
player.jumping = false;
player.vy = 0;
}
if ((player.x + player.w >= obj.x && player.x < obj.x) && (playerRight_ObjLeft < playerTop_ObjBottom && playerRight_ObjLeft < playerBottom_ObjTop)) {
player.x = obj.x - player.w;
player.vx = 0;
}
if ((player.x <= obj.x + obj.w && player.x + player.w > obj.x + obj.w) && (playerLeft_ObjRight < playerTop_ObjBottom && playerLeft_ObjRight < playerBottom_ObjTop)) {
player.x = obj.x + obj.w;
player.vx = 0;
}
}

How to detect the side on which collision occured

You'll want to calculate the distance between the x's and y's and also use the minimum distance that they could be colliding along each axis to find the depth along both axes. Then you can pick the smaller depth and move along that one. Here's an example:

if(collision(player, enemy)){
// Most of this stuff would probably be good to keep stored inside the player
// along side their x and y position. That way it doesn't have to be recalculated
// every collision check
var playerHalfW = player.w/2
var playerHalfH = player.h/2
var enemyHalfW = enemy.w/2
var enemyHalfH = enemy.h/2
var playerCenterX = player.x + player.w/2
var playerCenterY = player.y + player.h/2
var enemyCenterX = enemy.x + enemy.w/2
var enemyCenterY = enemy.y + enemy.h/2

// Calculate the distance between centers
var diffX = playerCenterX - enemyCenterX
var diffY = playerCenterY - enemyCenterY

// Calculate the minimum distance to separate along X and Y
var minXDist = playerHalfW + enemyHalfW
var minYDist = playerHalfH + enemyHalfH

// Calculate the depth of collision for both the X and Y axis
var depthX = diffX > 0 ? minXDist - diffX : -minXDist - diffX
var depthY = diffY > 0 ? minYDist - diffY : -minYDist - diffY

// Now that you have the depth, you can pick the smaller depth and move
// along that axis.
if(depthX != 0 && depthY != 0){
if(Math.abs(depthX) < Math.abs(depthY)){
// Collision along the X axis. React accordingly
if(depthX > 0){
// Left side collision
}
else{
// Right side collision
}
}
else{
// Collision along the Y axis.
if(depthY > 0){
// Top side collision
}
else{
// Bottom side collision
}
}
}
}

Working example

Here's a working example that you can play around with. Use the arrow keys to move the player around.

player = {  x: 9,  y: 50,  w: 100,  h: 100}enemy = {  x: 100,  y: 100,  w: 100,  h: 100}output = document.getElementById("collisionType");canvas = document.getElementById("canvas");ctx = canvas.getContext("2d")
function collision(object1, object2) { return !( object1.x > object2.x + object2.w || object1.x + object1.w < object2.x || object1.y > object2.y + object2.h || object1.y + object1.h < object2.y )}
function draw() { ctx.clearRect(0, 0, 400, 400) ctx.lineWidth = "5" ctx.beginPath(); ctx.strokeStyle = "red"; ctx.rect(player.x, player.y, player.w, player.h); ctx.stroke();
ctx.beginPath(); ctx.strokeStyle = "blue"; ctx.rect(enemy.x, enemy.y, enemy.w, enemy.h); ctx.stroke();
}
function handleCollision() { if (collision(player, enemy)) { var playerHalfW = player.w / 2 var playerHalfH = player.h / 2 var enemyHalfW = enemy.w / 2 var enemyHalfH = enemy.h / 2 var playerCenterX = player.x + player.w / 2 var playerCenterY = player.y + player.h / 2 var enemyCenterX = enemy.x + enemy.w / 2 var enemyCenterY = enemy.y + enemy.h / 2
// Calculate the distance between centers var diffX = playerCenterX - enemyCenterX var diffY = playerCenterY - enemyCenterY
// Calculate the minimum distance to separate along X and Y var minXDist = playerHalfW + enemyHalfW var minYDist = playerHalfH + enemyHalfH
// Calculate the depth of collision for both the X and Y axis var depthX = diffX > 0 ? minXDist - diffX : -minXDist - diffX var depthY = diffY > 0 ? minYDist - diffY : -minYDist - diffY
// Now that you have the depth, you can pick the smaller depth and move // along that axis. if (depthX != 0 && depthY != 0) { if (Math.abs(depthX) < Math.abs(depthY)) { // Collision along the X axis. React accordingly if (depthX > 0) { output.innerHTML = "left side collision" } else { output.innerHTML = "right side collision" } } else { // Collision along the Y axis. if (depthY > 0) { output.innerHTML = "top side collision" } else { output.innerHTML = "bottom side collision" } } } } else { output.innerHTML = "No collision" }}
keyStates = []
function handleKeys() { if (keyStates[39]) { player.x += 2 //Move right } else if (keyStates[37]) { player.x -= 2 //Move left } if (keyStates[38]) { player.y -= 2 //Move up } if (keyStates[40]) { player.y += 2 //Move down }}
function main() { handleKeys(); draw(); handleCollision(); window.requestAnimationFrame(main);}
window.onkeydown = function(e) { keyStates[e.keyCode] = true}
window.onkeyup = function(e) { keyStates[e.keyCode] = false}
main();
<h2 id="collisionType"></h2><canvas id="canvas" width='300' height='300'></canvas>

How to add collision detection?

I do highly recommend you change your player and coin to objects and assign their parameters within those objects. This make doing this kind of stuff more dynamic and easier to understand.

For your current code you can write something like

function collision() {
if (
xPos + playerWidth < 100 ||
xPos > 140 ||
yPos + playerHeight < 50 ||
yPos > 50 + 35
) {
return;
}
console.log(true)
}

The number are the x, y, width, and height you assigned to the coin when it was drawn.

A better way to write this code would be to have your player and coin as an object.

let player = {
x: 0,
y: 0,
width: 30,
height: 52
}

let coin = {
x: 100,
y: 50,
width: 40,
height: 35,
}

As you build your game you will find it easier to access and change values this way. I am just simply console logging true when they collide.

const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");

//set player, background and coin info
const playerImg = new Image();
playerImg.src =
"https://www.pngitem.com/pimgs/m/157-1571473_pac-man-png-pacman-png-illustration-transparent-png.png";
const background = new Image();
background.src =
"https://image.freepik.com/free-vector/desert-day-game-background_7814-292.jpg";
const coinImg = new Image();
coinImg.src = "https://image.pngaaa.com/391/204391-small.png";

//create player
function drawSprite(img, sX, sY, sW, sH, dX, dY, dW, dH) {
ctx.drawImage(img, sX, sY, sW, sH, dX, dY, dW, dH);
}

//create score
var score = 0;
function drawScore() {
ctx.font = "10px Arial";
ctx.fillStyle = "#FFFFFF";
ctx.fillText("Score: " + score, 254, 12);
}

//controls

let player = {
x: 0,
y: 0,
width: 30,
height: 52
};

let coin = {
x: 100,
y: 50,
width: 40,
height: 35
};

const speed = 6;
function movement(e) {
if (e.keyCode == 37) {
player.x -= speed;
// player.src = "left.png";
} else if (e.keyCode == 38) {
player.y -= speed;
// player.src = "up.png";
} else if (e.keyCode == 39) {
player.x += speed;
// player.src = "right.png";
} else if (e.keyCode == 40) {
player.y += speed;
// player.src = "down2.png";
}

if (player.x < 0) player.x = 0;

if (player.x > canvas.width - player.width)
player.x = canvas.width - player.width;

if (player.y < 0) player.y = 0;

if (player.y > canvas.height - player.height)
player.y = canvas.height - player.height;
}

document.onkeydown = movement;

function collision() {
if (
player.x + player.width < coin.x ||
player.x > coin.x + coin.width ||
player.y + player.height < coin.y ||
player.y > coin.y + coin.height
) {
return;
}
console.log(true);
}

//document.onkeyup = setInterval(idle, 0600);

//animation
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(background, 0, 0, canvas.width, canvas.height);
ctx.drawImage(coinImg, coin.x, coin.y, coin.width, coin.height);
drawSprite(
playerImg,
0,
0,
30,
52,
player.x,
player.y,
player.width,
player.height
);
drawScore();
collision();
requestAnimationFrame(animate);
}
animate();
<canvas id="canvas"></canvas>


Related Topics



Leave a reply



Submit