Vuxer
Vuxer

Reputation: 51

Collision detection in javascript game

I'm currently working on a little platform game. But it's not going really well with the collision detection. I've looked at many tutorials on this but is can't seem to understand it. Here's my code:

var ctx = document.getElementById("canvas").getContext("2d");
var rightPressed =false;
var spacePressed =false;
var leftPressed=false;
var gravity = 1;
//Player object
var player = {
  x:50,
  y:370,
  dy:0,
  dx:0,
  width:10,
  height:10,
  speed:10,
  jumping:false
};
//Box object
var box ={
  x:200,
  y:350,
  width:50,
  height:50
};

document.addEventListener("keydown", keyDown);
document.addEventListener("keyup", keyUp);

function keyDown(e) {
  if(e.keyCode ==39){
    rightPressed=true;
  }
  if (e.keyCode ==32){
    spacePressed=true;
  }
  if (e.keyCode ==37){
    leftPressed =true;
  }
}
function keyUp(e) {
  if(e.keyCode ==39){
    rightPressed=false;
  }
  if (e.keyCode ==32){
    spacePressed=false;
  }
  if (e.keyCode ==37){
    leftPressed =false;
  }
}
//Draw the ball
function drawBall(){
  ctx.beginPath();
  ctx.arc(player.x,player.y,10,0,Math.PI*2);
  ctx.fillStyle="red";
  ctx.fill();
}
//Draw the box
function drawBox() {
  ctx.fillRect(box.x,box.y,box.width,box.height);
}
//The main function that calls the other functions and handles the logic
function draw(){
  ctx.clearRect(0,0,canvas.width,canvas.height);
  drawBall();
  drawBox();
  if (rightPressed){
    //Right speed of the ball
    player.x+=5;
  }
  if(leftPressed){
    //Left speed of the ball
    player.x-=5;
  }
  if(spacePressed){
    if(!player.jumping){
      //The hight of the jump	
      player.dy=-player.speed * 2;
      player.jumping = true;

    }
  }
  player.dy+=gravity;

  player.x+=player.dx;
  player.y+=player.dy;

  if(player.y >= 390){
    player.y = 390;
    player.jumping = false;
  }
  //Box collision logic

  window.requestAnimationFrame(draw)
}
window.requestAnimationFrame(draw);
canvas {border:1px solid black;}
<canvas id="canvas" width="400px" height="400px"> </canvas>
CODEPEN HERE

Upvotes: 1

Views: 2118

Answers (2)

myfunkyside
myfunkyside

Reputation: 3950

I think this is the behaviour you're looking for. The following code does all the magic:
(see the code snippet below for a working demo)

//CHECK COLLISION
var collisionObjects = [rect.box, rect.wall];
for (var i=0,count=collisionObjects.length; i<count; ++i) {
    var obj = collisionObjects[i];
    var playerLeft=x-player.width, playerRight=x+player.width, playerTop=y-player.height, playerBottom=y+player.height;
    var objectLeft=obj.x, objectRight=obj.x+obj.width, objectTop=obj.y, objectBottom=obj.y+obj.height;
    //check if player is either touching or within the object-bounds
    if (playerRight>=objectLeft && playerLeft<=objectRight && playerBottom>=objectTop && playerTop<=objectBottom) {
        if (player.y+player.height==objectTop || player.y-player.height==objectBottom) {y=player.y;} //player is already colliding with top or bottom side of object
        else if (player.x+player.width==objectLeft || player.x-player.width==objectRight) {x=player.x;} //player is already colliding with left or right side of object
        else if (playerRight>objectLeft && playerLeft<objectRight && playerBottom>objectTop && playerTop<objectBottom) {
            //check on which side the player collides with the object
            var sides = {left:Math.abs(playerRight-objectLeft), right:Math.abs(playerLeft-objectRight), top:Math.abs(playerBottom-objectTop), bottom:Math.abs(playerTop-objectBottom)};
            var side = Math.min(sides.left, sides.right, sides.top, sides.bottom); //returns the side with the smallest distance between player and object
            if (side==sides.top) {y=objectTop-player.height;} else if (side==sides.left) {x=objectLeft-player.width;} //first check top, than left
            else if (side==sides.bottom) {y=objectBottom+player.height;} else if (side==sides.right) {x=objectRight+player.width;} //first check bottom, than right
        }
    }
}

But in order for this to work, I had to change some other things too:

  • Instead of directly updating player.x and player.y, I had to create variables x and y at the start of draw, and only at the and update player.x=x, player.y=y.
    So the whole function draw() should be copied I think to be save.

But don't blindly copy it, I also made some other changes, mostly for my own understanding and readability of your script. Do with that what you want.. you like it, copy it, you don't, leave it:

  • I changed player.speed into player.speedx and player.speedy. These properties are also used in draw(), so be careful with that.
  • I added variables floor and end, to restrict the ball from leaving the canvas.
  • I changed the gravity value to the gravity value on earth.
  • I changed var box to var rect, an object containing all rectangular shapes. If you want to place a new rectangle, just add a new one in the rect object and that's it.
    In draw() I changed drawBox(); to for (key in rect) {if (rect.hasOwnProperty(key)) {drawRect(rect[key]);}}, a loop that draws all the objects that are inside the rect object.
  • var collisionObjects = [rect.box, rect.wall]; is an array that contains all the objects that should trigger a collision with the player.
    As you can see in the demo, the yellow shelf doesn't trigger a collision, because it is not in the array.
    (If you do add the shelf to the collisionObjects-array, the ball still falls through after about half a second, I haven't figured out why. I thought it had to do with the width (height in code), but that's not it. For some reason, if the object doesn't touch the floor, the ball will eventually fall through.)

Code snippet:

var ctx = document.getElementById("canvas").getContext("2d");
//OBJECTS====================
var player = {x:50,y:100, dx:0,dy:0, width:10,height:10, speedx:5,speedy:10, jumping:false, color:"red"};
var rect = {
	box: {x:150,y:350, width:50,height:50, color:"blue"},
	shelf: {x:220,y:250, width:60,height:5, color:"gold"},
	wall: {x:360,y:300, width:10,height:100, color:"green"}
};
//VARS====================
var rightPressed = false;
var spacePressed = false;
var leftPressed = false;
var gravity = 0.98;
var floor = canvas.height-player.height;
var end = canvas.width-player.width;

//KEY-HANDLERS====================
document.addEventListener("keydown",function(e) {
	if (e.keyCode==39) {rightPressed=true;} //RIGHT
	if (e.keyCode==37) {leftPressed=true;} //LEFT
	if (e.keyCode==32) {spacePressed=true;} //JUMP
});
document.addEventListener("keyup",function(e) {
	if (e.keyCode==39) {rightPressed=false;} //RIGHT
	if (e.keyCode==37) {leftPressed=false;} //LEFT
	if (e.keyCode==32) {spacePressed=false;} //JUMP
});

//DRAW====================
//OBJECTS--------------------
function drawPlayer() {
	ctx.fillStyle = player.color;
	ctx.beginPath();
	ctx.arc(player.x,player.y,10,0,Math.PI*2);
	ctx.fill();
}
function drawRect(obj) {
	ctx.fillStyle = obj.color;
	ctx.fillRect(obj.x,obj.y,obj.width,obj.height);
}
//SCENE--------------------
function draw() {
	ctx.clearRect(0,0,canvas.width,canvas.height);
	drawPlayer(); //draw player
	for (key in rect) {if (rect.hasOwnProperty(key)) {drawRect(rect[key]);}} //draw all objects in 'rect'
	
	//MOVE PLAYER
	var x=player.x, y=player.y;
	if (rightPressed) {x = x+player.speedx;} //right
	if (leftPressed) {x = x-player.speedx;} //left
	if (spacePressed && !player.jumping) { //jump
		player.jumping = true;
		player.dy = -player.speedy * 2; //jump-factor
	}
	
	x = x+player.dx;
	if (x <= player.width) {x=player.width;}
	if (x >= end) {x=end;}
	
	player.dy += gravity;
	y = y+player.dy;
	if (y >= floor) {y=floor; player.jumping=false;}
	
	//CHECK COLLISION
	var collisionObjects = [rect.box, rect.wall];
	for (var i=0,count=collisionObjects.length; i<count; ++i) {
		var obj = collisionObjects[i];
		var playerLeft=x-player.width, playerRight=x+player.width, playerTop=y-player.height, playerBottom=y+player.height;
		var objectLeft=obj.x, objectRight=obj.x+obj.width, objectTop=obj.y, objectBottom=obj.y+obj.height;
		//check if player is either touching or within the object-bounds
		if (playerRight>=objectLeft && playerLeft<=objectRight && playerBottom>=objectTop && playerTop<=objectBottom) {
			if (player.y+player.height==objectTop || player.y-player.height==objectBottom) {y=player.y;} //player is already colliding with top or bottom side of object
			else if (player.x+player.width==objectLeft || player.x-player.width==objectRight) {x=player.x;} //player is already colliding with left or right side of object
			else if (playerRight>objectLeft && playerLeft<objectRight && playerBottom>objectTop && playerTop<objectBottom) {
				//check on which side the player collides with the object
				var sides = {left:Math.abs(playerRight-objectLeft), right:Math.abs(playerLeft-objectRight), top:Math.abs(playerBottom-objectTop), bottom:Math.abs(playerTop-objectBottom)};
				var side = Math.min(sides.left, sides.right, sides.top, sides.bottom); //returns the side with the smallest distance between player and object
				if (side==sides.top) {y=objectTop-player.height;} else if (side==sides.left) {x=objectLeft-player.width;} //first check top, than left
				else if (side==sides.bottom) {y=objectBottom+player.height;} else if (side==sides.right) {x=objectRight+player.width;} //first check bottom, than right
				player.jumping=false;
			}
		}
	}
	//SET PLAYER POSITION
	player.x=x, player.y=y;
	
	//NEXT FRAME--------------------
	requestAnimationFrame(draw)
} draw();
canvas {border:1px solid black;}
<canvas id="canvas" width="400px" height="400px"></canvas>
codepen: http://codepen.io/anon/pen/bByQJg?editors=0010
jsfiddle: https://jsfiddle.net/zvkgnegt/8/

Upvotes: 1

Dustin Snider
Dustin Snider

Reputation: 699

So I know this isn't going to be helping you fix your code, but I would highly recommend you check out http://wickeditor.com/ if you look at the demo on the front page (click to like the 4th one) you will see it offers great collision detection, you can download the project from the site and it should make it super easy to replicate to fit your needs.

Upvotes: 0

Related Questions