现在,@balexandre已经很好地解释了一些关于您的代码的要点,让我们来看看如何计算碰撞。
想象一下两个重叠的范围(范围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
让我们看一下最后一个例子(范围
a不在范围
b中)。
[50 ... 150]
[200 ... 300]
使用上述公式,得到结果
Math.min(150, 300) - Math.max(50, 200) // -50
,其绝对值为两个范围之间的差距,即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];
brickBounds[1] = brickBounds[0] + 40
var ballBounds = [ball.l];
ballBounds[1] = ballBounds[0] + 20
if (ball.t <= (offset.top + 20) && (Math.min(brickBounds[1], ballBounds[1]) - Math.max(brickBounds[0], ballBounds[0])) > 0) {
$bricks[i].style.opacity = 0;
$bricks.splice(i, 1)
return true;
}
}
}
通过这个方法,当球碰到砖块时,我们可以在移动函数中返回true。
但是,我们希望它以正确的方向弹开,所以我们将面临另一个问题。因此,我们可以返回一个新的方向,让球应该移动,而不是返回一个布尔值,表示砖块是否发生碰撞。
为了能够轻松地仅更改方向的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
但是,如果只想改变方向的一部分,我们需要将旧方向传递给碰撞函数,并使用位运算符 & 和 | 分别关闭或打开它们。
另外,我们还需要计算球碰撞的哪一侧。幸运的是,我们之前已经进行了重叠计算,这已经使用了我们需要的所有值来计算碰撞方向。
如果来自右侧:
- 砖块 ["Right"] - 球 ["Left"] 的值必须与重叠相同。
如果来自左侧:
- 球 ["Right"] - 砖块 ["Left"] 的值必须与重叠相同。
如果以上都不是,则必须来自底部:
- 如果 Ball ["Top"] 大于(Brick ["Top"] 加上 Brick ["height"] 的一半),则为底部。
否则就是从顶部来的。
为了减少侧面碰撞条件的真实性范围,我们可以添加另一个条件,即重叠必须小于例如
... && overlap < 2
。
因此,如果它与边缘发生碰撞,它不会总是反弹到侧面。
足够说了,代码可能看起来像这样。
bricksCollision: function (direction) {
var newDirection = direction
var ballBounds = [ball.l];
ballBounds[1] = ballBounds[0] + 20
for (var i = 0; i < $bricks.length; i++) {
var $brick = $($bricks[i]);
var offset = $brick.offset();
var brickBounds = [offset.left - field.l];
brickBounds[1] = brickBounds[0] + 40
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;
$bricks.splice(i, 1)
if (ballBounds[1] - brickBounds[0] == overlap && overlap < 2) {
newDirection &= ~(1);
} else if (brickBounds[1] - ballBounds[0] == overlap && overlap < 2) {
newDirection |= 1;
} else {
if (ball.t > (offset.top + (20 / 2)))
newDirection &= ~(2)
else
newDirection |= 2;
}
return newDirection;
}
}
return direction;
}
要使其正常工作,我们还应更改
moveXX
函数,以使用返回的新方向。
但是,如果我们无论如何都要从碰撞函数中获取新方向,我们可以将完整的碰撞检测移动到该函数中,以简化我们的移动函数。但在此之前,我们应该查看移动函数,并添加一个查找对象到
field
中,该对象保存方向的数字,以保持可读性。
var field = {
directions: {
uR : 3,
dR : 1,
dL : 0,
uL : 2
},
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)
if (newDirection !== field.directions.uR) {
clearInterval(timer);
moves[newDirection]();
}
}, timeout);
}
...
}
就像这样,如果碰撞函数返回的方向与当前方向不同,移动函数就会简单地改变方向。
现在我们可以将墙壁碰撞移动到碰撞函数中,为此我们可以在开头添加另一个检查。
bricksCollision: function (direction) {
...
if (ball.t <= field.t)
newDirection &= ~(2)
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
...
}
请注意,我省略了平台的碰撞检查,因为这个想法应该很清楚 =)
这里是一个
演示。
$varName
命名约定。您可以将您的砖块数组称为bricks
而不是$bricks
。 - Benjamin Gruenbaum