11.3.2 两个轴上的动量守恒
OK,深呼吸,进入下一级。现在,我们已经用过了这个冗长的公式,而它几乎是即插即用型的。
只需要把两个物体的质量和速度插入公式中,就可以得到结果。下面,进入更为复杂的一级——二维空间。本章开始时说过,其策略是使用坐标旋转。让我们来看看原因。
1.理解原理及策略
图 11-2 表示刚才看到那个例子:一维的碰撞。
![图11-2](images/soft/flash/flashAS3Animation/11-2.png)
图 11-2 一维碰撞
可以看到,两个物体有着不同的大小,不同的质量以及不同的速度。速度用箭头表示,就是向量。回忆一下,速度向量指针表示运动的方向,长度表示速度的大小。
一维的例子非常简单,因为两个速度向量都在 x 轴上。所以,可以直接加上或减去它们的量值(长度)。现在,请看图 11-3,小球在二维空间中的碰撞。
![图11-3](images/soft/flash/flashAS3Animation/11-3.png)
图 11-3 二维碰撞
速度向量完全不同了,不能单纯地将速度代入到动量守恒公式中,因为,这会导致完全错误的结果。那么应该如何解决呢?
通过旋转第二张图,可以得到与第一张非常相似的图。首先,要知道两个小球之间形成的角度并且旋转整个场景——坐标和速度——逆时针旋转。例如,如果角度是 30 度,就将所有的物体旋转 -30 度,这与第十章的斜面反弹是一样的。结果如图 11-4 所示。
![图11-4](images/soft/flash/flashAS3Animation/11-4.png)
图 11-4 旋转后的二维碰撞
两个小球之间的角度非常重要,这个角度称为碰撞角度。重要的原因在于,它是小球速度的一部分——速度的一部分要依赖于角度。
下面请看图 11-5。这里,在每个向量上面加入了 vx 和 vy。注意,两个小球的 vx 都严格地依照碰撞的角度来定。
![图11-5](images/soft/flash/flashAS3Animation/11-5.png)
图 11-5 加入 x,y 速度
我们说过,只关心碰撞的角度。那么现在它就是 vx,可以忘掉 vy。如图 11-6 所示。
![图11-6](images/soft/flash/flashAS3Animation/11-6.png)
图 11-6 只关心 x 速度
看得眼熟吗?这就是第一张图! 现在可以使用即插即用的动量公式来解决这个问题了。(请注意解题的步骤!)
在应用这个公式时,会得出两个新的 vx 值。记住 vy 的值永远不变。vx 的变化如图11-7 所示。
![图11-7](images/soft/flash/flashAS3Animation/11-7.png)
图 11-7 新的 x 速度,y 速度不变,得出新的速度
猜到下面该怎么办了吗?只需要把一切旋转回去,如图 11-8 所示。
![图11-8](images/soft/flash/flashAS3Animation/11-8.png)
图 11-8 将一切旋转回来
下面把这个过程转换为代码。对我来说最难的地方就是要一再地说服大家“这很容易”。
2.编写代码
首先要让两个小球以一定的角度运动并且最后相互碰撞。开始的设置与前面相同,两个小球实例:ball0 和 ball1。这次让它们变大一点,如图 11-9 所示,这样它们碰撞的机会就会大一些。
![图11-9](images/soft/flash/flashAS3Animation/11-9.png)
图11-9 二维动量守恒,设置舞台
package { import flash.display.Sprite; import flash.events.Event; import flash.display.StageScaleMode; import flash.display.StageAlign; public class Billiard3 extends Sprite { private var ball0:Ball; private var ball1:Ball; private var bounce:Number = -1.0; public function Billiard3() { init(); } private function init():void { stage.scaleMode = StageScaleMode.NO_SCALE; stage.align = StageAlign.TOP_LEFT; ball0 = new Ball(150); ball0.mass = 2; ball0.x = stage.stageWidth - 200; ball0.y = stage.stageHeight - 200; ball0.vx = Math.random() * 10 - 5; ball0.vy = Math.random() * 10 - 5; addChild(ball0); ball1 = new Ball(90); ball1.mass = 1 ball1.x = 100; ball1.y = 100; ball1.vx = Math.random() * 10 - 5; ball1.vy = Math.random() * 10 - 5; addChild(ball1); addEventListener(Event.ENTER_FRAME, onEnterFrame); } private function onEnterFrame(event:Event):void { ball0.x += ball0.vx; ball0.y += ball0.vy; ball1.x += ball1.vx; ball1.y += ball1.vy; checkCollision(ball0, ball1); checkWalls(ball0); checkWalls(ball1); } private function checkWalls(ball:Ball):void { if(ball.x + ball.radius > stage.stageWidth) { ball.x = stage.stageWidth - ball.radius; ball.vx *= bounce; } else if(ball.x - ball.radius < 0) { ball.x = ball.radius; ball.vx *= bounce; } if(ball.y + ball.radius > stage.stageHeight) { ball.y = stage.stageHeight - ball.radius; ball.vy *= bounce; } else if(ball.y - ball.radius < 0) { ball.y = ball.radius; ball.vy *= bounce; } } private function checkCollision(ball0:Ball, ball1:Ball):void { var dx:Number = ball1.x - ball0.x; var dy:Number = ball1.y - ball0.y; var dist:Number = Math.sqrt(dx*dx + dy*dy); if(dist < ball0.radius + ball1.radius) { // calculate angle, sine and cosine var angle:Number = Math.atan2(dy, dx); var sin:Number = Math.sin(angle); var cos:Number = Math.cos(angle); // rotate ball0's position var x0:Number = 0; var y0:Number = 0; // rotate ball1's position var x1:Number = dx * cos + dy * sin; var y1:Number = dy * cos - dx * sin; // rotate ball0's velocity var vx0:Number = ball0.vx * cos + ball0.vy * sin; var vy0:Number = ball0.vy * cos - ball0.vx * sin; // rotate ball1's velocity var vx1:Number = ball1.vx * cos + ball1.vy * sin; var vy1:Number = ball1.vy * cos - ball1.vx * sin; // collision reaction var vxTotal:Number = vx0 - vx1; vx0 = ((ball0.mass - ball1.mass) * vx0 + 2 * ball1.mass * vx1) / (ball0.mass + ball1.mass); vx1 = vxTotal + vx0; x0 += vx0; x1 += vx1; // rotate positions back var x0Final:Number = x0 * cos - y0 * sin; var y0Final:Number = y0 * cos + x0 * sin; var x1Final:Number = x1 * cos - y1 * sin; var y1Final:Number = y1 * cos + x1 * sin; // adjust positions to actual screen positions ball1.x = ball0.x + x1Final; ball1.y = ball0.y + y1Final; ball0.x = ball0.x + x0Final; ball0.y = ball0.y + y0Final; // rotate velocities back ball0.vx = vx0 * cos - vy0 * sin; ball0.vy = vy0 * cos + vx0 * sin; ball1.vx = vx1 * cos - vy1 * sin; ball1.vy = vy1 * cos + vx1 * sin; } } } }
这些内容想必大家睡梦中都可以写出来。设置边界,随机的速度,加入质量,根据速度移动小球,判断边界。注意,我把边界的判断单独放在了 checkWalls 函数中,以便重复使用。
同样,将碰撞判断放到名为 checkCollision 的函数中。onEnterFrame 变为:
private function onEnterFrame(event:Event):void { ball0.x += ball0.vx; ball0.y += ball0.vy; ball1.x += ball1.vx; ball1.y += ball1.vy; checkCollision(ball0, ball1); checkWalls(ball0); checkWalls(ball1); }
这样一来,我只需要给大家介绍 checkCollision 函数以及与之相配套的函数就可以了。其它的代码没有变化,大家可以在 Billiard3.as 中看到完整的程序。
函数一开始非常简单,就是进行距离碰撞检测。
private function checkCollision(ball0:Ball, ball1:Ball):void { var dx:Number = ball1.x - ball0.x; var dy:Number = ball1.y - ball0.y; var dist:Number = Math.sqrt(dx*dx + dy*dy); if (dist < ball0.radius + ball1.radius) { // 处理碰撞的代码 } }
三分之二的代码已经写好了,目前为止都是小意思!首先判断碰撞需要知道两个 ball 之间的角度,使用 Math.atan2(dy, dx) 得到。(如果读到这里你还没有想到这点,请复习第三章三角学)然后,保存计算出的正余弦值,因为我们要反复使用。
// 计算角度和正余弦值 var angle:Number = Math.atan2(dy, dx); var sin:Number = Math.sin(angle); var cos:Number = Math.cos(angle);
下面,对速度和小球的位置进行坐标旋转。调用旋转后的位置 x0, y0, x1, y1 然后旋转 vx0, vy0, vx1, vy1。
因为我们使用 ball0 作为“中心点”,它的坐标就是 0,0。这个值在旋转后都不会改变,只要写:
// 旋转 ball0 的位置 var x0:Number = 0; var y0:Number = 0;
接下来, ball1 的位置是与 ball0 的相对位置, 与刚刚计算出来的距离值 dx 和 dy 相对应。因此,只需对这两个数进行旋转,就可以得到 ball1 旋转后的位置:
// 旋转 ball1 的位置 var x1:Number = dx * cos + dy * sin; var y1:Number = dy * cos - dx * sin;
最后,旋转速度。写法如下:
// 旋转 ball0 的速度 var vx0:Number = ball0.vx * cos + ball0.vy * sin; var vy0:Number = ball0.vy * cos - ball0.vx * sin; // 旋转 ball1 的速度 var vx1:Number = ball1.vx * cos + ball1.vy * sin; var vy1:Number = ball1.vy * cos - ball1.vx * sin;
所有的旋转代码如下所示:
private function checkCollision(ball0:Ball, ball1:Ball):void { var dx:Number = ball1.x - ball0.x; var dy:Number = ball1.y - ball0.y; var dist:Number = Math.sqrt(dx*dx + dy*dy); if (dist < ball0.radius + ball1.radius) { // 计算角度和正余弦值 var angle:Number = Math.atan2(dy, dx); var sin:Number = Math.sin(angle); var cos:Number = Math.cos(angle); // 旋转 ball0 的位置 var x0:Number = 0; var y0:Number = 0; // 旋转 ball1 的位置 var x1:Number = dx * cos + dy * sin; var y1:Number = dy * cos - dx * sin; // 旋转 ball0 的速度 var vx0:Number = ball0.vx * cos + ball0.vy * sin; var vy0:Number = ball0.vy * cos - ball0.vx * sin; // 旋转 ball1 的速度 var vx1:Number = ball1.vx * cos + ball1.vy * sin; var vy1:Number = ball1.vy * cos - ball1.vx * sin; } }
现在怎么样, 不那么可怕了吧?先叫个暂停。我们已经完成了这个艰难历程的三分之一。
接下来只需要用 vx0,ball0.mass 和 vx1,ball1.mass 就可以执行一维碰撞。根据早先那个一维碰撞的例子可知:
var vxTotal:Number = ball0.vx - ball1.vx; ball0.vx = ((ball0.mass - ball1.mass) * ball0.vx + 2 * ball1.mass * ball1.vx) / (ball0.mass + ball1.mass); ball1.vx = vxTotal + ball0.vx;
现在重写这段代码:
var vxTotal:Number = vx0 - vx1; vx0 = ((ball0.mass - ball1.mass) * vx0 + 2 * ball1.mass * vx1) /(ball0.mass + ball1.mass); vx1 = vxTotal + vx0;
只需将 ball0.vx 和 ball1.vx 替换成了旋转后的版本 vx0 和 vx1。然后插入到函数中:
private function checkCollision(ball0:Ball, ball1:Ball):void { var dx:Number = ball1.x - ball0.x; var dy:Number = ball1.y - ball0.y; var dist:Number = Math.sqrt(dx*dx + dy*dy); if (dist < ball0.radius + ball1.radius) { // 计算角度和正余弦值 var angle:Number = Math.atan2(dy, dx); var sin:Number = Math.sin(angle); var cos:Number = Math.cos(angle); // 旋转 ball0 的位置 var x0:Number = 0; var y0:Number = 0; // 旋转 ball1 的位置 var x1:Number = dx * cos + dy * sin; var y1:Number = dy * cos - dx * sin; // 旋转 ball0 的速度 var vx0:Number = ball0.vx * cos + ball0.vy * sin; var vy0:Number = ball0.vy * cos - ball0.vx * sin; // 旋转 ball1 的位置 var vx1:Number = ball1.vx * cos + ball1.vy * sin; var vy1:Number = ball1.vy * cos - ball1.vx * sin; // 碰撞的作用力 var vxTotal:Number = vx0 - vx1; vx0 = ((ball0.mass - ball1.mass) * vx0 + 2 * ball1.mass * vx1) / (ball0.mass + ball1.mass); vx1 = vxTotal + vx0; x0 += vx0; x1 += vx1; } }
这段代码同样也把新的 x 速度加到 x 位置上,为了使小球分开,同一维碰撞的例子。
现在更新工作已完成,接下来将一切再反转回来:
// 将位置旋转回来 var x0Final:Number = x0 * cos - y0 * sin; var y0Final:Number = y0 * cos + x0 * sin; var x1Final:Number = x1 * cos - y1 * sin; var y1Final:Number = y1 * cos + x1 * sin;
回忆一下旋转方程中 + 和 – 的调换,因此现在是向另一个方向旋转。最终的位置与系统中心点 ball0 的位置相对的。因此,需要把它们都加到 ball0 的位置上,从而得到实际在屏幕上的位置。先从 ball1 开始,因此就要用到 ball0 的初始位置,而不是更新后的位置:
// 将位置调整为屏幕的实际位置 ball1.x = ball0.x + x1Final; ball1.y = ball0.y + y1Final; ball0.x = ball0.x + x0Final; ball0.y = ball0.y + y0Final;
最后,将速度旋转回来。可以直接使用 ball 的 vx 和 vy 属性:
// 将速度旋转回来 ball0.vx = vx0 * cos - vy0 * sin; ball0.vy = vy0 * cos + vx0 * sin; ball1.vx = vx1 * cos - vy1 * sin; ball1.vy = vy1 * cos + vx1 * sin;
让我们看一下这段完整的函数:
function checkCollision(ball0:Ball, ball1:Ball):void { var dx:Number = ball1.x - ball0.x; var dy:Number = ball1.y - ball0.y; var dist:Number = Math.sqrt(dx*dx + dy*dy); if (dist < ball0.radius + ball1.radius) { // 计算角度和正余弦值 var angle:Number = Math.atan2(dy, dx); var sin:Number = Math.sin(angle); var cos:Number = Math.cos(angle); // 旋转 ball0 的位置 var x0:Number = 0; var y0:Number = 0; // 旋转 ball1 的位置 var x1:Number = dx * cos + dy * sin; var y1:Number = dy * cos - dx * sin; // 旋转 ball0 的速度 var vx0:Number = ball0.vx * cos + ball0.vy * sin; var vy0:Number = ball0.vy * cos - ball0.vx * sin; // 旋转 ball1 的速度 var vx1:Number = ball1.vx * cos + ball1.vy * sin; var vy1:Number = ball1.vy * cos - ball1.vx * sin; // 碰撞的作用力 var vxTotal:Number = vx0 - vx1; vx0 = ((ball0.mass - ball1.mass) * vx0 + 2 * ball1.mass * vx1) / (ball0.mass + ball1.mass); vx1 = vxTotal + vx0; x0 += vx0; x1 += vx1; // 将位置旋转回来 var x0Final:Number = x0 * cos - y0 * sin; var y0Final:Number = y0 * cos + x0 * sin; var x1Final:Number = x1 * cos - y1 * sin; var y1Final:Number = y1 * cos + x1 * sin; // 将位置调整为屏幕的实际位置 ball1.x = ball0.x + x1Final; ball1.y = ball0.y + y1Final; ball0.x = ball0.x + x0Final; ball0.y = ball0.y + y0Final; // 将速度旋转回来 ball0.vx = vx0 * cos - vy0 * sin; ball0.vy = vy0 * cos + vx0 * sin; ball1.vx = vx1 * cos - vy1 * sin; ball1.vy = vy1 * cos + vx1 * sin; } }
实验一下这个例子。试改变 Ball 实例的大小,初始速度,质量等。
至于 checkCollision 函数,非常显眼。通过读注释,可以看出它实际上是被分做很多简单代码段的。我们还可以做优化,或再进行因式分解消除多余的重复内容。请培养这个良好的习惯,请见 Billiard4.as:
package { import flash.display.Sprite; import flash.events.Event; import flash.display.StageScaleMode; import flash.display.StageAlign; import flash.geom.Point; public class Billiard4 extends Sprite { private var ball0:Ball; private var ball1:Ball; private var bounce:Number = -1.0; public function Billiard4() { init(); } private function init():void { stage.scaleMode = StageScaleMode.NO_SCALE; stage.align = StageAlign.TOP_LEFT; ball0 = new Ball(150); ball0.mass = 2; ball0.x = stage.stageWidth - 200; ball0.y = stage.stageHeight - 200; ball0.vx = Math.random() * 10 - 5; ball0.vy = Math.random() * 10 - 5; addChild(ball0); ball1 = new Ball(90); ball1.mass = 1 ball1.x = 100; ball1.y = 100; ball1.vx = Math.random() * 10 - 5; ball1.vy = Math.random() * 10 - 5; addChild(ball1); addEventListener(Event.ENTER_FRAME, onEnterFrame); } private function onEnterFrame(event:Event):void { ball0.x += ball0.vx; ball0.y += ball0.vy; ball1.x += ball1.vx; ball1.y += ball1.vy; checkCollision(ball0, ball1); checkWalls(ball0); checkWalls(ball1); } private function checkWalls(ball:Ball):void { if(ball.x + ball.radius > stage.stageWidth) { ball.x = stage.stageWidth - ball.radius; ball.vx *= bounce; } else if(ball.x - ball.radius < 0) { ball.x = ball.radius; ball.vx *= bounce; } if(ball.y + ball.radius > stage.stageHeight) { ball.y = stage.stageHeight - ball.radius; ball.vy *= bounce; } else if(ball.y - ball.radius < 0) { ball.y = ball.radius; ball.vy *= bounce; } } private function checkCollision(ball0:Ball, ball1:Ball):void { var dx:Number = ball1.x - ball0.x; var dy:Number = ball1.y - ball0.y; var dist:Number = Math.sqrt(dx*dx + dy*dy); if(dist < ball0.radius + ball1.radius) { // calculate angle, sine and cosine var angle:Number = Math.atan2(dy, dx); var sin:Number = Math.sin(angle); var cos:Number = Math.cos(angle); // rotate ball0's position var pos0:Point = new Point(0, 0); // rotate ball1's position var pos1:Point = rotate(dx, dy, sin, cos, true); // rotate ball0's velocity var vel0:Point = rotate(ball0.vx, ball0.vy, sin, cos, true); // rotate ball1's velocity var vel1:Point = rotate(ball1.vx, ball1.vy, sin, cos, true); // collision reaction var vxTotal:Number = vel0.x - vel1.x; vel0.x = ((ball0.mass - ball1.mass) * vel0.x + 2 * ball1.mass * vel1.x) / (ball0.mass + ball1.mass); vel1.x = vxTotal + vel0.x; // update position pos0.x += vel0.x; pos1.x += vel1.x; // rotate positions back var pos0F:Object = rotate(pos0.x, pos0.y, sin, cos, false); var pos1F:Object = rotate(pos1.x, pos1.y, sin, cos, false); // adjust positions to actual screen positions ball1.x = ball0.x + pos1F.x; ball1.y = ball0.y + pos1F.y; ball0.x = ball0.x + pos0F.x; ball0.y = ball0.y + pos0F.y; // rotate velocities back var vel0F:Object = rotate(vel0.x, vel0.y, sin, cos, false); var vel1F:Object = rotate(vel1.x, vel1.y, sin, cos, false); ball0.vx = vel0F.x; ball0.vy = vel0F.y; ball1.vx = vel1F.x; ball1.vy = vel1F.y; } } private function rotate(x:Number, y:Number, sin:Number, cos:Number, reverse:Boolean):Point { var result:Point = new Point(); if(reverse) { result.x = x * cos + y * sin; result.y = y * cos - x * sin; } else { result.x = x * cos - y * sin; result.y = y * cos + x * sin; } return result; } } }
这里我设计了一个用作旋转的函数,rotate,传入所需的参数值,返回一个flash.geom.Point 实例。这个对象已经定义好了 x 和 y 属性(还有许多其他的这里用不到的属性),返回的旋转后的 Point 的 x,y 属性。虽然这个版本并不是很好读,但是可以省去很多重复的代码。
3.加入更多的物体
让两个影片碰撞并带有反作用力不是件容易的事,但我们已经做到了。恭喜各位。下面要让多个物体进行碰撞——比如说八个。听起来要复杂四倍,其实不然。之前的函数每次要判断两个小球,而这并不是我们真正想要的。将多个物体放到舞台上,让它们运动,判断碰撞,这是我们在碰撞检测的例子中(第九章)作过。现在所要作的就是把这些代码插入到checkCollision 函数中的碰撞检测中去。
例子程序(MultiBilliard.as),开始将八个小球存入数组,循环执行,为它们设置不同的属性:
package { import flash.display.Sprite; import flash.events.Event; import flash.geom.Point; public class MultiBilliard extends Sprite { private var balls:Array; private var numBalls:uint = 8; private var bounce:Number = -1.0; public function MultiBilliard() { init(); } private function init():void { balls = new Array(); for (var i:uint = 0; i < numBalls; i++) { var radius:Number = Math.random() * 20 + 20; var ball:Ball = new Ball(radius); ball.mass = radius; ball.x = i * 100; ball.y = i * 50; ball.vx = Math.random() * 10 - 5; ball.vy = Math.random() * 10 - 5; addChild(ball); balls.push(ball); } addEventListener(Event.ENTER_FRAME, onEnterFrame); } private function onEnterFrame(event:Event):void { // 稍后给出… } // checkWalls, checkCollision, rotate 函数与上一个例子相同 } }
大家也许注意到了,我将每个小球的初始位置人为地进行了设置,为的是不让它们一开始就发生接触。如果那样的话,它们就会粘在一起。
onEnterFrame 方法惊人的简单。只需要做两次循环:一次是基本运动,一次是碰撞检测。
private function onEnterFrame(event:Event):void { for (var i:uint = 0; i < numBalls; i++) { var ball:Ball = balls[i]; ball.x += ball.vx; ball.y += ball.vy; checkWalls(ball); } for (i = 0; i < numBalls - 1; i++) { var ballA:Ball = balls[i]; for (var j:Number = i + 1; j < numBalls; j++) { var ballB:Ball = balls[j]; checkCollision(ballA, ballB); } } }
第一次循环,遍历所有舞台上的小球,让它们运动并在撞墙后反弹。接下来,在一个嵌套循环中让每个小球与其它小球进行比较,就像第九章碰撞检测中讨论的那样。获得两个小球的引用,分别叫作 ballA 和 ballB,将它们传入 checkCollision 函数。这样就可以了,checkWalls, checkCollision, rotate 函数与上一个例子完全相同,这里不再敷述。
要加入更多的小球,只需改变 numBalls 变量即可,并确保它们最初不会发生碰撞。
4.解决潜在问题
前面提醒过:两个物体之间仍有可能在某些情况下粘在一起。最有可能发生在影片非常拥挤的条件下,并且在运动速度很快时结果会更糟糕。我们有可能看到两三个小球在舞台的边角上发生碰撞。
假设舞台上有三个小球 —— ball0, ball1, ball2 —— 它们恰好要碰撞在一起。下面是发生的基本情况:
- 依照物体的速度移动物体。
- 先判断 ball0 与 ball1,ball0 与 ball2,发现没有产生碰撞。
- 判断 ball1 与 ball2。发现它俩发生了碰撞,然后计算出所有新的速度及位置,为的是让它们不会接触到一起。但却不小心让 ball1 与 ball0 接触上了。然而,这一组判断已经执行过了,因此这次就被忽略了。
- 在下一次循环中, 代码继续依照物体的速度移动物体。 但是无意中把 ball0 和 ball1 移动得更近了。
- 现在代码注意到了 ball0 与 ball1 发生了碰撞。然后计算出新的速度并加到物体的当前位置上,让它们分离。但是,因为它们已经接触上了,不能将它们真正地分开。于是又粘到了一起。
这种情况最容易发生在空间很小物体很多,移动速度很快的情况下。也发生在,物体间一开始就产生接触的情况下。可能迟早我们都会遇到这种情况,所以最好来看看问题出在哪。确切的位置是在 checkCollision 函数中,定义的这两条语句:
// 更新位置 pos0.x += vel0.x; pos1.x += vel1.x;
这里我们只是假设产生的碰撞是由两个小球的速度引起的,再给它们加入的新的速度,就会使它们分开。多数情况下,这是可以的。但是我刚才描述的情况除外。如果那样的话,就要明确地知道影片在运动前是分离的。于是想出了如下方法:
// 更新位置 var absV:Number = Math.abs(vel0.x) + Math.abs(vel1.x); var overlap:Number = (ball0.radius + ball1.radius) - Math.abs(pos0.x - pos1.x); pos0.x += vel0.x / absV * overlap; pos1.x += vel1.x / absV * overlap;
这些都是我自己创造的,所以我能不确定它的精确度如何,但是看上去工作得还不错。首先确定绝对速度(自创的一个词),是所有速度的绝对值之和。例如,如果一个速度是 -5 另一个速度是 10,那么绝对值就是 5 和 10,总和就是 5 + 10 = 15。
接下来,确定小球之间重叠部分的大小。用总半径长度减去总距离。
然后根据小球速度与绝对速度的百分比,让小球移动出重叠的那一部分。
结果会让小球之间没有重叠。这种方法比早前的版本要复杂一些, 但确消除了很多bug。
在 MultiBilliard2.as 中,创建了 20 个小球,并让它们的体积大一些,在舞台上随机分布。也许在几帧之内,小球之间可能发生重叠,但是由于新加入这些代码的作用,让它们平静了下来。
当然,您也可以去研究自己的解决方案,如果您找到了更简单,更有效,更精确的方法,请拿出来一起分享!
文件下载(已下载 2382 次)发布时间:2011/6/28 上午10:15:34 阅读次数:9339