Hi everyone
I started to write a little game with ball and bricks and have some problem with collision detection. Here is my code http://jsbin.com/ibufux/9 . I know that detection works though array, but I can't figure how I can apply it to my code.
Here is what i have tried:
bricksCollision: function() {
for (var i = 0; i < $bricks.length; i++) {
if ($ball.t == $bricks[i].offset().top) {
$bricks[i].splice(i, 1);
}
}
Every bricks in game are generated by for loop and then goes to $bricks array. Every brick after generating receive top and left position and have position absolute. I have tried to check if $ball.t (it's properties of my ball object which detects ball top position) reach the bricks and than remove bricks.
Thanks for any help. I'm only start to learn JS that's why my code is knotty.
First of all, let'stalk about some code errors
$ball.t
should be probably $ball.top
$
as a prefix, for your code, it's simply a variable and you are calling $ball
instead of ball
witch results in assumption errors!for those assumption errors here is what you are doing wrong:
$ball
is a dom element, not a jQuery element$bricks
$ball
is an arraywith those concluded from some console.log()
let's try to fix the code:
the $ball
should be called, as there's only one, by it's array element, as $ball[0]
and because you have variables pointing to DOM elements and not jQuery elements, you need to wrap it in Jquery as:
if ( $($ball[0]).top === $($bricks[i]).offset().top ) { ...
a good idea not to get confused is only use $
in jQuery elements, prefixing it in a variable, does not make them a jQuery Element.
And everytime you see that you have an error such as "element x has no method y" always assume that you're calling a method from a DOM element, and not a jQuery element.
Now, that @balexandre has nicely explained some points about your code, lets examine how we can compute the collision.
Imagine 2 Ranges overlapping each other (Range a partly overlaps Range b)
[100 .|.. 300]
[200 ..|. 400]
The part overlapping is from | to | -> 200 to 300, so the size of the overlap is 100
If you look at the numbers, you notice, that the overlap could be seen like,
Lets take a look at 2 other situations. (Range b completely in Range a)
[50 ... 150]
[75...125]
So the values we have are: Math.min (150,125) //125
for the end value and Math.max (50,75) // 75
for the start value, resulting in a value of 125 - 75 = 50 for the overlap
Let's take a look the last example (Range a not in Range b)
[50 ... 150]
[200 ... 300]
Using the above formula, yields the result Math.min (150 , 300 ) - Math.max (50,200) // -50
which absolutes' value is the gap between the 2 Ranges, 50
Now we can add a last condition, as you want to compute the collision, only values > 0
are of interest for us. Given this we can put it into one condition.
Math.min ((Brick["Right"],Ball["Right"]) - Math.max (Brick["Left"], Ball["Left"]) > 0)
Which will yield true
if the elements' overlap and false
if they don't.
Applying this to your code, we could compute the collision the following way
bricksCollision: function () {
for (var i = 0; i < $bricks.length; i++) {
var $brick = $($bricks[i]);
var offset = $brick.offset();
var brickBounds = [offset.left - field.l]; //brick left pos
brickBounds[1] = brickBounds[0] + 40 //bricks right pos -> left pos + .bricks.width;
var ballBounds = [ball.l]; //balls left pos
ballBounds[1] = ballBounds[0] + 20 //balls right pos -> left pos + #ball.width;
if (ball.t <= (offset.top + 20) && (Math.min(brickBounds[1], ballBounds[1]) - Math.max(brickBounds[0], ballBounds[0])) > 0) {
$bricks[i].style.opacity = 0; //Make the brick opaque so it is not visible anymore
$bricks.splice(i, 1) //remove the brick from the array -> splice on the array, not the element
return true;
}
}
}
With this we could return true to the move function, when the Ball collides with a Brick.
But hey, we want it to Bounce off in the right direction, so we will face another problem. So rather then returning a Boolean value whether the Brick collides or not, we could return a new direction in which the Ball will should move.
To be able to easily change only the x or the y part of the direction, we should use something like a vector.
To do so, we could use 2 Bits of an Integer, where the bit b0 stays for the x direction and the bit b1 for the y direction. Such that.
Dec Bin Direction
0 -> 00 -> Down Left
^ -> Left
^ -> Down
1 -> 01 -> Down Right
^ -> Right
^ -> Down
2 -> 10 -> Up Left
^ -> Left
^ -> Up
3 -> 11 -> Up Right
^ -> Right
^ -> Up
But to be able to change only a part of the direction, we need to pass the old direction to the collision function, and use bitwise &
and |
respectively to turn them off or on
Also we have to compute from which side the ball collides. Fortunatly we have overlap calculation from before, which already uses all values we need, to compute the direction of collision.
If it comes frome the
If none of them are true, it has to either come from the
or else from the top.
To reduce the range where the condition, for the collision from the side, evaluates to true we can add another condition that the overlap has to be less than e.g ... && overlap < 2
So if it collides with the edge it doesn't always bounce of to the side.
So enough the talking, in code this could look like something like this.
bricksCollision: function (direction) {
var newDirection = direction
var ballBounds = [ball.l]; //balls left pos
ballBounds[1] = ballBounds[0] + 20 //balls right pos -> left pos + #ball.width;
for (var i = 0; i < $bricks.length; i++) {
var $brick = $($bricks[i]);
var offset = $brick.offset();
var brickBounds = [offset.left - field.l]; //brick left pos
brickBounds[1] = brickBounds[0] + 40 //bricks right pos -> left pos + .bricks.width;
var overlap = Math.min(brickBounds[1], ballBounds[1]) - Math.max(brickBounds[0], ballBounds[0]);
if (ball.t <= ((offset.top - field.t) + 20) && overlap > 0) {
$bricks[i].style.opacity = 0; //Make the brick opaque so it is not visible anymore
$bricks.splice(i, 1) //remove the brick from the array -> splice on the array, not the element
if (ballBounds[1] - brickBounds[0] == overlap && overlap < 2) { //ball comes from the left side
newDirection &= ~(1); //Turn the right bit off -> set x direction to left
} else if (brickBounds[1] - ballBounds[0] == overlap && overlap < 2) { //ball comes from the right side
newDirection |= 1; // Turn the right bit on -> set x direction to right;
} else {
if (ball.t > (offset.top + (20 / 2))) //Ball comes from downwards
newDirection &= ~(2) // Turn the left bit off -> set y direction to down;
else //Ball comes from upwards
newDirection |= 2; // Turn the left bit on -> set y direction to up;
}
//console.log("Coming from: %s Going to: %s", field.directionsLkp[direction], field.directionsLkp[newDirection], direction)
return newDirection;
}
}
return direction;
}
To get that to work, we should also change the moveXX
functions, to use the new direction, returned.
But if we are going to get the new direction from the collision function anyway, we could move the complete collision detection to the function, to simplify our move functions.
But before that, we should have a look at the move functions and, add a lookup object to field
which holds the numbers for the direction, to maintain readability.
var field = {
directions: {
uR : 3, // 11
dR : 1, // 01
dL : 0, // 00
uL : 2 // 10
},
directionsLkp: [
"dL","dR","uL","uR"
],
...
}
Now the move functions could then look like this,
ballCondact: function () {
var moves = [moveDl,moveDr,moveUl,moveUr]
var timeout = 5;
function moveUr() {
var timer = setInterval(function () {
$ball.css({
top: (ball.t--) + "px",
left: (ball.l++) + "px"
})
var newDirection = game.bricksCollision(field.directions.uR) //get the new direction from the collision function
if (newDirection !== field.directions.uR) {
clearInterval(timer);
moves[newDirection](); //move in the new direction
}
}, timeout);
}
...
}
Like this, the move function simply changes the direction if the collision function returns a direction which differs from the current one.
Now we can start moving the wall collisions to the collision function, to do this we could add another check at the beginning.
bricksCollision: function (direction) {
...
if (ball.t <= field.t)
newDirection &= ~(2); //Ball is at top, move down
else if (ball.l <= 0) //Ball is at the left, move right
newDirection |= 1;
else if (ball.t >= field.b - ball.height) //Ball is at the bottom, move up
newDirection |= 2;
else if (ball.l > field.width - ball.width) //Ball is at the right, move left
newDirection &= ~(1);
if (direction !== newDirection)
return newDirection
...
}
Note, i left out the collision check for the platform, as the idea should be clear =)
Here is a Fiddle
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With