12.24 HTML Canvas动画 - 碰撞反弹

使用Canvas实现碰撞检测和反弹动画

到目前为止我们使用的示例其实都没有边界。也就是说,当形状移动到画布的边界处时,什么都没发生,它们只是消失在我们的视野中,再也看不见了。 这也许是你需要的效果。例如,如果你只是创建一段简短的动画,并且动画在到达边界之前就会停止,或者你希望形状移动到画布之外。 但是,如果你不需要这种行为怎么办?如果你希望形状能够感知周围的环境,或者在边界处反弹回来怎么办呢?这种行为可以避免机械性的动画,使动画更加自然和随机。

在学习如何实现这种行为之前,先用本章讨论的技术编写如下代码:

 
var canvasWidth = canvas.width(); 
var canvasHeight = canvas.height(); 

var playAnimation = true; 

var startButton = $("#startAnimation"); 
var stopButton = $("#stopAnimation"); 

startButton.hide(); 
startButton.click(function() { 
$(this).hide(); 
stopButton.show(); 

playAnimation = true; 
animate(); 
}); 

stopButton.click(function() { 
$(this).hide(); 
startButton.show(); 

playAnimation = false; 
}); 

var Shape = function(x, y, width, height) { 
this.x = x; 
this.y = y; 
this.width = width; 
this.height = height; 
}; 

var shapes = new Array(); 

for (var i = 0; i < 10; i++) {         var x = Math.random()*250; 
var y = Math.random()*250; 
var width = height = Math.random()*30; 
shapes.push(new Shape(x, y, width, height)); 
}; 

function animate() { 
context.clearRect(0, 0, canvasWidth, canvasHeight); 

var shapesLength = shapes.length; 
for (var i = 0; i < shapesLength; i++) { 
var tmpShape = shapes[i]; 
context.fillRect(tmpShape.x, tmpShape.y, tmpShape.width, 
tmpShape.height); 
}; 

if (playAnimation) { 
setTimeout(animate, 33); 
}; 
}; 

animate(); 

这些代码建立了一个完整的动画循环,该循环将遍历10个随机生成的形状。代码并没有在视觉上移动任何形状,因为我们没有修改动画循环中形状的属性(如,增加x的值将形状向右移动)。

使形状感知画布边界的过程其实非常简单。假设一个形状在每个循环中向右移动1像素。一旦该形状移动到画布的右边界处(假设是500像素),它将会继续移动,并且x值仍在增加,但我们就无法在画布上看到它了。其实你希望该形状发生的行为是:形状在画布的右边界处反弹回来,就好像边界处有一堵墙一样。为此,你需要检查形状是否超过了画布的右边界,如果已经到达边界处,则反向改变形状运动的方向,这样它就会反弹回来。

图1描述了这种效果的基本概念,运动的球上的数字代表每个动画循环。左边的图显示了没有边界时形状的正常反应,右边的图显示了存在边界时形状会如何反应。

图1
形状从边界弹回的示意图

计算一个形状是否超过画布的右边界其实就是检查形状的x位置是否超过了画布的宽度。如果形状的x位置大于画布的宽度,那么形状必然会超出右边界。同样,检查形状是否超过画布的左边界也可以采用这种方法。其中,形状的左边界的位置对应的x值为0。检查形状的x位置是否小于0,就可以确定形状是否位于画布的左边界之外。当然,也可以使用同样的方法检查形状是否位于画布的上边界和下边界。具体做法是,检查y值是否小于上边界0,并检查y值是否大于画布的高度(下边界)。

综合运用这些方法,可以创建一组简单的逻辑:让形状在画布的边界处弹回。第一步是向Shape类中添加一些新属性,它们将用于定义形状是否碰到边界及反弹的路径方向:

this.reverseX=false; 
this.reverseY=false; 

默认情况下,这些属性的值为false,在本示例中,这表明形状将一直向右下方运动。下一步是添加逻辑关系来检查形状是否超出了画布边界。在动画循环的fillRect调用下面插入以下代码:

 
if (tmpShape.x < 0) { 
tmpShape.reverseX = false; 
} else if (tmpShape.x + tmpShape.width > canvasWidth) { 
tmpShape.reverseX = true; 
}; 

if (tmpShape.y < 0) { 
tmpShape.reverseY = false; 
} else if (tmpShape.y + tmpShape.height > canvasHeight) { 
tmpShape.reverseY = true; 
}; 

当形状即将到达边界之外时,这些检查将反向改变形状的运动路线。但是,设置布尔值并不能实际改变形状的具体运动方向,因此,需要另外进行一些检查。此时,需要将它们放在fillRect调用的上面:

 
if (!tmpShape.reverseX) { 
tmpShape.x += 2; 
} else { 
tmpShape.x -= 2; 
}; 

if (!tmpShape.reverseY) { 
tmpShape.y += 2; 
} else { 
tmpShape.y -= 2; 
}; 

这将会产生一些神奇的效果。如果形状在x轴上没有反转,那么这些检查将会使形状向右移动(通过增加x位置)。如果形状在x轴上反转,那么这些检查将会使形状向左移动(通过减少x位置)。同样,在y轴上也可以执行相同的检查。

由于有了这些相对简单的逻辑检查,你可以使一组形状移动到画布的边界时反弹回来(如图2所示)。甚至可以更改Shape类表示反转方向的属性的默认值,从而改变形状的运动方式。

Start
Stop
一组在边界处弹回的形状