Array collision detection

Hello to all! I started writing a little ball and brick game and had some problems with collision detection. Here is my code http://jsbin.com/ibufux/9 . I know that detection works, although an array, but I cannot figure out how I can apply it to my code.

Here is what I tried:

bricksCollision: function() { for (var i = 0; i < $bricks.length; i++) { if ($ball.t == $bricks[i].offset().top) { $bricks[i].splice(i, 1); } } 

Each brick in the game is generated by a for loop, and then goes into the $ bricks array. Each brick after creation receives an upper and left position and has an absolute position. I tried to check if $ ball.t (the properties of the object of my ball, which determines the position of the ball) reaches the brick and removes the bricks.

Thanks for any help. I'm just starting to learn JS, why my code is knotty.

+6
source share
2 answers

First of all, let's skip some code errors

  • $ball.t should probably be $ball.top
  • you don’t need to have $ as a prefix, for your code it is just a variable, and you call $ball instead of ball witch results in error assumptions !

for these assumption errors here is what you are doing wrong:

  • $ball is a dom element, not a jQuery element
  • same as $bricks
  • $ball is an array

with the conclusions made from some console.log() , try fixing the code:

$ball should be called as soon as one using this array element, like $ball[0] , and since you have variables pointing to DOM elements and not jQuery elements, you need to wrap this in jQuery like:

 if ( $($ball[0]).top === $($bricks[i]).offset().top ) { ... 

A good idea not to get confused is to use only $ in jQuery elements, the prefix in the variable does not make them a jQuery element.

And every time you see that you have an error such as β€œthe element x does not have a method y”, always assume that you are calling the method from the DOM element, not the jQuery element.

+5
source

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]; //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 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

+2
source

Source: https://habr.com/ru/post/949403/


All Articles