6

大家
好,我开始用球和砖块编写一个小游戏,并且在碰撞检测方面遇到了一些问题。这是我的代码http://jsbin.com/ibufux/9。我知道检测通过数组起作用,但我不知道如何将它应用到我的代码中。

这是我尝试过的:

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

游戏中的每个积木都是由 for 循环生成的,然后进入 $bricks 数组。生成后的每个砖块都接收顶部和左侧位置并具有绝对位置。我试图检查 $ball.t (它是检测球顶位置的球对象的属性)是否到达砖块而不是移除砖块。

谢谢你的帮助。我才刚刚开始学习 JS,这就是我的代码很棘手的原因。

4

2 回答 2

5

首先说一下一些代码错误

  • $ball.t应该是$ball.top
  • 您不需要将其$作为前缀,对于您的代码,它只是一个变量,您调用$ball而不是ball女巫会导致假设错误

对于这些假设错误,这是您做错的事情:

  • $ball是 dom 元素,而不是 jQuery 元素
  • 一样$bricks
  • $ball是一个数组

从一些得出的结论console.log()让我们尝试修复代码:

应该被$ball调用,因为只有一个,它是数组元素,$ball[0]因为你有指向 DOM 元素而不是 jQuery 元素的变量,你需要将它包装在 Jquery 中:

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

不要混淆的一个好主意是仅$在 jQuery 元素中使用,在变量中添加前缀,不会使它们成为 jQuery 元素。

每次您看到诸如“元素 x 没有方法 y”之类的错误时,总是假定您正在从 DOM 元素调用方法,而不是从 jQuery 元素调用方法。

于 2013-07-14T13:05:23.033 回答
2

现在,@balexandre 已经很好地解释了您的代码的一些要点,让我们来看看我们如何计算碰撞。

想象2个范围相互重叠(范围a部分重叠范围b

[100     .|..      300]
          [200      ..|.      400]

重叠部分来自 | 到 | -> 200 到 300,所以重叠的大小是 100

如果您查看这些数字,您会注意到,可以看到重叠,

  • 取右边较小的数 -> 300
  • 取左边的最大数 -> 200
  • 将它们彼此相减 -> 300 - 200 = 100。

让我们看看另外两种情况。(范围b完全在范围a中)

[50    ...    150]
    [75...125]

所以我们的值是:Math.min (150,125) //125对于结束值和Math.max (50,75) // 75开始值,导致重叠的值为 125 - 75 = 50

我们来看最后一个例子(Range a not in Range b

[50    ...    150]
                      [200    ...    300]

使用上面的公式,得出的结果Math.min (150 , 300 ) - Math.max (50,200) // -50是绝对值是 2 个范围之间的差距,50

现在我们可以添加最后一个条件,因为您想计算碰撞,> 0我们只对值感兴趣。鉴于此,我们可以将其置于一种条件下。

Math.min ((Brick["Right"],Ball["Right"]) - Math.max (Brick["Left"], Ball["Left"]) > 0)

true如果元素重叠并且false不重叠,这将产生。

将此应用于您的代码,我们可以通过以下方式计算碰撞

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;
        }
    }
}

有了这个,我们可以在 Ball 与 Brick 碰撞时返回 true 到 move 函数。

但是,嘿,我们希望它朝着正确的方向反弹,所以我们将面临另一个问题。因此,与其返回 Brick 是否碰撞的布尔值,不如返回一个新的 Ball 应该移动的方向。

为了能够轻松地仅更改方向的 x 或 y 部分,我们应该使用向量之类的东西。

为此,我们可以使用 2 位整数,其中位b0用于 x 方向,位b1用于 y 方向。这样。

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

但是为了能够只改变一部分方向,我们需要将旧方向传递给碰撞函数,并分别使用按位&|来关闭或打开它们

我们还必须计算球从哪一侧碰撞。幸运的是,我们之前有重叠计算,它已经使用了我们需要的所有值来计算碰撞方向。

如果它来自

  • 正确的
    • Brick ["Right"] - Ball["Left"] 必须与重叠的值相同。
  • 剩下
    • Ball ["Right"] - Brick["Left"] 必须与重叠的值相同。

如果它们都不是真的,它必须要么来自

  • 底部
    • 如果 Ball["Top"] 大于 ( Brick["Top"] 加上一半的 Brick["height"] )

或者从顶部。

为了减少从侧面碰撞的条件评估为真的范围,我们可以添加另一个条件,即重叠必须小于例如... && overlap < 2

因此,如果它与边缘碰撞,它并不总是弹到一边。


说了这么多,在代码中这可能看起来像这样。

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;
}

为了让它工作,我们还应该改变moveXX函数,使用新的方向,返回。

但是如果我们无论如何要从碰撞函数中获得新的方向,我们可以将完整的碰撞检测移动到函数中,以简化我们的移动函数。但在此之前,我们应该看一下移动函数,并添加一个查找对象来field保存方向的数字,以保持可读性。

var field = {
  directions: {
    uR : 3, // 11
    dR : 1, // 01
    dL : 0, // 00
    uL : 2  // 10

  },
  directionsLkp: [
    "dL","dR","uL","uR"
  ],
...
}

现在移动功能看起来像这样,

    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);
        }
...
}

像这样,如果碰撞函数返回的方向与当前方向不同,则移动函数只是改变方向。

现在我们可以开始将墙壁碰撞移动到碰撞函数中,为此我们可以在开始时添加另一个检查。

    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

      ...
}

注意,我省略了平台的碰撞检查,因为这个想法应该很清楚=)

这是一个小提琴

于 2013-07-14T15:44:42.217 回答