Now that @balexandre has well explained some points in your code, letβs look at how we can calculate the collision.
Imagine 2 Ranges overlapping each other (range partially overlaps range b)
[100 .|.. 300] [200 ..|. 400]
Overlapping parts comes from | to | β 200 to 300, so the overlap size is 100
If you look at the numbers, you will notice that the overlap can be seen as
- Take less right side β 300
- Take the left side welcome number β 200
- Subtract them from each other β 300 - 200 = 100.
Let's look at 2 more situations. (Range b is completely in range a)
[50 ... 150] [75...125]
Thus, we have the following values: Math.min (150,125) //125 for the final value and Math.max (50,75) // 75 for the initial value, which leads to a value of 125 - 75 = 50 for overlapping
Let's look at the last example (Range a not in Range b)
[50 ... 150] [200 ... 300]
Using the above formula, we get the result Math.min (150 , 300 ) - Math.max (50,200) // -50 , which absolutes the value - this is the gap between the two ranges, 50
Now we can add the last condition, since you want to calculate the collision, only values > 0 are of interest to us. Given this, we can bring it to one condition.
Math.min ((Brick["Right"],Ball["Right"]) - Math.max (Brick["Left"], Ball["Left"]) > 0)
Which will give true if the elements overlap and false if they do not.
Applying this to your code, we could calculate the collision as follows.
bricksCollision: function () { for (var i = 0; i < $bricks.length; i++) { var $brick = $($bricks[i]); var offset = $brick.offset(); var brickBounds = [offset.left - field.l];
With this, we can return true displacement functions when the ball collides with a brick.
But hey, we want him to bounce in the right direction, so we will run into another problem. Thus, instead of returning a logical value, colliding with a brick or not, we could return a new direction in which the ball should move.
To be able to easily change only the x or y part of the direction, we should use something like a vector.
For this, we could use 2 bits of an integer, where bit b0 remains for the x direction and bit b1 for the y direction. In this way.
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 in order to be able to change only part of the direction, we need to transfer the old direction to the collision function and use the bitwise & and | respectively to disable them or on
We also need to calculate which side the ball is facing. We have a calculation of the overlap from earlier, which already uses all the values ββwe need to calculate the direction of the collision.
If it comes from
- right
- Brick ["Right"] - Ball ["Left"] must be the same value as the overlap.
- left
- Ball ["Right"] - Brick ["Left"] must be the same value as the overlap.
If none of them is true, it must either come from
- Bottom
- if Ball ["Top"] is larger (Brick ["Top"] plus half Brick ["height"])
or on top.
To reduce the range in which the condition for collision from the side is evaluated as true, we can add another condition that the overlap must be less than ... && overlap < 2
Therefore, if it collides with an edge, it does not always bounce to the side.
Suffice it to say, in the code it might look 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 make this work, we also need to modify the moveXX functions to use the new direction returned.
But if we are still going to get a new direction from the collision function, we could transfer the full collision detection to a function to simplify our moving functions. But before that, we need to take a look at the move functions and add a search object in the field that contains numbers for the direction in order 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 may 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); } ... }
Similarly, the move function simply changes direction if the collision function returns a direction other than the current one.
Now we can start moving collisions into collisions with the collision function, to do this, we could add one more 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 ... }
Please note that I left a collision check for the platform, as the idea should be clear =)
Here is the fiddle