22 记忆要绘制的形状
在创建动画循环时,需要克服的一个主要问题是:准确记忆要绘制的对象的内容及位置。我们采用的方法是:在循环外部设置一个变量来保存需要绘制的对象的位置值,并通过fillRect调用和在循环内部使用位置变量,就可以获取需要绘制的对象的内容及位置信息。但是,如果需要绘制多个动画形状该怎么办呢?甚至在创建循环时你都不知道需要创建多少种动画形状又该怎么办?因此,我们需要采用一种更好的方法。
1.错误的方法
你也许试图在动画循环外部使用独立的变量来存储每种形状的位置值。为什么不可以呢?既然这种方法适用于一种形状,那么为什么不适用于多个形状呢?你试图采用的方法是正确的,这样做可以达到目的,但这种方法非常笨拙,需要复制大量代码,并且以后修改这些形状也会非常复杂。
以下代码展示了如何通过修改上一节的代码来使多个形状产生动画效果。
var firstX = 0;
var secondX = 50;
var thirdX = 100;
function animate() {
firstX++;
secondX++;
thirdX++;
context.clearRect(0, 0, canvasWidth, canvasHeight);
context.fillRect(firstX, 50, 10, 10);
context.fillRect(secondX, 100, 10, 10);
context.fillRect(thirdX, 150, 10, 10);
if (playAnimation) {
setTimeout(animate, 33);
};
};
animate();
这里特意把这段代码单独列出来,而忽略了其他代码(例如,按钮事件),因为目前我们只需关注这部分内容。
这个例子和前面例子的唯一不同之处在于代码中使用了3次位置变量,并3次调用了fillRect方法。但是,没有绝对的理由能够说明这段代码不起作用,代码的运行结果如图1所示。
这种方法在此处也许很管用,但是,如果需要绘制上百个形状该怎么办呢?你会编写上百个位置变量并上百次调用fillRect方法吗?当然不会!因为代码能帮你自动地让对象产生动画效果,这才是使用代码创建动画的真正原因。
2.正确的方法
既然使用上面的方法创建多个变量既冗长又复杂,那我们该如何使其简化呢?简而言之,可以考虑使用对象和数组来实现。实际上,需要解决的问题有两个:第一,不管形状的数量有多少,首先考虑如何存储每个形状的位置值;第二,在不复制代码的情况下如何绘制每个形状。
第一个问题的解决方法非常简单,因此我们将直接介绍第二个问题的解决方法。你已经知道了每个形状所需的位置数据,即x的值。但是,我们还需要进一步深入研究,考虑同时使用y的值。如果知道每个形状具有两个相同类型的位置值,就可以通过创建JavaScript类来创建形状对象,代码如下所示:
var Shape = function(x, y) { this.x = x; this.y = y; };
对象这个概念非常重要,你必须掌握它,本书的章节也经常需要使用它。简言之,通过使用对象的属性(例如,火箭上的机翼数目)和方法(例如,启动火箭的发动机),可以为某些对象定义模板。
但是,仅仅定义对象还不够,还需要通过某种方式来存储对象,这样就不必手动引用上百种形状了。为此,可以在数组中存储形状对象,这样能够把它们按顺序存储在同一个变量中:
var shapes = new Array(); shapes.push(new Shape(50, 50)); shapes.push(new Shape(100, 100)); shapes.push(new Shape(150, 150));
向数组中添加形状对象时,可以使用Array对象的push方法。这听起来也许很复杂,其实该方法就是在数组的末尾添加对象。在本例中,该对象是一个形状对象。其功能与以下代码完全相同:
var shapes = new Array(); shapes[0] = new Shape(50, 50); shapes[1] = new Shape(100, 100); shapes[2] = new Shape(150, 150);
使用push方法的好处是,无须知道数组中最后一个元素的序号,该方法将会自动在数组的末端添加对象。
现在就得到了一组形状,每个形状都有不同的x和y值,这些值存储在一个数组中(这个数组已经被赋给shapes变量)。接下来的任务是如何将这些形状从数组中取出来并更新它们的位置(使它们产生动画效果),然后绘制这些形状。为此,需要在动画循环内部设置一个for循环:
function animate() { context.clearRect(0, 0, canvasWidth, canvasHeight); var shapesLength = shapes.length; for (var i = 0; i < shapesLength; i++) { var tmpShape = shapes[i]; tmpShape.x++; context.fillRect(tmpShape.x, tmpShape.y, 10, 10); }; if (playAnimation) { setTimeout(animate, 33); }; };
for循环遍历了数组中的每个形状,并将形状赋给了tmpShape变量,因此很容易访问它。现在你已经有了对数组中当前形状的引用,下面只需更新形状的x属性,然后使用x和y属性在当前位置绘制形状就可以了。
以下是完整的代码(如图2所示):
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) { this.x = x; this.y = y; }; var shapes = new Array(); shapes.push(new Shape(50, 50, 10, 10)); shapes.push(new Shape(100, 100, 10, 10)); shapes.push(new Shape(150, 150, 10, 10)); function animate() { context.clearRect(0, 0, canvasWidth, canvasHeight); var shapesLength = shapes.length; for (var i = 0; i < shapesLength; i++) { var tmpShape = shapes[i]; tmpShape.x++; context.fillRect(tmpShape.x, tmpShape.y, 10, 10); }; if (playAnimation) { setTimeout(animate, 33); }; }; animate();
3.随机产生形状
现在已经可以采用一种快速而简单的方法来创建形状了,接下来就是如何产生随机形状。因为使用了对象,所以解决这个问题非常简单。首先,需要更改Shape类来定义形状的宽度和高度:
var Shape = function(x, y, width, height) { this.x = x; this.y = y; this.width = width; this.height = height; };
然后,为每个形状随机选取起始位置和大小,并使用以下代码替换shapes.push语句:
for (var i = 0; i < 10; i++) { var x = Math.random()*250; var y = Math.random()*250; var width = height = Math.random()*50; shapes.push(new Shape(x, y, width, height)); };
width和heighr变量的赋值语句是一个双赋值语句,看上去也许有些奇怪,但它可以为这两个变量赋相同的值。另外,还需要更改对fillRect方法的调用,以便采用新的宽度和高度:
context.fillRect(tmpShape.x, tmpShape.y, tmpShape.width, tmpShape.height);
就这么简单。使用类和对象的优点在于,无须编辑太多的代码来添加或删除新特性。如果需要通过新示例来验证该特性,可以选择10个不同大小和位置的形状,并让它们在浏览器上移动(如图3所示)。
文件下载(已下载 2211 次)发布时间:2013/2/19 下午1:49:39 阅读次数:7005