到目前为止,我们已经学会了如何在影片中绘制图形,并且通过施加外力使影片运动起
来。然而,在这些例子中也许会遇到这样的烦恼:物体移动到屏幕外后就不到了。如果在某
个角度上运动得过快,那么就没有办法再让物体退回来,只能选择重新运行影片。
要用环境边界作为一道屏障,保证物体能够在一个可见的范围内运动。
在空间中穿梭,并保持以同样的方向及速度运动,只有对其施加外力,才会使它的运动发生
改变。改变物体速度向量的力,可能是摩擦力的一种——甚至可以是空气的阻力。目前,我
们已经能够模拟真空环境下的物体运动了,但是大家一定还想模拟真实环境下的物体运动。
那么本章就要解决前面这两个问题。首先,学习边界环境下物体的运动,然后学习如何模拟
阻力,Let's go。
环境边界
这里边界是指为这项活动保留的活动空间。意思是“我只关心发生在这个范围内的事情,如
果超出了这个范围,就不再关注它了。”
关注的对象中移除,另一种选择是跟随它。只要物体是运动的,那么它就有机会离开这个范
围。当物体离开后,我们可以选择忘记它,或将它移动回来,或跟随它。我们将介绍前两种
方法,不过先要确定边界的位置,再学习如何定义边界。
设置边界
舞台由一个名为 stage 的属性表示,它是每个显示对象的一部分。所以在文档类(继承自
MovieClip 或 Sprite)中,我们可以直接使用这个属性访问舞台及舞台的相关属性。
播放器窗口改变大小后希望舞台的尺寸与播放器尺寸相匹配的话,就应该设置这两个属性:
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
请注意,还需要导入 flash.display.StageAlign 和 flash.display.StageScaleMode 这两
个类。这样一来,影片的左上边界将为零,而右下边界将为 stage.stageWidth 和
stage.stageHeight。可将它们保存为变量,如下:
private var left:Number = 0;
private var top:Number = 0;
private var right:Number = stage.stageWidth;
private var bottom:Number = stage.stageHeight;
如果要使用一个固定的区域作为边界的话,这样做是非常合适的。
然而,如果使用整个舞台区域作边界的话,那么即使舞台大小发生了改变,只要在代码中直
接调用 stage.stageWidth 和 stage.stageHeight 就可以了。
bottom = 300, left = 50, right = 400。OK,边界已经有了,用它们能做什么呢?判断所
有移动的对象,看它们是否仍在这个空间内,这里可以使用 if 语句,简化的样式如下:
if(ball.x > stage.stageWidth) {
} else if(ball.x < 0) {
}
if(ball.y > stage.stageHeight) {
} else if(ball.y < 0) {
}
边界。但不可能同时超出左边界,所以不需要再用一条 if 语句进行判断。因此,只需要在
第一个 if 语句失败后再判断左边边界即可, 顶部和底部也是如此。然而,物体有可能在 x,y
轴上同时超出边界,所以要把这两个判断语句分开。如果物体出界后,应该对它们执行什么
样的操作呢?答案有四种:
■ 将对象移除;
■ 重置到舞台上,像生成一个新对象一样(重置对象);
■ 重置到舞台上,将同一个对象放置在不同位置;
■ 将其反弹回去。
我们从最简单的移除对象开始。
移除对象
将会由新的对象所取代,这样舞台就永远不为空。但也不能生成太多的可移动对象,因为这
样会使 Flash Player 变慢。
意,被移除的显示对象仍然存在,只是看不到而已。如果要将该对象彻底删除,还应该调用
delete 对象名 将其完全删除。
那么要停止整个程序的执行只需调用 removeEventListener(Event.ENTER_FRAME,
onEnterFrame);就可以了。另一方面,如果运动的对象很多,要通过持续执行代码使每个对
象都动起来(除了被删除的对象外),就应该在数组中保存所有对象的引用,然后循环这个
数组使里面的每个对象都动起来。
随后,当删除了其中的某一个对象后,使用 Array.splice 方法同时将该对象的引用在数组
中删除,后面会有代码。
所以,如果想要删除影片,就要知道边界在哪儿,使用 if 语句来完成:
if(ball.x > stage.stageWidth ||
ball.x < 0 ||
ball.y > stage.stageHeight ||
ball.y < 0)
{
removeChild(ball);
}
或下边界,就将它删除。”。
这里有一个小问题,也许大家现在还没有意识到,小球的位置由中心的注册点的位置决定,而注册点超出了屏幕的右边界,小球将被删除。如果小球的运动足够快,也许看上去问题不大。但如果运动得非常缓慢,每帧
运动一像素的话, 那会是怎样?我们会看着它走向屏幕的边界,但还差一半没有走完就被移
除了!这就像一个演员只离开了舞台的一半就把戏服脱掉了,破坏了塑造人物的形象。
实现这个计划,需要考虑到物体的宽度。因为注册点在中心,所以,可以将宽度的一半保存
为 radius 属性。代码如下:
if(ball.x - ball.radius > stage.stageWidth ||
ball.x + ball.radius < 0 ||
ball.y - ball.radius > stage.stageHeight ||
ball.y + ball.radius < 0)
{
removeChild(ball);
}
来说,都是适用的。
为该类加入公共属性 vx 和 vy ,让每个小球都有自己的速度向量。全部代码如下:
package {
}
用 getter/setter 方法进行操作。但是为了方便起见,就不再遵循这个规则了,仅使用公
共属性作替代。下面一个文档类,Removal.as,设置了许多小球,并在它们离开舞台后进行
删除:
package {
}
出随机的 x,y 速度向量,并将它们加入显示列表,然后 push 到数组中。
意,
删除。Array.splice 有两个参数:开始删除元素的索引,删除元素的个数。在这个例子中,
只删除一个元素即当前索引处的元素。
大家也许注意到了本例中的 for 语句,与其它例子中的不太一样:
for(var i:Number = balls.length - 1; i > 0; i--)
索引就会改变。
enterFrame 的侦听器。
重置对象
当一个对象离开了舞台后,它就没有作用了,不过,可以将其重置到舞台上,让它作为一个
新对象再加入进来。永远不要担心对象的数量过多,因为这个数量是固定不变的。这个技术
用于制作喷泉效果非常合适:一串粒子不停地喷射,超出舞台的粒子重新加入到水流中。
2 像素的大小并给它一个随机的颜色。 水源在舞台底部的中心位置,所有的粒子都从这里发
出,当它们超出舞台边界后,将会被重置回来。所有粒子都以一个随机的负 y 速度和一个
随机的 x 速度作为开始。这样就会上喷射,并伴有轻微的左右移动。当粒子重置后,它们
的速度向量也将被重置,同时粒子也要受到重力的牵引。文档类 Fountain.as:
package {
private var gravity:Number = 0.5;
private var balls:Array;
public function Fountain() {
}
private function init():void {
}
private function onEnterFrame(event:Event):void {
}
屏幕环绕
左边界,就让它在屏幕右边出现;在右边出界,则将它置到左边;上面出界就回到下面。明
白了吧。这个思想与重置对象的概念非常相似,只是位置有所不同。
舞台,就很难将它找回。如果使用屏幕环绕技术,那么影片超出屏幕边界的距离不会大于一
像素。
体标出:
package {
}
private function onKeyDown(event:KeyboardEvent):void {
}
private function onKeyUp(event:KeyboardEvent):void {
}
用白色线条绘制的,所以不要忘记将背景色改为黑色。大家可以看到,在新的类中加入了边
界的定义及判断。
反弹
担心。当检测到物体超出舞台后,开始应用弹性,不改变改变物体的位置,只改变它的速度
向量。方法很简单:如果物体超出了左、右边界,只需要使它的 x 速度向量取反。如果超
出了上、下边界,只需要让 y 速度向量取反。坐标轴取反非常简单,只需要乘以 -1 。如
果速度向量等于 5,则变成-5。如果是-13,则变成 13。代码也非常简单:vx *= -1 或 vy *=
-1。
希望出现半张图片的效果。比如,往墙上扔一个球,不希望球的一半进入墙体后再反弹回来。
因此,首先要判断出小球首次超出边界的瞬间。然后,将小球的运动路径取反,再加上小球
宽度/高度的一半。比如:
if(ball.x – ball.radius > right) . . .
就要变为
if(ball.x + ball.radius > right) . . .
体重新定位到边界处,这就形成了一个非常明显的撞击反弹的效果。如果不调整物体的位置,
到下一帧,在物体移动之前,也许仍然处在边界外。如果这样的话,物体的速度向量又将取
反,则向墙内运动!就会产生物体进出墙体的情形,然后在这附近振荡。
x 轴的全部 if 语句如下:
if(ball.x + ball.radius > right) {
}
else if(ball.x – ball.radius < left) {
}
反弹的步骤如下:
■ 判断物体是否超出了边界;
■ 如果是,将其置到边界处;
■ 然后将它的速度向量取反。
(Bouncing.as):
package {
import flash.display.StageScaleMode;
import flash.events.Event;
public class Bouncing extends Sprite {
}
这是一个数学计算与现实情况不完全一致的例子。从图 6-5 中可以看到,小球实际撞击墙面
的位置与模拟的位置的差别。
用第三章三角学),但是我保证人们不会注意到这些细微的差别。如果在某些模拟中,对位
置的要求至关重要的话,那么我们还需要去查阅其它的书籍,
成。但是对于大多用 Flash 制作的数游戏或视觉效果而言,用这种方法已经足够了。
上的反弹,但它永远不会回到我们的手中。这是因为在反弹的过程中小球损失了一部分能量。
损失的能量也许是制造声音了,也可能是制造热量了,
量。重要一点是小球在发生反弹后运动的速度比之前要慢一些。换句话讲,它在反弹到某一
轴上时,损失了速度矢量。
体反弹的力量为 100%。为了制造能量的损失,可以用弹性系数作为阻力。为了能在代码中
使用这个参数,最好将它定义成一个变量。创建一个名为 bounce 的变量,并将它设置为
-0.7 这样的数字:
private var bounce:Number = -0.7;
么地相似。为 bounce 变量使用不同的系数,试试效果吧。
件文档类 Bouncing2.as,在这个示例中还包括了重力,我相信大家可以通过已掌握的知识
自行将它加上去。
摩擦力(Friction)
松手后,纸片的 x 轴起初运动得非常快,但很快 x 轴的运动速度又归为零。
阻力或阻尼。 虽然它不是一种严格意义上的力,但作用是相同的,因为它改变了物体的速度。
原理是,摩擦力只改变速度向量中的速度,而不会改变运动的方向。
种正确的方法和一种简易的方法。我们会分别讲述这两种方法,首先从“正确”的方法开始
吧。
摩擦力,正确的方法
去。事实上,是从速度向量的量值或速度中减去,不能只是简单地从 x,y 轴上减去。这样
做的话,如果物体沿着一定角度运动,其中的一个分速度会提前到达零,使得物体继续垂直
或水平地运动一会儿,结果看起来非常奇怪。
求出速度(是的,这就是勾股定理,第三章的内容)。再使用, Math.atan2(vy, vx) 求出角
度,代码如下:
var speed:Number = Math.sqrt(vx * vx + vy * vy);
var angle:Number = Math.atan2(vy, vx);
下:
if (speed > friction) {
} else {
}
这样一来,还需要使用正弦和余弦将角速度转换回 vx 和 vy,如下:
vx = Math.cos(angle) * speed;
vy = Math.sin(angle) * speed;
工作量不小吧?这里是全部文档类的内容 Friction1.as:
package {
}
向量。在onEnterFrame 方法中,speed 和 angle 的计算方法同前面所介绍的。如果 speed
小于 friction 则相减,否则让 speed 等于零。然后,重新计算 vx 和 vy ,最后将它们
加入到坐标上。
化这个计算过程。我想你一定同意去看看那个简便的方法吧。
摩擦力,简便的方法
意到这些细微的变化。只用两行简单的乘法即可搞定,我们所要做的就是用摩擦力乘以 x,y
速度向量,摩擦力常用的值大约为 0.9 或 0.8。因此,在每一帧, vx 和 vy 的值都将变
为上一次的 80% 或 90%。理论上,速度向量会无限接近零,但永远不会等于零。在实际应
用中,计算机计算如此小的数字的能力是有限的,所以最终都会取整为零。
的速度向量也是同比率变化的,所以不需要再将进行繁琐的转换。
方法(可在文档类 Friction2.as 中找到):
private function onEnterFrame(event:Event):void {
}
真实。
摩擦力应用
friction 变量:
private var friction:Number = 0.97;
其它的类变量继续延用,还要改变 onEnterFrame 方法,如下(ShipSimFriction.as):
private function onEnterFrame(event:Event):void {
}
力,会使旋转的速度慢下来直至停止。大家可以在第五章的旋转箭头中试验一下。这个手段
可以应用在所有的物体上,比如轮盘,电风扇或飞船推进器。
本章重要公式
让我们回顾一下本章介绍过的重要公式
移除出界对象:
if(sprite.x - sprite.width / 2 > right ||
sprite.x + sprite.width / 2 < left ||
sprite.y – sprite.height / 2 > bottom ||
sprite.y + sprite.height / 2 < top)
{
}
重置出界对象:
if(sprite.x - sprite.width / 2 > right ||
sprite.x + sprite.width / 2 < left ||
sprite.y – sprite.height / 2 > bottom ||
sprite.y + sprite.height / 2 < top)
{
}
屏幕环绕出界对象:
if (sprite.x - sprite.width / 2 > right) {
} else if (sprite.x + sprite.width / 2 < left) {
}
if (sprite.y – sprite.height / 2 > bottom) {
} else if (sprite.y + sprite.height / 2 < top) {
}
摩擦力应用(正确方法):
speed = Math.sqrt(vx * vx + vy * vy);
angle = Math.atan2(vy, vx);
if (speed > friction) {
} else {
}
vx = Math.cos(angle) * speed;
vy = Math.sin(angle) * speed;
摩擦力应用(简便方法):
vx *= friction;
vy *= friction;