转 第十二章 粒子引力与万有引力(as3.0)

很高兴到晋级到了这一章。  前面的章节总体来说都是些交互的运动。先让物体运动起来,
再让物体与环境产生交互,随后与用户交互,最后是物体之间的交互。本章将详细地为大家
介绍物体之间的交互,并带有一定距离的。说具体些,我们将学习粒子,重力(与前面有些
不同),弹性运动(还是它!)以及大名鼎鼎的 Node Garden。让我们开始吧!

粒子(Particles)
      说明一下我们所指的粒子是什么意思。出于本章的目的,粒子只就是一个独立的单位,
通常会伴有着几个(或多个)同样的单位。一个粒子可以说是一颗灰尘,一个沙滩球,或是
一个星球。
      粒子都具有共同的行为,也可以有它们各子独立的行为。OOP 的程序设计员们通常把
它们看成与对象相似的东西。      某个类的所有对象都具有相同的行为,它们是由这个类定义的,
而构造出的实例由于被赋予了不同的属性值,       从而变得有所差异。我们已经在例子中看过许
多 Ball 类的这样的实例。每个对象都有自己的属性:速度,质量,大小,颜色等等,但是
所有的小球都以相同的规则运动。
      在 Ball 类中,包涵了我们所需要的所有的功能。同样,粒子的实例也只是用来保存属
性的。   文档类只负责移动粒子及处理它们之间的交互。    另一种方法是把行为代码放到粒子类
中,这样每个粒子对象都会有它们自己的 enterFrame 函数或定时动画,它们将自行负责运
动或处理交互运动。执行处理函数时,它们都会有各自的加减运算。本书的例子程序,都将
运动和交互放在了文档类中,为的是看起来简单一些。
      程序总体来说与前面例子相同,     变化的部分都在两个粒子间的交互和引力上, 这些代码
都写在 onEnterFrame 方法中出现。程序中我们要创建多个粒子并随机放置到屏幕上,代码
如下:
package {
  import flash.display.Sprite;
  import flash.events.Event;
  public class ClassName extends Sprite {
   private var particles:Array;
   private var numParticles:uint = 30;
   public function ClassName() {
     init();
   }
private function init():void {
  particles = new Array();
  for (var i:uint = 0; i < numParticles; i++) {
    var particle:Ball = new Ball(5);
    particle.x = Math.random() * stage.stageWidth;
    particle.y = Math.random() * stage.stageHeight;
    particle.mass = 1;
    addChild(particle);
    particles.push(particle);
  }
  addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(event:Event):void {
    }
  }
}
    这里,每个粒子初始的半径都是 5 设置,质量均为 1。稍后通过改变这些值我们可以
看到不同的效果,当然还可以给粒子随机的速度和大小。
    本章的程序都是假设这些基本的设置已经完成,因为我们主要是来介绍 onEnterFrame
函数及其所需的方法。下面,先来介绍一些基本的理论,刚才创建的这个文件先不要关,因
为马上还要用到。

 

重力
     第一种粒子引力,就是重力。大家可能会说“这不是又回到的第二部分了吗?”是的,
但那是最常见的引力形式。
     站在地球上,引力的定义很简单:将物体向下牵引。实事上,向下的牵引力有着一种特
殊的速率。 它所施加在物体上的加速度等于 32 英尺每秒。从加速度的角度来讲就是某一时
刻内累加了多少速度。重力让物体运动 32 英尺,并且每秒都向下拉。但是,我们可以到最
高的山上, 或者最低的峡谷,这些对于 32 这个数字所产生的细微的变化是不足以让人们觉
察到的(除非使用精密的仪器) 。

万有引力
     我们知道,距离某个星球或某个巨大的天体越远,那么所受的引力就越小。对我们来说
这是件好事。如不然的话,我们会被吸到太阳里面,而太阳也许还会被吸到其它太阳或星球
中进去,所有的一切很快就灰飞烟灭了。我们把星球看作是粒子,粒子间的距离对于引力的
大小起决定性的作用。距离对于引力的影响很简单:引力与距离的平方成反比。需要解释一
下。首先, 引力也与质量联系紧密。物体质量越大,那么该物体对于其它物体的引力就越大,
那么它所受到其它物体的反作用力就越大。这就是所谓的万有引力常数(简写 G)   。引力方
程如下:

    force = G * m1 * m2 / distance2
    简单来说,  其它物体对该个物体的引力等于万有引力常数乘以二者的质量,再除以它们
之间距离的平方。呃… … 看上去很简单,只需要知道引力常数,就 OK 了。下面是正式
的定义:
    G = (6.6742 ± 0.0010) * 10-11 * m3 * kg-1 * s-2
    如果您想用 ActionScript 来计算结果的话,可以试一下。就我个人而言,当我看到这
样的公式,立刻就想到要伪造这些数字。我的引力公式如下:
    force = m1 * m2 / distance2

       是的,就是这样,忽略 G。如果要用 Flash 为 NASA [国家航空局] 作飞船导航系统的
话,就要把 G 算进去。但是如果我们是做宇宙大战游戏的话,那么就应该舍弃它。
       现在,公式已经有了,来写代码吧。首先设置 onEnterFrame 函数,而它又要调用
gravitate 函数,所以要把负责引力的代码分离出来:
private function onEnterFrame(event:Event):void {
  for (var i:uint = 0; i < numParticles; i++) {
    var particle:Ball = particles[i];
    particle.x += particle.vx;
    particle.y += particle.vy;
  }
  for (i=0; i < numParticles - 1; i++) {
    var partA:Ball = particles[i];
    for (var j:uint = i + 1; j < numParticles; j++) {
      var partB:Ball = particles[j];
      gravitate(partA, partB);
    }
  }
}

      开始用一个单独的循环让粒子运动,然后做一个双重循环执行交互。获得 partA 和
partB 后就将这两个粒子对象传给 gravitate 函数:
private function gravitate(partA:Ball, partB:Ball):void {
  var dx:Number = partB.x - partA.x;
  var dy:Number = partB.y - partA.y;
  var distSQ:Number = dx * dx + dy * dy;
  var dist:Number = Math.sqrt(distSQ);
  var force:Number = partA.mass * partB.mass / distSQ;
  var ax:Number = force * dx / dist;
  var ay:Number = force * dy / dist;
  partA.vx += ax / partA.mass;
  partA.vy += ay / partA.mass;
  partB.vx -= ax / partB.mass;
  partB.vy -= ay / partB.mass;
}
      首先,找出两个粒子间的 dx 和 dy 以及总距离。记住这个公式—— F = G * m1 * m2 /
distance2 — — 其 中 包 括 有 距 离 的 平 方 。 在 计 算 距 离 时 , 我 们 通 常 直 接 写 成 dist =
Math.sqrt(dx*dx + dy*dy)。但是在求距离的平方时,还要为这个平方根求平方!两倍的工作
量。如果用变量 distSQ 在开平方前保存 dx*dx + dy*dy,就节省了一部分计算。
      接下来,用总的引力乘以质量再除以距离的平方。就求出了 x,y 轴上总的加速度。然
后,使用第九章最后讨论的那个简洁的计算方法,用 dx / dist 取代 Math.cos(angle),用 dy
/ dist 取代 Math.sin(angle)。这样一来,就无需再使用 Math.atan2(dy, dx) 求出角度了。
    下面,我们继续讨论总引力和总加速度的问题。这是于两个物体间的引力构成的。根据
物体的质量,需要把它们分解为两个部分。想一想地球和太阳,两者之间存在着一种特殊的
引力,是由质量除以距离的平方产生的。地球与太阳在总引力的作用力下相互吸引。地球被
太阳吸引,而太阳反过来又被地球吸引。很明显,地球所获的加速度更多,因为它的质量比
太阳小。所以,在系统中每个物体都有单独的加速度,用总加速度除以物体的质量。因此,
就有了最后四行公式。注意 partA 的加速度是增加的,而partB 则是减少的。这要取决于 dx
和 dy 相减的顺序。
    最终的代码可在 Gravity.as 中找到。测试一下,可以看到开始时粒子是不动的,然后
慢慢地开始相互吸引。

 

    这种超速运动是 bug 吗?并不是代码的 bug。这其实正是期望的效果,这就是所谓的
弹弓效应。这是 NASA 用来发射探测器到外太空的方法。当一个物体与某个星球越来越近
时,它所受的加速度会越来越大,物体的运动速度也会非常之快。如果当物体与某个星球非
常接近时,该物体会受到这个星球的引力作用,被吸引过去。而这些速度是零燃料的——因
此它功不可没。
    回到 Flash。两个物体间的距离非常小时会发生什么——在几乎零距离的情况下。这时,
两个物体间的引力变得非常大,几乎是无穷大。因此,计算的结果是正确,但是从模拟的观
点来看,这并不真实。应该在物体足够接近时产生碰撞。如果我们计划让太空探测器直接落
到某个星球上,那么它的速度就不能是无穷大了,这样的话可能会撞出一个弹坑。

碰撞检测及反作用
       对粒子而言,需要进行碰撞判断以及产生反作用力。到底让碰撞后发生什么事情,取决
于我们自己,可以让粒子爆炸后消失,也可以让一个粒子消失,并把它的质量加到另一个粒
子上面,就像两个粒子溶合到了一起。
       例如,前一章在 checkCollision 函数中,讲到的碰撞及反作用力。下面把它插入进来,
内容如下:
function onEnterFrame(event:Event):void {
  for (var i:uint = 0; i < numParticles; i++) {
    var particle:Ball = particles[i];
    particle.x += particle.vx;
    particle.y += particle.vy;
  }
  for (i=0; i < numParticles - 1; i++) {
    var partA:Ball = particles[i];
    for (var j:uint = i + 1; j < numParticles; j++) {
      var partB:Ball = particles[j];
      checkCollision(partA, partB);
      gravitate(partA, partB);
    }
  }
}
      粗体部分是新加入的。同时要把 checkCollision 及 rotate 函数复制粘贴到了这个文件
中。 (不要忘记导入 flash.geom.Point 函数,因为这些函数中需要使用它) ,全部代码请见
GravityBounce.as。
      我们看到粒子间相互吸引,在产生碰撞后反弹。试改变粒子的质量(mass),观察不同的
引力效果。如果给粒子一个负质量,就可以看到它们彼此互斥。
      在 GravityRandom.as,文件中,我坚持让代码不会有太大的变化,只是在 init 方法中
多加入了一行语句,并在两个地方做了一下改变:
private function init():void {
  particles = new Array();
  for (var i:uint = 0; i < numParticles; i++) {
    var size:Number = Math.random() * 25 + 5;
    var particle:Ball = new Ball(size);
    particle.x = Math.random() * stage.stageWidth;
    particle.y = Math.random() * stage.stageHeight;
    particle.mass = size;
    addChild(particle);
    particles.push(particle);
  }
  addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
      这里我们给每个粒子一个随机的大小并且根据物体的大小给出质量,如图 12-2 所示。
课题开始变得越来越有趣了。
轨道运动
    最后看一个现实中的例子,我们来创建一个简单的行星系统,有太阳和地球。创建一个
质量为 10,000 的太阳和一个质量为 1 的行星。接下来,让行星移开太阳一段距离,并给
它一个垂直于太阳的速度。
如果给出的质量,距离与速度都非常合适,那么就能让行星进入轨道。见文档类

Orbit.as。 需要解释一下 init 中的代码,还有一点变化就是将 numParticles 变量设为 2。
private function init():void {
  particles = new Array();
  var sun:Ball = new Ball(100, 0xffff00);
  sun.x = stage.stageWidth / 2;
  sun.y = stage.stageHeight / 2;
  sun.mass = 10000;
  addChild(sun);
  particles.push(sun);
  var planet:Ball = new Ball(10, 0x00ff00);
  planet.x = stage.stageWidth / 2 + 200;
  planet.y = stage.stageHeight / 2;
  planet.vy = 7;
  planet.mass = 1;
  addChild(planet);
  particles.push(planet);
  addEventListener(Event.ENTER_FRAME, onEnterFrame);
}

 

引力 VS 弹性
    对比一下引力与弹性,我们发现它们非常相似,或者说它们是皆然相反的。因为,它们
都是用物体间的加速度,将两个物体牵在一起的。但是,在引力效果中,物体间的距离越远,
则加速度越小。在弹性效果中,则是距离越大,加速度越大。
    如果将前一个例子中的引力代码换成弹性代码的话, 产生的效果就没那么有趣了,因为
粒子最终会聚集在一起,因为弹性运动不能容忍距离。如果引力的座右铭是 “眼不见为净”,
那么弹性运动的信条就是“距离产生美”。
    因此现在就陷入了一个进退两难的局面。我们想让粒子以弹性的形式相互吸引, 还想让
弹性能够容忍距离,不让粒子都牵连在一起。那么我的解决办法就是,设置一个最短距离的
变量。严格来讲,这更像是最大距离,因为它表示能够产生交互的最大距离。但时我大脑总
认为最短距离是正确的,是因为粒子间至少要达到这个距离才会产生交互。 如果它们彼此距
离很远,则忽略不计。

弹性节点花园
     让我们来动手搭建自己的弹性节点花园吧。为了与本书的上一版相同,我将影片的背景
色改为黑色,让粒子为白色。看起来不如用渐变填充代码或嵌入外部图像效果好,不过,这
些是我们今后肯定要做的。将来我将给大家一下有趣的图形材料。
     可在 NodeGarden.as 中找到代码,让我们一步步来。首先,设置一些变量:粒子的数
目,刚刚提到的最短距离,以及弹性(spring)。
var numParticles:uint = 30;
var minDist:Number = 100;
var springAmount:Number = .001;
     第八章的弹性运动示例中,我们设置的弹性值为 0.2 左右。这里我们使用的值要更小
些,因为有很多的粒子会产生交互运动。如果给的值过大,则会使速度过快。然而,如果给
的值太小,粒子就会在屏幕上慢条斯理地运动,就像是没有觉察到其它节点一样。
     接下来进行初始化:
private function init():void {
  particles = new Array();
  for (var i:uint = 0; i < numParticles; i++) {
   var particle:Ball = new Ball(5, 0xffffff);
   particle.x = Math.random() * stage.stageWidth;
   particle.y = Math.random() * stage.stageHeight;
   particle.vx = Math.random() * 6 - 3;
   particle.vy = Math.random() * 6 - 3;
   addChild(particle);
   particles.push(particle);
  }
  addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
    创建一些粒子,放在舞台上,给予它们随机的速度。注意,这里没有给出物体的质量
(mass)。稍后,在“带有质量的节点”一节,会给大家演示加入质量的例子。
    再下面,就是 onEnterFrame 方法:
private function onEnterFrame(event:Event):void {
    创建一些粒子,放在舞台上,给予它们随机的速度。注意,这里没有给出物体的质量
(mass)。稍后,在“带有质量的节点”一节,会给大家演示加入质量的例子。
    再下面,就是 onEnterFrame 方法:
private function onEnterFrame(event:Event):void {
     很熟悉了吧。以前讲过这个例子,只是在屏幕环绕的代码中 spring 了函数。最后是本
程序的主干 spring 函数如下:
private function spring(partA:Ball, partB:Ball):void {
  var dx:Number = partB.x - partA.x;
  var dy:Number = partB.y - partA.y;
  var dist:Number = Math.sqrt(dx * dx + dy * dy);
  if (dist < minDist) {
   var ax:Number = dx * springAmount;
   var ay:Number = dy * springAmount;
   partA.vx += ax;
   partA.vy += ay;
   partB.vx -= ax;
   partB.vy -= ay;
  }
}
     首先求出两个粒子间的距离。如果不小于 minDist [最短距离],则继续向下执行。如
果小于则根据 springValue 求出每个轴的加速度,  然后将它加入 partA 的速度,再将它从
partB 的速度中减去。这就会将粒子拉近。
     如图 12-5 所示,注意观察粒子是如何汇聚成团的,有点像苍蝇在乱飞。但它们也会聚
在一起,散开,加入其它的团中等等,做出一些有趣的随机行为。试改变 minDist 及
springValue 的值,观察运行效果。
带连线的节点
      从标题可以看出,这就是为了能够表现每对节点间的交互。还有比绘制节点间的连线更
好的方法吗?实现起来也非常简单。只对 spring 函数做一点改变:
private function spring(partA:Ball, partB:Ball):void {
  var dx:Number = partB.x - partA.x;
  var dy:Number = partB.y - partA.y;
  var dist:Number = Math.sqrt(dx * dx + dy * dy);
  if (dist < minDist) {
    graphics.lineStyle(1, 0xffffff);
    graphics.moveTo(partA.x, partA.y);
    graphics.lineTo(partB.x, partB.y);
   var ax:Number = dx * springAmount;
   var ay:Number = dy * springAmount;
   partA.vx += ax;
   partA.vy += ay;
   partB.vx -= ax;
   partB.vy -= ay;
  }
}
      如果两个产生交互,则该函数设置将一个线条样式并在两点间进行连线。我们还应该将
下述语句加入到 onEnterFrame 方法的第一行,用以在每帧时进行刷新:
graphics.clear();
      现在节点间已经连接起来了,如图 12-6 所示。但是我不喜欢这种随节点的变化突然连
线或断开的效果。
    我想要一种梯度式的变化。意思是如果两个节点距离大于 minDist,  则线条应当几乎是
完全透明的。随着它们的距离越来越近,线条会变得越来越亮。因此,如果用 dist / minDist,
就会得到一个从 0 到 1 的分数作为 alpha。  但这种效果与我们的设想是反向的,  因为如果
dist 等于 minDist,alpha 将等于 1,但是当 dist 接近 0 是,alpha 将接近 0。OK,我
们可以 1 减去这个数,那么这个数值就反过来了。以下是画线代码:
graphics.lineStyle(1, 0xffffff, 1 - dist / minDist);
graphics.moveTo(partA.x, partA.y);
graphics.lineTo(partB.x, partB.y);
    在我看来,这是一种非常漂亮的效果。
带有质量的节点
     在撰写本章时,我就密谋要让每个节点都有其质量,这是我以前没有想到的。于是就写
出了 NodesMass.as。在这个文件中给每个节点一个随机的大小,并且根据大小给定质量。
private function init():void {
  particles = new Array();
  for (var i:uint = 0; i < numParticles; i++) {
   var size:Number = Math.random() * 10 + 2;
   var particle:Ball = new Ball(size, 0xffffff);
   particle.x = Math.random() * stage.stageWidth;
   particle.y = Math.random() * stage.stageHeight;
   particle.vx = Math.random() * 6 - 3;
   particle.vy = Math.random() * 6 - 3;
    particle.mass = size;
   addChild(particle);
   particles.push(particle);
  }
  addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
      质量仅仅用于在 spring 函数中为物体添加速度。我们用每个粒子的速度除以质量,给
较大的物体较大的惯性。
private function spring(partA:Ball, partB:Ball):void {
  var dx:Number = partB.x - partA.x;
  var dy:Number = partB.y - partA.y;
  var dist:Number = Math.sqrt(dx * dx + dy * dy);
  if (dist < minDist) {
   graphics.lineStyle(1, 0xffffff, 1 - dist / minDist);
   graphics.moveTo(partA.x, partA.y);
   graphics.lineTo(partB.x, partB.y);
   var ax:Number = dx * springAmount;
   var ay:Number = dy * springAmount;
    partA.vx += ax / partA.mass;
    partA.vy += ay / partA.mass;
    partB.vx -= ax / partB.mass;
    partB.vy -= ay / partB.mass;
  }
}
      由于我削减了部分弹性运动的效果,因此将 springValue 的值增加到 .0025。我喜欢
这种完善的效果。

    节点能做什么用呢?在我看来,这种效果看上去酷毙了,我曾用它作为屏幕保护程序。
这种效果可以作为所有游戏场景的序幕,放入一个行星飞船在上面,让飞船躲避这些节点。
我想这是个很好的挑战!

本章重要公式
    显然本章最大的公式就是万有引力公式。
引力的一般公式:
                               2
force = G * m1 * m2 / distance   (上面的2 是平方)
ActionScript 实现万有引力:
function gravitate(partA:Ball, partB:Ball):void {
    var dx:Number = partB.x - partA.x;
    var dy:Number = partB.y - partA.y;
    var distSQ:Number = dx * dx + dy * dy;
    var dist:Number = Math.sqrt(distSQ);
    var force:Number = partA.mass * partB.mass / distSQ;
    var ax:Number = force * dx / dist;
    var ay:Number = force * dy / dist;
    partA.vx += ax / partA.mass;
    partA.vy += ay / partA.mass;
    partB.vx -= ax / partB.mass;
    partB.vy -= ay / partB.mass;
}
    函数中,使用的是 Ball 类型,大家可以根据实际需要使用自定义类的类型,只要能够
存储速度,质量和位置即可。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值