影片通过施加外力使影片运动起来。然而,物体移动到屏幕外后就看不到了。如果在某个角度上运动得过快,那么就没有
办法再让物体退回来,只能选择重新运行影片。
另一个常被忽略的问题是,所处的环境如何改变物体的运动。惯性一词是用来形容物体在空间中穿梭,并保持以同样的方向及速度运动,只有对其施加外力,才会使它的运动发生改变。
环境边界中的边界指为这项活动保留的活动空间。意思是“我只关心发生在这个范围内的事情,如果超出了这个范围,就
不再关注它了。”
只要物体是运动的,那么它就有机会离开这个范围。当物体离开后,我们可以选择忘记它,或将它移动回来,或跟随它。
设置边界
通常,边界就是一个矩形。然后判断所有移动的对象,看它们是否仍在这个空间内,这里可以使用if和else语句判断边界,如果对象X坐标大于右边界,就意味着它超出了右边界。但不可能同时超出左边界,所以不需要再用一条if语句进行判断。因此,只需要在第一个if语句失败后再判断左边边界即可,顶部和底部也是如此。然而,物体有可能在X,Y轴上同时超出边界,所以要氢这
两个判断语句分开。示例如下:
if(objcet.x>stage.stageWidth){
//do something
}else if(object.x<0){
//do something
}
if(object.y>stage.stageHeight){
//do something
}else if(object.y<0){
//do something
}
如果对象出界后,可以执行如下四种操作:
将对象移除;
重置到舞台上,像生成一个新对象一样(重置对象);
重置到舞台上,将同一个对象放置在不同的位置;
将其反弹回去。
移除对象
如果对象是不断产生的,那么使用一次性删除对象的方法是非常有效的。被删除的对象将会由新的对象所取代,这样舞台就永远不会为空。但也不能生成太多的可移动对象,因为这样会使Flash Player变慢。
调用removeChild(对象名),删除影片或显示对象,会将对象实例从舞台上移除。请注意,被移除的显示对象仍然存在,只是看不到而已。如果要将该对象彻底删除,还应该调用delete 对象名 将其完全删除。
如果移动的对象只是一些影片实例,并且物体的运动只由enterFrame函数进行处理,那么要停止整个程序的执行只需调用removeEventListener(Event.ENTER_FRAME,onEnterFrame);就可以了。另一方面,如果运动的对象很多,要通过持续执行代码使每个对象都动起来,就应该在数组中保存所有对象的引用,然后循环这个数组使里面的每个对象都动起来。随后,当删除了其中的某一个对象后,使用Array.splice方法同时将该对象的引用在数组中删除。
对象的位置由中心的注册点的位置决定,而注册点超出了屏幕的右边界,对象将被删除。如果对象的运动足够快,也许看上去问题不大。但如果运动得非常缓慢,我们会看着它走向屏幕的边界,但还差一半没有走完就被移除了!这就像一个演员只离开了舞台的一半就把戏服脱掉了,破坏了塑造人物的形象。
所以,要让对象完全离开场景,要等到它完全离开视野后再采取处理。实现这个计划,需要考虑到物体的宽度。因为注册点在中心,所以,可以将宽度的一半保存为radius属性。
下面是一个事例:用到二个类,一个是Ball类,绘制一个小球。一个是Removal类,创建20个小球,让它们移动,直到全部移动到屏幕外,删除onEnterFrame事件。
Ball类代码如下:
package {
import flash.display.Sprite;
public class Ball extends Sprite{
public var radius:Number;
private var color:uint;
public var vx:Number=0;
public var vy:Number=0;
public function Ball(radius:Number=10,color:uint=0xff0000){
this.radius=radius;
this.color=color;
init();
}
public function init():void{
graphics.beginFill(color);
graphics.drawCircle(0,0,radius);
graphics.endFill();
}
}
}
Removal类代码如下:
package {
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
public class Removal extends Sprite {
private var count:int=20;
private var balls:Array;
public function Removal() {
init();
}
private function init():void {
stage.scaleMode=StageScaleMode.NO_SCALE;
stage.align=StageAlign.TOP_LEFT;
balls=new Array();
for (var i:int=0; i
var ball:Ball=new Ball(10);
ball.x=Math.random()*stage.stageWidth;
ball.y=Math.random()*stage.stageHeight;
ball.vx=Math.random()*2-1;
ball.vy=Math.random()*2-1;
addChild(ball);
balls.push(ball);
}
addEventListener(Event.ENTER_FRAME,onEnterFrame);
}
private function onEnterFrame(e:Event):void {
for (var i:Number=balls.length-1; i>0; i--) {
var ball:Ball=Ball(balls[i]);
ball.x+=ball.vx;
ball.y+=ball.vy;
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);
balls.splice(i,1);
if (balls.length<=0) {
removeEventListener(Event.ENTER_FRAME,onEnterFrame);
}
}
}
}
}
}
重置对象
重置对象是将超出舞台范围的对象进行重置。实际上就是重新配置,重新设置属性。当一个对象离开了舞台后,它就没有作用了,不过可以将其重置到舞台上,让它作为一个新对象再加入进来。永远不要担心对象的数量过多,因为这个数量是固定不变的。这个技术用于制作喷泉效果非常合适:一串粒子不停地喷射,超出舞台的粒子重新加入到水流中。
下面制作喷泉效果,使用Ball类作为水粒,使用Fountain用作效果类,其中gravity表示重力值,wind表示风力。代码如下
:
Ball类
package {
import flash.display.Sprite;
public class Ball extends Sprite{
public var radius:Number;
private var color:uint;
public var vx:Number=0;
public var vy:Number=0;
public function Ball(radius:Number=10,color:uint=0xff0000){
this.radius=radius;
this.color=color;
init();
}
public function init():void{
graphics.beginFill(color);
graphics.drawCircle(0,0,radius);
graphics.endFill();
}
}
}
Fountain类
package {
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
public class Fountain extends Sprite{
private var count:int=100;
private var gravity:Number=0.5;
private var wind:Number=0.1;
private var balls:Array;
public function Fountain(){
init();
}
private function init():void{
stage.scaleMode=StageScaleMode.NO_SCALE;
stage.align=StageAlign.TOP_LEFT;
balls=new Array();
for(var i:int=0;i
var ball:Ball=new Ball(2,Math.random()*0xffffff);
ball.x=stage.stageWidth/2;
ball.y=stage.stageHeight;
ball.vx=Math.random()*2-1;
ball.vy=Math.random()*-30-30;
addChild(ball);
balls.push(ball);
}
addEventListener(Event.ENTER_FRAME,onEnterFrame);
}
private function onEnterFrame(e:Event):void{
for(var i:Number=0;i
var ball:Ball=Ball(balls[i]);
ball.vy+=gravity;
ball.vx+=wind;
ball.x+=ball.vx;
ball.y+=ball.vy;
if(ball.x-ball.radius>stage.stageWidth || ball.x+ball.radius<0 || ball.y-
ball.radius>stage.stageHeight || ball.y+ball.radius<0){
ball.x=stage.stageWidth/2;
ball.y=stage.stageHeight;
ball.vx=Math.random()*2-1;
ball.vy=Math.random()*-18-18;
}
}
}
}
}
屏幕环绕
屏幕环绕是当一个对象超出了屏幕的左界,就让它在屏幕右边出现;在右边出界,则将它置到左边;上面出界就回到下面;下面出界就到上面。
现在用两个类来实现屏幕环绕:一个是Ball类,这个不用说了吧,呵呵。。。还有一个是Cincture类,控制小球。代码如
下:
Cincture类
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
public class Cincture extends Sprite{
private var ball:Ball;
public function Cincture(){
init();
}
private function init():void{
stage.scaleMode=StageScaleMode.NO_SCALE;
stage.align=StageAlign.TOP_LEFT;
ball=new Ball();
ball.x=Math.random()*stage.stageWidth;
ball.y=Math.random()*stage.stageHeight;
ball.vx=ball.vy=2;
addChild(ball);
addEventListener(Event.ENTER_FRAME,onEnterFrame);
}
private function onEnterFrame(e:Event){
ball.x+=ball.vx;
ball.y+=ball.vy;
var left:Number=0;
var right:Number=stage.stageWidth;
var top:Number=0;
var bottom:Number=stage.stageHeight;
if(ball.x-ball.width/2>right){
ball.x=left-ball.width/2;
}else if(ball.x+ball.width/2
ball.x=right+ball.width/2;
}
if(ball.y-ball.height/2>bottom){
ball.y=top-ball.height/2;
}else if(ball.y
ball.y=bottom+ball.height/2;
}
}
}
}
反弹
反弹就是当检测到物体超出舞台后,不改变物体的位置,只改变它的速度向量。方法很简单:如果物体超出了左、右边界,只需要使它的X速度向量取反。如果超出了上、下边界,只需要让Y速度向量取反。坐标轴取反非常简单,只需要乘以-1。
对于反弹时机来说,我们不希望等到物体完全超出了舞台后开始反弹。同样,也不希望出现半张图片的效果。因此,首先要判断出物体首次超出边界的瞬间。然后,将物体的运动路径取反,再加上小球宽度/高度的一半。
只要物体超出了舞台,即使只有不少部分,都要使它的速度向量取反,并且还需要将物体重新定位到边界外,这就形成了一个非常明显的撞击反弹的效果。如果不调整物体的位置,到下一帧,在物体移动之前,也许仍然处在边界外。如果这样的话,物体的速度向量又将取反,则向墙内运动!就会产生物体进出墙体的情形,然后在这附近振荡。
反弹的步骤如下:
判断物体是否超出了边界;
如果是,将其置到边界外;
然后将它的速度向量取反。
下面来看代码,还使用以前的Ball类,再使用一个Bouncing类来描述反弹效果。
Bouncing类:
package {
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
public class Bouncing extends Sprite{
private var ball:Ball;
private var vx:Number;
private var vy:Number;
public function Bouncing(){
init();
}
private function init():void{
stage.scaleMode=StageScaleMode.NO_SCALE;
stage.align=StageAlign.TOP_LEFT;
ball=new Ball();
ball.x=stage.stageWidth/2;
ball.y=stage.stageHeight/2;
vx=Math.random()*20-5;
vy=Math.random()*20-5;
addChild(ball);
addEventListener(Event.ENTER_FRAME,onEnterFrame);
}
private function onEnterFrame(e:Event):void{
ball.x+=vx;
ball.y+=vy;
var left:Number=0;
var right:Number=stage.stageWidth;
var top:Number=0;
var bottom:Number=stage.stageHeight;
if(ball.x+ball.radius>right){
ball.x=right-ball.radius;
vx*=-1;
}else if(ball.x-ball.radius
ball.x=left+ball.radius;
vx*=-1;
}
if(ball.y+ball.radius>bottom){
ball.y=bottom-ball.radius;
vy*=-1;
}else if(ball.y-ball.radius
ball.y=top+ball.radius;
vy*=-1;
}
}
}
}
摩擦力
假设有一张纸,将它撕碎后用力丢向空中。纸片会受到重力向下的牵引(Y轴),当我们松手后,纸片的X轴起初运动得非常快,但很快X轴的运动速度又归为零。
很显然,这里面没有负的加速度,但是纸片的速度向量却发生了改变,这就是摩擦力或阻力。虽然它不是一种严格意义上的力,但作用是相同的,因为它改变了物体的速度。原理是,摩擦力只改变速度向量中的速度,而不会改变运动的方向。
摩擦力是与速度向量相反的力,假设有一个摩擦力的数值,就可以将它从速度向量中减去。事实上,是从速度向量的量值或速度中减去,不能只是简单地从X,Y轴上减去。这样做的话,如果物体沿着一定角度运动,其中的一个分速度会提前到达零,使得物体继续垂直或水平地运动一会儿,结果看起来非常奇怪。
所以,我们要做的就是根据速度和方向找出角速度。使用vx和vy的平方和开平方后求出速度。再使用Math.atan2(vy,vx)求出角度,代码如下:
var speed:Number=Math.sqrt(vx*vx+vy*vy);
var angle:Number=Math.atan2(vy,vx);
然后就可以从速度向量中减去速度。如果摩擦力大于速度,速度就变为零,计算代码如下:
if(speed>friction){
speed-=friction;
}else{
speed=0;
}
这样一来,还需要使用正弦和余弦将角速度转换回vx和vy,如下:
vx=Math.cos(angle)*speed;
vy=Math.sin(angle)*speed;
下面给出一个关于摩擦力的事例:
package {
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
public class Friction extends Sprite{
private var ball:Ball;
private var vx:Number=0;
private var vy:Number=0;
private var friction:Number=0.1;
public function Friction(){
init();
}
private function init():void{
stage.scaleMode=StageScaleMode.NO_SCALE;
stage.align=StageAlign.TOP_LEFT;
ball=new Ball();
ball.x=stage.stageWidth/2;
ball.y=stage.stageHeight/2;
vx=-Math.random()*7-7;
vy=-Math.random()*7-7;
addChild(ball);
addEventListener(Event.ENTER_FRAME,onEnterFrame);
}
private function onEnterFrame(e:Event):void{
var speed:Number=Math.sqrt(vx*vx+vy*vy);
var angle:Number=Math.atan2(vy,vx);
trace(speed);
if(speed>friction){
speed-=friction;
}else{
speed=0;
removeEventListener(Event.ENTER_FRAME,onEnterFrame);
}
vx=Math.cos(angle)*speed;
vy=Math.sin(angle)*speed;
ball.x+=vx;
ball.y+=vy;
}
}
}
还有一种简单的方法实现摩擦力:用摩擦力乘以X,Y速度向量,摩擦力常用的值大约为0.9或0.8。因此,在每一帧,vx和vy的值都将变为上一次的80%或90%。理论上,速度向量会无限接近零,但永远不会等于零。在实际应用中,计算机计算如此小的数字的能力是有限的,所以最终都会取整为零。
这种方法最好的一点的速度向量永远不会变为负数,所以不需要进行判断。同样,X、Y轴的速度向量也是同比率变化的,所以不需要再将进行繁琐的转换。
只需要将前面例子中的firction变量设为0.9,然后按如下代码改变onEnterFrame方法:
private function onEnterFrame(e:Event):void{
vx*=friction;
vy*=friction;
ball.x+=vx;
ball.y+=vy;
}
大家可以观察下这两个方法的实现效果,我感觉第二个比第一个更真实。
下面是边界与摩擦力的主要公式:
移除出界对象:
if(sprite.x-sprite.width/2>right || sprite.x+sprite.width/2
bottom
|| sprite.y+sprite.height/2
//移除影片的代码
}
重置出界对象:
if(sprite.x-sprite.width/2>right || sprite.x+sprite.width/2
bottom
|| sprite.y+sprite.height/2
//重置影片的位置和速度
}
屏幕环绕出界对象:
if(sprite.x-sprite.width/2>right){
sprite.x=left-sprite.width/2;
}else{
sprite.x=right+sprite.width/2;
}
if(sprite.y-sprite.height/2>bottom){
sprite.y=top-sprite.height/2;
}else if(sprite.y+sprite.height/2
sprite.y=bottom+sprite.height/2;
}
摩擦力应用(正确方法):
speed=Math.sqrt(vx*vx+vy*vy);
angle=Math.atan2(vy,vx);
if(speed>friction){
speed-=friction;
}else{
speed=0;
}
vx=Math.cos(angle)*speed;
vy=Math.sin(angle)*speed;
摩擦力应用(简便方法):
vx*=friction;
vy*=friction;