This is my first post so I'm trying to make my problem as clear as possible. I'm making a game and I want to improve my collision detection. This is because I want to check what side is being hit and stop the player from moving past it without using something general like if(collision(player, enemy)) player.x = enemy.x - player.w(width) because if the player were to collide with the top it wouldn't keep the player on top.
In the code it checks if any one of the statements is true and then returns it but it doesn't tell me which statement was the one that was equal to true so I can stop the player from moving accordingly, if that makes sense. If you have a more efficient collision detection for me to use it would be greatly appreciated.
I've already tried to make a position variable to be equal to whatever side gets collided into and then stop the player from moving past it but it only works for the left side and won't let my player jump over the enemy or block.
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
)
}
//Only works for the left side
if(collision(player, enemy)) player.x = enemy.x - player.w
I expect it to be able to tell me what side is being collided into and then either stop the player from moving past/into it and for the player to be able to be on top of the block/enemy without just being pushed to the left.
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>
Reacting to the collision
Now that you know the side the collision happened on, it should be fairly trivial to decide how to react. It would be very similar to what you are currently doing for the left side just flip some signs around and change the axis.
Other Considerations
You may want to take into account your player's velocity (if it has one) otherwise the detection may fail.
If the player's velocity is too high, it might 'tunnel' through the enemy and no collision will be detected.
The player's movement can also look jittery if the velocity is not stopped upon collision
Can your objects rotate or have more than 4 sides? If so, you'll probably want to use another method as described below.
Here's a good answer to another post that talks in depth about collision engines
Other Methods
As for other collision detection methods, there's quite a few but one that comes to mind is Separating Axis Theorem which is a little more complex than what you have but will work with more complex convex shapes and rotation. It also tells you the direction and distance needed to move to resolve the collision. Here's a site that has interactive examples and goes in-depth on the subject. It doesn't appear to give a full implementation but those can be found other places.
I need to detect wether two objects collide / overlap with each other,
for achieving this purpose I stumbled upon the collision algorithm used in the "run pixie run" game, that didn't work, so I passed to this other function I found on the pixijs forum ( code follows below ), but even this works only in some cases.
The objects involved in the hit test are two DisplayObjectContainer containing a Sprite and a Graphics element (namely a rectangle that used for showing the boundingBox of the sprite).
The sprite has the anchor point set to 0.5 ( for that reason the x/y values in the function are inited like this )
var hitTest = function(s2, s1)
{
var x1 = s1.position.x - (s1.width/2),
y1 = s1.position.y - (s1.height/2),
w1 = s1.width,
h1 = s1.height,
x2 = s2.position.x - ( s2.width / 2 ),
y2 = s2.position.y - ( s2.height / 2 ),
w2 = s2.width,
h2 = s2.height;
if (x1 + w1 > x2)
if (x1 < x2 + w2)
if (y1 + h1 > y2)
if (y1 < y2 + h2)
return true;
return false;
};
I also read that it might be possible to use the box2d engine to perform such a task, but I find this solution a little bit overwhelming.
I was looking for a simple as convenient way to do so.
In the end I came up with this solution, that I found on mdn and changed in order to fit my scenario.
var isColliding = function(el) {
el.children[0].position, el.children[1].position, el.position);
rect1 = {
x:el.position.x-(el.children[0].width/2),
y:el.position.y-(el.children[0].height/2),
w:el.children[0].width,
h:el.children[0].height
}
for(i=0; i<stage.children.length;i++)
{
if(stage.children[i] != el) {
el2 = stage.children[i]
rect2 = {
x:el2.position.x-(el2.children[0].width/2),
y:el2.position.y-(el2.children[0].height/2),
w:el2.children[0].width,
h:el2.children[0].height
}
if (rect1.x < rect2.x + rect2.w &&
rect1.x + rect1.w > rect2.x &&
rect1.y < rect2.y + rect2.h &&
rect1.h + rect1.y > rect2.y) {
return true;
}
}
}
return false;
OK, so I am making a Pacman game using HTML5 . the problem is whenever I hit one of the brick blocks I want the sprite to stop moving but it keeps going until it hits the left most brick object.
how do I fix this? please help... here is the code I'm using to make the sprite stop.
here is all my code, if you have time, please parse it, and tell me what I have done wrong.
function init(){
var canvas=document.getElementById("ctx");
var ctx = canvas.getContext("2d");
var player = {sx:6,sy:6,sw:15,sh:15,x:230,y:377,w:20,h:20}
var ss = new Image();
ss.src="SS.png";
var right=false,left= true,up = false,down = false
var b = [{x:0,y:0,w:25,h:((canvas.height/2)-25)},{x:0,y:((canvas.height/2)),w:25,h:((canvas.height/2))},{x:50,y:25,w:50,h:50},{x:125,y:25,w:75,h:50},{x:225,y:0,w:25,h:75},{x:275,y:25,w:75,h:50},{x:375,y:25,w:50,h:50},{x:50,y:100,w:50,h:25},{x:125,y:100,w:25,h:125},{x:125,y:150,w:75,h:25},{x:175,y:100,w:125,h:25},{x:225,y:125,w:25,h:50},{x:325,y:100,w:25,h:125},{x:275,y:150,w:75,h:25},{x:375,y:100,w:50,h:25},{x:25,y:150,w:75,h:75},{x:375,y:150,w:75,h:75},{x:375,y:250,w:75,h:75},{x:25,y:250,w:75,h:75},{x:125,y:250,w:25,h:75},{x:325,y:250,w:25,h:75},{x:175,y:300,w:125,h:25},{x:225,y:325,w:25,h:50},{x:50,y:350,w:50,h:25},{x:75,y:350,w:25,h:75},{x:125,y:350,w:75,h:25},{x:275,y:350,w:75,h:25},{x:375,y:350,w:50,h:25},{x:375,y:350,w:25,h:75},{x:25,y:400,w:25,h:25},{x:125,y:400,w:25,h:75},{x:50,y:450,w:150,h:25},{x:275,y:450,w:150,h:25},{x:325,y:400,w:25,h:50},{x:425,y:400,w:25,h:25},{x:175,y:400,w:125,h:25},{x:225,y:425,w:25,h:50},{x:450,y:0,w:50,h:((canvas.height/2)-25)},{x:450,y:(canvas.height/2),w:50,h:((canvas.height/2))}];
function gen(){
for(var i=0;i<b.length;i++){
ctx.fillStyle="blue"
ctx.fillRect(b[i].x,b[i].y,b[i].w,b[i].h)
}
ctx.drawImage(ss,player.sx,player.sy,player.sw,player.sh,player.x,player.y,player.w,player.h)
}
function move(){
for(var i=0;i<b.length;i++){
//((a.x + a.width) < b.x)
if(left &&
player.x > b[i].x && (player.x + player.w) < (b[i].x + b[i].w) &&
player.y > b[i].y && (player.y + player.h) < (b[i].y + b[i].h)) {
// here you can tell that the user is colliding an object
player.x-=1
}
else {
}
}
}
function animate(){
ctx.save()
ctx.clearRect(0,0,canvas.width,canvas.width);
gen()
move()
ctx.restore();
}
var ani = setInterval(animate, 30)
}
window.addEventListener("load",function(){
init()
})
First, I can see a problem with the first part of your if condition:
left = true && ...
Should be
left === true && ...
Or even better
left && ...
Now for the collision part it usually is top-left or in the middle of the object
I'd suggest this top-left origin collision check:
if(left &&
(player.x >= b[i].x && player.x + player.w <= b[i].x + b[i].w) &&
(player.y >= b[i].y && player.y + player.h <= b[i].y + b[i].h) {
// here you can tell that the user is colliding an object
}
It checks several cases, this part
(player.x >= b[i].x && player.x + player.w <= b[i].x + b[i].w)
Will meet requirements if the player's x (with its width component) inside the occupied x range of the current object
The second part
(player.y >= b[i].y && player.y + player.h <= b[i].y + b[i].h)
Will meet requirements if the player's y (with its height component) is inside the occupied y range of the current object.
It will only execute the if statement if the condition is satisfied for both of the above cases.
You can tell if you should reposition the player on the left or on the right, by substracting the players x component to the object's x component, same goes for top or bottom with y component. The previous sentence is only valid if you move in a grid cell by cell.
Essentially, what I'm doing is placing a bunch of random width/height rects onto a grid (near the center of it), then pushing them all away from each other until none of them overlap. I have another version where I check for collisions before I place them on the grid, but that's not what I'm going for in this build.
I'm wondering if someone can explain a better way to go about this?
What I've tried so far is something similar to:
let r1/r2 = rect1/rect2
do {
var ox = Math.max(0, Math.min(r1.x + r1.w, r2.x + r2.w) - Math.max(r1.x, r2.x)),
oy = Math.max(0, Math.min(r1.y + r1.h, r2.y + r2.h) - Math.max(r1.y, r2.y)),
dx = r2.x - r1.x,
dy = r2.y - r1.y;
if (ox > 0 && oy > 0) {
if (ox >= oy) {
if (r1.x >= r2.x && Math.random() > .1) {
r1.x += ox;
spaced = true;
continue;
} else {
r1.x -= ox;
spaced = true;
continue;
}
} else {
if (r1.y >= r2.y && Math.random() > .1) {
r1.y += oy;
spaced = true;
continue;
} else {
r1.y -= oy;
spaced = true;
continue;
}
}
}
} while ( /* stuff */ )
the random is only there because I will run into times when a certain rect gets pushed back and forth and never gets free and causes an infinite loop. This way is horribly inefficient however.
I believe what your trying to accomplish is known as a packing problem http://en.wikipedia.org/wiki/Packing_problem. If you just search stack overflow for "2d bin packing" you should be able to find all you need to roll a much more efficient algorithm.