I am writing an Arkanoid clone in javaScript and I have a problem with ball/brick collisions. This is how I do rendering (this might be important, I don't know):
function Game() {
//...
this.fps = 60;
//...
}
Game.prototype.gameloop = function() {
var current = new Date().getTime(),
delta = current - this.lastRender;
if (delta >= this.delay && !this.paused) {
this.updateAll();
if (this.paused)
return;
this.drawAll();
this.lastRender = new Date().getTime();
}
this.loop = requestAnimationFrame(Game.prototype.gameloop.bind(this));
};
And I start this loop in Game constructor by simply calling this.gameloop();
.
My algorithm of detecting collision which I perform in every iteration of gameloop:
- I calculate level of utmost lower row of bricks (brick.height*rows);
and every time I move the ball ( I do it simply by
ball.x +=ball.xVelocity; ball.y+=ball.yVelocity;
btw), I check if ball's 'y' coordinate is lesser then this bricksLevel. -> If check in step 1 returned 'true' I look for closest bricks:
var rowTop = Math.floor(ball.y / height), rowBot = Math.floor(ball.bottom / height), colLeft = Math.floor(ball.x / width), colRight = Math.floor(ball.right / width);
I get these bricks if they exist from the BricksCollection Object :
var suspects = bricks.slice({ rows: [rowTop, rowBot], cols: [colLeft, colRight] }); and for each of them I apply my ball/brick collision detection algorithm until it returns true (which means collision happened):
var i = 0; while ( suspects[i] && !this.detectBrickCollision(ball, suspects[i]) && i < suspects.length) { i++; }
Inside detectBrickCollision lives the code for actually determining if collision occurred or not and if it did with wich side of block. At first I came up with rather more logical than mathematical way. In four consecutive
if
blocks I checked each side of rectangle for collision. Like that:if (ball.yVelocity < 0 && !brick.below && ball.y <= brick.bottom && ball.center.x <= brick.right && ball.center.x >= brick.x) { ball.yVelocity = -ball.yVelocity; ball.y = brick.bottom; brick.collide(ball); return true; } if (//similar checks for other sides) //...
And that's how it worked: http://jsfiddle.net/78NJ6/ Note that ball collides perfectly fine with bottom side of bricks and reasonably fine with left and right sides, but worse with top side, although the logic for all sides are pretty much the same! For example here is code for top side collision check:
if (ball.yVelocity > 0 && !brick.above && !brick.isUpper() && ball.bottom >= brick.y && (ball.x <= brick.right || ball.right >= brick.x)) { ball.yVelocity = -ball.yVelocity; ball.y = brick.y - ball.height; brick.collide(ball); return true; }
Now if you tried out the fiddle from above you may have noticed that in case of top collisions the ball goes far below of the upper edge of brick. Sometimes it even goes as fa r as bottom edge. Obviously, it isn't good enough. And I looked for other ways to detect collisions and eventually I came across this solution: https://stackoverflow.com/a/1879223/3149635
I replaced the code inside detectBrickCollision function with following:
var intersects = this.intersects(ball, brick);
if (intersects) {
switch (intersects) {
case "top":
ball.y = brick.y-ball.height;
ball.yVelocity = - ball.yVelocity;
break;
case 'bottom':
ball.y=brick.bottom;
ball.yVelocity = -ball.yVelocity;
break;
case 'left':
ball.x = brick.x-ball.width;
ball.xVelocity = - ball.xVelocity;
break;
case 'right':
ball.x = brick.right;
ball.xVelocity = -ball.xVelocity;
break;
}
brick.collide(ball);
return true;
}
return false;
};
All the math I placed in function intersects. I'll show it here for completenes, but it's basically what I've seen in this answer above:
CollisionResolver.prototype.intersects = function(ball, brick) {
//find closest point on the rectangle's perimeter
var closestX = this.clamp(ball.center.x, brick.x, brick.right),
closestY = this.clamp(ball.center.y, brick.y, brick.bottom);
//calculate the distance between this closest point and circle's center
var dx = ball.center.x - closestX,
dy = ball.center.y - closestY;
//if the distance is less than circle's radius, an intersection occurs
var d = Math.sqrt((dx * dx) + (dy * dy));
if (d < ball.radius) {
//determin which side of rectangle collided
var side;
if (closestX === brick.x)
side = "left";
else if (closestX === brick.right)
side = 'right';
else if (closestY === brick.y)
side = 'top';
else if (closestY === brick.bottom)
side = 'bottom';
return side;
}
return false;
};
And despite this was supposed to be more precise and neat, the result wasn't so great. Now the ball still reflects realistically from bottom and somehow normal from right/left. The top collisions become even worse. Sometimes it just flies through brick if it comes from above. The new version: http://jsfiddle.net/X4MuW/
I am desperate. I can't get it - why both methods handle bottom, right, left collisions easily but SUCK when it comes to top edge of the brick? What's so special about upper side? Please, give me some tips, what it might be. I'm dying here.