11.3.2 两个轴上的动量守恒
OK,深呼吸,进入下一级。现在,我们已经用过了这个冗长的公式,而它几乎是即插即用型的。
只需要把两个物体的质量和速度插入公式中,就可以得到结果。下面,进入更为复杂的一级——二维空间。本章开始时说过,其策略是使用坐标旋转。让我们来看看原因。
1.理解原理及策略
图 11-2 表示刚才看到那个例子:一维的碰撞。
图 11-2 一维碰撞
可以看到,两个物体有着不同的大小,不同的质量以及不同的速度。速度用箭头表示,就是向量。回忆一下,速度向量指针表示运动的方向,长度表示速度的大小。
一维的例子非常简单,因为两个速度向量都在 x 轴上。所以,可以直接加上或减去它们的量值(长度)。现在,请看图 11-3,小球在二维空间中的碰撞。
图 11-3 二维碰撞
速度向量完全不同了,不能单纯地将速度代入到动量守恒公式中,因为,这会导致完全错误的结果。那么应该如何解决呢?
通过旋转第二张图,可以得到与第一张非常相似的图。首先,要知道两个小球之间形成的角度并且旋转整个场景——坐标和速度——逆时针旋转。例如,如果角度是 30 度,就将所有的物体旋转 -30 度,这与第十章的斜面反弹是一样的。结果如图 11-4 所示。
图 11-4 旋转后的二维碰撞
两个小球之间的角度非常重要,这个角度称为碰撞角度。重要的原因在于,它是小球速度的一部分——速度的一部分要依赖于角度。
下面请看图 11-5。这里,在每个向量上面加入了 vx 和 vy。注意,两个小球的 vx 都严格地依照碰撞的角度来定。
图 11-5 加入 x,y 速度
我们说过,只关心碰撞的角度。那么现在它就是 vx,可以忘掉 vy。如图 11-6 所示。
图 11-6 只关心 x 速度
看得眼熟吗?这就是第一张图! 现在可以使用即插即用的动量公式来解决这个问题了。(请注意解题的步骤!)
在应用这个公式时,会得出两个新的 vx 值。记住 vy 的值永远不变。vx 的变化如图11-7 所示。
图 11-7 新的 x 速度,y 速度不变,得出新的速度
猜到下面该怎么办了吗?只需要把一切旋转回去,如图 11-8 所示。
图 11-8 将一切旋转回来
下面把这个过程转换为代码。对我来说最难的地方就是要一再地说服大家“这很容易”。
2.编写代码
首先要让两个小球以一定的角度运动并且最后相互碰撞。开始的设置与前面相同,两个小球实例:ball0 和 ball1。这次让它们变大一点,如图 11-9 所示,这样它们碰撞的机会就会大一些。
图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 阅读次数:10729
