转 第六章 边界与摩擦力(as3.0)

到目前为止,我们已经学会了如何在影片中绘制图形,并且通过施加外力使影片运动起
来。然而,在这些例子中也许会遇到这样的烦恼:物体移动到屏幕外后就不到了。如果在某
个角度上运动得过快,那么就没有办法再让物体退回来,只能选择重新运行影片。
    我们常常忽视边界的存在如:墙和屋顶,最平常的就是地面。通常在制作太空模拟时,
要用环境边界作为一道屏障,保证物体能够在一个可见的范围内运动。
    另一个常被忽略的问题是,所处的环境如何改变物体的运动。惯性一词是用来形容物体
在空间中穿梭,并保持以同样的方向及速度运动,只有对其施加外力,才会使它的运动发生
改变。改变物体速度向量的力,可能是摩擦力的一种——甚至可以是空气的阻力。目前,我
们已经能够模拟真空环境下的物体运动了,但是大家一定还想模拟真实环境下的物体运动。
那么本章就要解决前面这两个问题。首先,学习边界环境下物体的运动,然后学习如何模拟
阻力,Let's go。

 

环境边界
    先来学习边界的设置,就像我们的日常活动一样:开运动会,做某项工作,盖房子等,
这里边界是指为这项活动保留的活动空间。意思是“我只关心发生在这个范围内的事情,如
果超出了这个范围,就不再关注它了。”
    当物体超出了这个范围后,我们可以对它进行一些操作。可以再把它移回来,或把它从
关注的对象中移除,另一种选择是跟随它。只要物体是运动的,那么它就有机会离开这个范
围。当物体离开后,我们可以选择忘记它,或将它移动回来,或跟随它。我们将介绍前两种
方法,不过先要确定边界的位置,再学习如何定义边界。

设置边界
    通常,边界就是一个矩形。从最简单的例子开始——基于舞台大小的边界, AS 3 中,
                                                                      
舞台由一个名为 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 就可以了。
    比如,我们可以为对象创建一个居住的“房间” 在这个例子中,
                                              ,             边界可以是 top = 100,
bottom = 300, left = 50, right = 400。OK,边界已经有了,用它们能做什么呢?判断所
有移动的对象,看它们是否仍在这个空间内,这里可以使用 if 语句,简化的样式如下:
if(ball.x > stage.stageWidth) {
    // do something
} else if(ball.x < 0) {
    // do something
}
if(ball.y > stage.stageHeight) {
    // do something
} else if(ball.y < 0) {
    // do something
}
     使用 if 和 else 语句判断边界,如果小球 x 坐标大于右边界,就意味着它超出了右
边界。但不可能同时超出左边界,所以不需要再用一条 if 语句进行判断。因此,只需要在
第一个 if 语句失败后再判断左边边界即可, 顶部和底部也是如此。然而,物体有可能在 x,y
轴上同时超出边界,所以要把这两个判断语句分开。如果物体出界后,应该对它们执行什么
样的操作呢?答案有四种:
■ 将对象移除;
■ 重置到舞台上,像生成一个新对象一样(重置对象);
■ 重置到舞台上,将同一个对象放置在不同位置;
■ 将其反弹回去。
我们从最简单的移除对象开始。

 

移除对象
    如果对象是不断产生的,那么使用一次性删除对象的方法是非常有效的。被删除的对象
将会由新的对象所取代,这样舞台就永远不为空。但也不能生成太多的可移动对象,因为这
样会使 Flash Player 变慢。
    调用 removeChild(对象名),删除影片或显示对象,会将对象实例从舞台上移除。请注
意,被移除的显示对象仍然存在,只是看不到而已。如果要将该对象彻底删除,还应该调用
delete 对象名 将其完全删除。
    如果移动的对象只是一些影片实例,并且物体的运动只由 enterFrame 函数进行处理,
那么要停止整个程序的执行只需调用 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);
}

     虽然使用球形或圆形是个比较特殊的例子,但这样的代码对于所有注册点在中心的物体
来说,都是适用的。
     下面一个例子中,将要使用 Ball 类,这个类前面也用过,但这次要加上一点新的内容。
为该类加入公共属性 vx 和 vy ,让每个小球都有自己的速度向量。全部代码如下:
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=40, color:uint=0xff0000) {
     this.radius = radius;
     this.color = color;
     init();
   }
   public function init():void {
     graphics.beginFill(color);
     graphics.drawCircle(0, 0, radius);
     graphics.endFill();
   }
  }
}
      对于纯面向对象编程来说,也许不会用这些公共属性,而是将它们转为私有属性,再使
用 getter/setter 方法进行操作。但是为了方便起见,就不再遵循这个规则了,仅使用公
共属性作替代。下面一个文档类,Removal.as,设置了许多小球,并在它们离开舞台后进行
删除:
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 < count; 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(event: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);
        }
      }
     }
   }
  }
}
      应该很容易理解吧。首先。创建 20 个小球的实例,随机安排它们在舞台上的位置,给
出随机的 x,y 速度向量,并将它们加入显示列表,然后 push 到数组中。
      onEnterFrame 方法通过速度向量使小球移动,判断边界,并将出界的小球删除。请注
意,  不但要将小球从显示列表中删除,同时还要使用 Array.splice 函数将数组中的引用也
删除。Array.splice 有两个参数:开始删除元素的索引,删除元素的个数。在这个例子中,
只删除一个元素即当前索引处的元素。
大家也许注意到了本例中的 for 语句,与其它例子中的不太一样:
for(var i:Number = balls.length - 1; i > 0; i--)
    这是让 for 循环倒着执行,遍历整个数组。因为如果在数组中使用 splice ,数组的
索引就会改变。
    最后,在删除数组元素后,还要判断数组长度是否小于零。如果小于零,则撤消对
enterFrame 的侦听器。

 

重置对象
     下一个策略是将超出舞台范围的对象进行重置。实际上就是重新配置,重新设置属性。
当一个对象离开了舞台后,它就没有作用了,不过,可以将其重置到舞台上,让它作为一个
新对象再加入进来。永远不要担心对象的数量过多,因为这个数量是固定不变的。这个技术
用于制作喷泉效果非常合适:一串粒子不停地喷射,超出舞台的粒子重新加入到水流中。
     现在就来制作一个喷泉效果。作为喷泉的粒子,我们同样使用 Ball 类,但只把它设为
2 像素的大小并给它一个随机的颜色。 水源在舞台底部的中心位置,所有的粒子都从这里发
出,当它们超出舞台边界后,将会被重置回来。所有粒子都以一个随机的负 y 速度和一个
随机的 x 速度作为开始。这样就会上喷射,并伴有轻微的左右移动。当粒子重置后,它们
的速度向量也将被重置,同时粒子也要受到重力的牵引。文档类 Fountain.as:
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 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 < count; 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() * -10 - 10;
   addChild(ball);
   balls.push(ball);
  }
  addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(event:Event):void {
  for (var i:Number = 0; i < balls.length; i++) {
   var ball:Ball = Ball(balls[i]);
    ball.vy += gravity;
   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() * -10 - 10;
      }
     }
   }
  }
}
      请试着加入风力效果(提示:设置 wind 变量,并加入到 vx )。

 


屏幕环绕
    下一个处理越界对象的方法,我称其为屏幕环绕。概念很简单:一个对象超出了屏幕的
左边界,就让它在屏幕右边出现;在右边出界,则将它置到左边;上面出界就回到下面。明
白了吧。这个思想与重置对象的概念非常相似,只是位置有所不同。
    再回到前面那个老游戏,小行星。第五章的这个飞船影片有些问题:一旦太空船飞出了
舞台,就很难将它找回。如果使用屏幕环绕技术,那么影片超出屏幕边界的距离不会大于一
像素。
      让我们为太空船示例重新加入一些行为,这里是文档类(ShipSim2.as),新的部分用粗
体标出:
package {
  import flash.display.Sprite;
  import flash.events.Event;
  import flash.events.KeyboardEvent;
  import flash.ui.Keyboard;
  import flash.display.StageAlign;
  import flash.display.StageScaleMode;
  public class ShipSim2 extends Sprite {
   private var ship:Ship;
   private var vr:Number = 0;
   private var thrust:Number = 0;
   private var vx:Number = 0;
   private var vy:Number = 0;
   public function ShipSim2() {
     init();
   }
   private function init():void {
     stage.scaleMode = StageScaleMode.NO_SCALE;
     stage.align=StageAlign.TOP_LEFT;
     ship = new Ship();
     addChild(ship);
  ship.x = stage.stageWidth / 2;
  ship.y = stage.stageHeight / 2;
  addEventListener(Event.ENTER_FRAME, onEnterFrame);
  stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
  stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
}
private function onKeyDown(event:KeyboardEvent):void {
  switch (event.keyCode) {
   case Keyboard.LEFT :
    vr = -5;
    break;
   case Keyboard.RIGHT :
    vr = 5;
    break;
   case Keyboard.UP :
    thrust = 0.2;
    ship.draw(true);
    break;
   default :
    break;
  }
}
private function onKeyUp(event:KeyboardEvent):void {
  vr = 0;
     thrust = 0;
     ship.draw(false);
   }
   private function onEnterFrame(event:Event):void {
     ship.rotation += vr;
     var angle:Number = ship.rotation * Math.PI / 180;
     var ax:Number = Math.cos(angle) * thrust;
     var ay:Number = Math.sin(angle) * thrust;
     vx += ax;
     vy += ay;
     ship.x += vx;
     ship.y += vy;
     var left:Number = 0;
     var right:Number = stage.stageWidth;
     var top:Number = 0;
     var bottom:Number = stage.stageHeight;
     if (ship.x - ship.width / 2 > right) {
       ship.x = left - ship.width / 2;
     } else if (ship.x + ship.width / 2 < left) {
       ship.x = right + ship.width / 2;
     }
     if (ship.y - ship.height / 2 > bottom) {
       ship.y = top - ship.height / 2;
     } else if (ship.y < top - ship.height / 2) {
       ship.y = bottom + ship.height / 2;
     }
   }
  }
}
    这里同样使用了第五章的 Ship 类,请确保该文件与这个类在同一路径下。由于飞船是
用白色线条绘制的,所以不要忘记将背景色改为黑色。大家可以看到,在新的类中加入了边
界的定义及判断。

 

反弹
    本节中的弹性处理也许是最常用也是最复杂的,  但也没有屏幕环绕那么复杂,所以不用
担心。当检测到物体超出舞台后,开始应用弹性,不改变改变物体的位置,只改变它的速度
向量。方法很简单:如果物体超出了左、右边界,只需要使它的 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) {
    ball.x = right – ball.radius;
    vx *= -1;
}
else if(ball.x – ball.radius < left) {
    ball.x = left + ball.radius;
    vx *= -1;
}
反弹的步骤如下:
■ 判断物体是否超出了边界;
■ 如果是,将其置到边界处;
■ 然后将它的速度向量取反。
     叙述部分就到这里,下面来看代码。下一个示例,仍使用 Ball 类,文档类
(Bouncing.as):
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() * 10 - 5;
   vy=Math.random() * 10 - 5;
   addChild(ball);
   addEventListener(Event.ENTER_FRAME,onEnterFrame);
 }
 private function onEnterFrame(event: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 < left) {
      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 < top) {
      ball.y=top + ball.radius;
      vy*= -1;
     }
   }
  }
}
    多进行几次测试,观察不同角度的运动,并试着将速度向量变大或变小。不得不承认,
这是一个数学计算与现实情况不完全一致的例子。从图 6-5 中可以看到,小球实际撞击墙面
的位置与模拟的位置的差别。

    要达到真正的位置,就要使用更为复杂的计算方法。虽然我们完全可以做到这一点(使
用第三章三角学),但是我保证人们不会注意到这些细微的差别。如果在某些模拟中,对位
置的要求至关重要的话,那么我们还需要去查阅其它的书籍,  并重新考虑使用哪种软件来完
成。但是对于大多用 Flash 制作的数游戏或视觉效果而言,用这种方法已经足够了。
    再比如,我们手握一个橡胶球,然后松手使它落到地上,当小球落到地面时,会发生向
上的反弹,但它永远不会回到我们的手中。这是因为在反弹的过程中小球损失了一部分能量。
损失的能量也许是制造声音了,也可能是制造热量了,  地面或周围的空气也会吸收一部分能
量。重要一点是小球在发生反弹后运动的速度比之前要慢一些。换句话讲,它在反弹到某一
轴上时,损失了速度矢量。
    这样一来,可以简单地重建一个 Flash,前面使用 -1 作为弹性系数。这就意味着,物
体反弹的力量为 100%。为了制造能量的损失,可以用弹性系数作为阻力。为了能在代码中
使用这个参数,最好将它定义成一个变量。创建一个名为 bounce 的变量,并将它设置为
-0.7 这样的数字:
private var bounce:Number = -0.7;
    在 if 语句中使用 bounce 这个变量代替 -1。试过后大家会发现与现实中的弹性是多
么地相似。为 bounce 变量使用不同的系数,试试效果吧。
    我们在学习知识的时候,最好与前面所学的原理结合起来。  大家可以看一下本书的源文
件文档类 Bouncing2.as,在这个示例中还包括了重力,我相信大家可以通过已掌握的知识
自行将它加上去。

 

摩擦力(Friction)
     假设有一张纸,将它撕碎后用力丢向空中。纸片会受到重力向下的牵引(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;
工作量不小吧?这里是全部文档类的内容 Friction1.as:
package {
 import flash.display.Sprite;
 import flash.display.StageAlign;
 import flash.display.StageScaleMode;
 import flash.events.Event;
 public class Friction1 extends Sprite {
  private var ball:Ball;
  private var vx:Number = 0;
  private var vy:Number = 0;
  private var friction:Number = 0.1;
  public function Friction1() {
   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() * 10 - 5;
     vy = Math.random() * 10 - 5;
     addChild(ball);
     addEventListener(Event.ENTER_FRAME, onEnterFrame);
   }
   private function onEnterFrame(event:Event):void {
     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 = Math.cos(angle) * speed;
     vy = Math.sin(angle) * speed;
     ball.x += vx;
     ball.y += vy;
   }
  }
}
    这就是反比例速度向量的代码。摩擦力设置为 0.1,然后给小球一个随机的 x,y 速度
向量。在onEnterFrame 方法中,speed 和 angle 的计算方法同前面所介绍的。如果 speed
小于 friction 则相减,否则让 speed 等于零。然后,重新计算 vx 和 vy ,最后将它们
加入到坐标上。
    总共用了十几行代码和四个三角函数才完成,大家也许能提出更好的写法,但却不能简
化这个计算过程。我想你一定同意去看看那个简便的方法吧。

 

摩擦力,简便的方法
    大家也许都猜到了,简便的方法不像前面的方法那样精确, 但是我敢打赌不会有人会注
意到这些细微的变化。只用两行简单的乘法即可搞定,我们所要做的就是用摩擦力乘以 x,y
速度向量,摩擦力常用的值大约为 0.9 或 0.8。因此,在每一帧, vx 和 vy 的值都将变
为上一次的 80% 或 90%。理论上,速度向量会无限接近零,但永远不会等于零。在实际应
用中,计算机计算如此小的数字的能力是有限的,所以最终都会取整为零。
    这种方法最好的一点是速度向量永远不会变为负数,所以不需要进行判断。同样,x,y 轴
的速度向量也是同比率变化的,所以不需要再将进行繁琐的转换。
    只需要将前面例子中的 firction 变量设为 0.9 ,然后按如下代码改变 onEnterFrame
方法(可在文档类 Friction2.as 中找到):
private function onEnterFrame(event:Event):void {
    vx *= friction;
    vy *= friction;
    ball.x += vx;
    ball.y += vy;
}
    的确简便了不少吧!测试几次,能看出与之前的不同吗?平心而论,这个效果看起来更
真实。

 

摩擦力应用
     让我们回到熟悉的飞船上,现在让宇宙空间具有摩擦力。在 ShipSim2.as 类中加入
friction 变量:
private var friction:Number = 0.97;
其它的类变量继续延用,还要改变 onEnterFrame 方法,如下(ShipSimFriction.as):
private function onEnterFrame(event:Event):void {
  ship.rotation += vr;
  var angle:Number = ship.rotation * Math.PI / 180;
  var ax:Number = Math.cos(angle) * thrust;
  var ay:Number = Math.sin(angle) * thrust;
  vx += ax;
  vy += ay;
  vx *= friction;
  vy *= friction;
  ship.x += vx;
  ship.y += vy;
  var left:Number = 0;
  var right:Number = stage.stageWidth;
  var top:Number = 0;
  var bottom:Number = stage.stageHeight;
  if (ship.x - ship.width / 2 > right) {
   ship.x = left - ship.width / 2;
  } else if (ship.x + ship.width / 2 < left) {
   ship.x = right + ship.width / 2;
  }
  if (ship.y - ship.height / 2 > bottom) {
   ship.y = top - ship.height / 2;
  } else if (ship.y < top - ship.height / 2) {
   ship.y = bottom + ship.height / 2;
  }
}
     只加了三行代码,感觉就不一样了。
     任何使用速度向量的地方都可以加入摩擦力。比如在物体的旋转上(变量 vr )应用摩擦
力,会使旋转的速度慢下来直至停止。大家可以在第五章的旋转箭头中试验一下。这个手段
可以应用在所有的物体上,比如轮盘,电风扇或飞船推进器。
本章重要公式
让我们回顾一下本章介绍过的重要公式
移除出界对象:
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) {
  sprite.x = left - sprite.width / 2;
} else if (sprite.x + sprite.width / 2 < left) {
  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 < top) {
  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;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值