也可以follow catfly围脖:t.sina.com.cn/iscat
fxzjw在天地会发的文章:http://flash.9ria.com/thread-2541-1-1.html
fxzjw的个人博客上有很多不错的flash文章:似是故人来的BLOG
我把学习向量的心得发到这里把,发到blog上看的人太少了,顺便可以让高手指正一下。呵呵,
向量第一课
一、向量的基本介绍:
向量几何在flash图形编程中的地位非常重要,因为在flash程序员的眼中,显示屏幕就是一个坐标系,运动物体的轨迹就是物体在这个坐标系曲线运动结果,而描述这些曲线运动的,就是2D向量及3D向量。使用向量可以很好的模拟物理现象以及基本的AI.
什么是向量?简单来说,向量就是有方向的线段,也叫做矢量。长度为1的向量叫做单位向量。
先来研究一下二维空间的向量,如图

上面的a,b,c,d四个向量的长度和方向相等,故它们都相等:
a=b=c=d=(x,y);(这是代数表示法)
令x方向的单位向量为i,y方向的单位向量为j, (i=(1,0),j=(0,1) |i|=|j|=1;|向量|表示对向量取模,也就是求取长度,必为正值)
则a=x*i+y*j
二、向量的运算:(其中a=(ax,ay),b=(bx,by))

加法:a+b=((ax+bx),(ay+by))
它的几何意义是a向量和b向量首尾相连则在上图中明显看出,以a的始点为始,以b的终点为终的向量就是a+b
减法:a-b=((ax-bx),(ay-by))
它的几何意义就是,两个向量始点重合,从b的终点开始到a的终点结束的向量就是a-b了
和常数相乘:a*k=e=(k*ax,k*ay)
它的几何意义是,乘一个大于0的数,则向量的方向不变,只是在同方向上伸缩。如果乘一个小于0的数,就在反方向伸缩了,是伸是缩,取决于|k|是大于还是小于1了。
点乘:a·b=|a|*|b|*cosα(注意a·b中的点不是数字相乘的意思,α是a和b之间的夹角)
( 附:a·b=(xa * i + ya * j).(xb * i + yb * j)
= xa * i * xb * i + xa * i * yb * j + ya * j * xb * i + ya * j * yb * j
=(xa * xb) * (i * i) + (xa * yb) * (i * j) + (xb * ya) * (i * j) + (ya * yb) * (j * j)= xa * xb + ya * yb )
它的几何意义是,a的长度与b在a上的投影长度的乘积,或者是b的长度与a在b上投影长的乘积,它是一个标量,而且可正可负。因为互相垂直的向量其中 一个在另一个上面的投影为0,所以它们的内积为0(a.b = |a|.|b|*cos(PI/2) = |a|.|b|*0 = 0).注意:点乘得到的是一个值,不是一个向量
至于叉乘,用到的很少,就不说了。
另外,再介绍一点向量的基本性质:
1. a + b = b + a
2. (a + b) + c = a + (b + c)
3. a + 0 = 0 + a = a
4. a + (-a) = 0
5. k*(l*a) = (k*l)*a = a*(k*l)
6. k*(a + b) = k*a + k*b
7. (k + l)*a = k*a + l*a
8. 1*a = a
9. a·b = b·a
10. a·(b + c) = a·b + a·c
11. k*(a·b) = (k*a)·b = a·(k*b)
12. 0·a = 0
13. a·a = |a|^2
好,有了这些,你已经有了大部分的知识,下一次我们就要用它们做一个动画,但是!在此之前,请你确定,你已经把上面的知识掌握了个八九不离十!
————————
向量第二课
那么,在flahs中,什么地方有向量呢?
物体运动的速度,物体的位置,力,障碍物等都可以看成向量,我们还是先来看一个非常简单的例子吧,假设你正在编写一个飞行射击游戏,你的敌人需要一种很厉 害的武器-跟踪导弹,这种武器在行进的同时不断的修正自己与目标之间的位置关系,使得指向的方向总是玩家,而不论玩家的位置在哪里,这对一个水平不高的玩 家(我?)来说可能将是灭顶之灾,玩家可能很诧异敌人会拥有这么先进的秘密武器,但对于你来说只需要在程序循环中加入几行代码。在此之前,首先再学一个向 量的知识。
向量的归一化:
有些时候,我们需要让向量的长度为一个定值,如速度等,这个时候我们就要让向量的长度(模)为1,然后乘上速度的大小。那么怎么让向量的长度为1?
首先你必需先算出向量的长度,再用向量除以长度,即可。
设a=(x,y),则
|a| = |(x,y)| = |x*i + y*j| = sqrt(x*x + y*y),这里sqrt是开平方。
a的单位向量为a/|a|,即(x,y)/sqrt(x*x + y*y)。
首先我们要知道玩家的位置(x_player, y_player),然后,我们的导弹就可以通过计算得到一个有初始方向的速度,速度的方向根据玩家的位置不断修正,(如图)它的实质是一个向量减法的计 算过程。速度的大小我们自己来设置,它可快可慢,视游戏难易度而定,它的实质就是向量单位化和数乘向量的过程。具体算法是:导弹的更新速度 (vx_missile, vy_missile) = 玩家的位置(x_player, y_player) – 导弹的位置(x_missile, y_missile),然后再对(vx_missile, vy_missile)做归一处理,乘上你设置的速度。导弹移动,判断是否追到玩家,重新更新速度,……

var n_missile:Number ; // 这是玩家位置与导弹位置之间向量的长度
var v_rate:Number ; // 这是导弹的速率缩放比率
// 计算一下玩家与导弹之间的位置向量
var xv_missile:Number = x_player-x_missile ; // 向量减法,方向由导弹指向玩家,x分量
var yv_missile:Number = y_player-y_missile ; // y分量
// 计算一下它的长度
n_missile = sqrt( xv_missile*xv_missile + yv_missile*yv_missile ) ;
// 归一化导弹的速度向量:
xv_missile /= n_missile ;
yv_missile /= n_missile ;
// 此时导弹的速率为1,注意用"速率"是因为它没有方向,是值,不是向量。
// 好!现在导弹的速度方向已经被修正,它指向玩家。
// 由于现在的导弹速度太快,为了缓解一下紧张的气氛,我要给导弹减速
var v_rate:Number = 0.2 ; // 减速比率
xv_missile *= v_rate ; // 这里的速率缩放比率,你可以任意调整大小
yv_missile *= v_rate ; // 可以加速:v_rate大于1;减速v_rate大于0小于1,这里就这么做!
// 导弹行进!导弹勇敢的冲向玩家!
x_missile += xv_missile ;
y_missile += yv_missile ;
// 然后判断是否攻击成功
请注意,因为上例只是为了说明向量的一个最简单的应用,所以只是部分代码,另外,有的中级朋友可能要问了,你这个写法不用向量也行啊?为什么要舍简取繁?这个问题啊,呵呵,下回分解!
———————-
第三课
学习了前两课后,大家对向量有了一个初步的认识,这次,我们还是来个简单的应用:小圆在大圆内的碰撞,(为了简化这个问题,我在图中以圆心为坐标原点)如图:

分析一下碰撞的过程,我们发现,要实现这个碰撞的过程,必须要解决两个问题,
1、什么时候发生碰撞?
2、碰撞后小圆如何运动?
第一个问题解决起来非常简单,只要把小球的位置看成一个向量,这个向量的长度(模)大于或等于圆的半径r时,即判断发生了碰撞(实际因为小圆不是一个质点,故还应该考虑它的半径,这个稍后再说)。
第二个问题,解决起来也不难,请看图,就是一个已经知道了速度向量v,要求出反弹后的速度向量v1,只要把速度向量取反,得到vo,再求出l和vo的夹角 α,再让v0旋转2*α(因β=α)不就搞定了么?现在可能有的朋友要问了,你说的简单,那么写成代码怎么写啊,又是一大堆吧?
这里我们就发现一个问题,我们经常用到向量的一些相关知识,比如说取长度,取反(求逆)相加相减,求夹角,等等,那么,我们为什么不写成一个向量的类呢? 比如说写一个Vector.as的类文件,其中有上面各种计算的方法(这个类文件下载地址在后面),那么比如说我要新建一个速度向量v,水平速度为2,竖 直速度为3,就可以:
var v:Vector=new(2,3);
求其模就可以:
v.getLength();//getLength()是Vector类中的一个方法
求其与x轴正向的夹角就可以:
v.getAngle();//getAngle()也是Vector类中的一个方法
好了,下面就说一下怎么写这个小球在圆内碰撞的AS了,首先,我新建了两个MC,ball_mc(小球的实例名)circle(外圆的实例名),在主时间轴上附加如下代码(也可用文档类,但我觉得东西不多就懒得写)
import Vector;
circle.x=stage.stageWidth/2;
circle.y=stage.stageHeight/2;
//速度向量,你可以随便改之
var v:Vector=new Vector(8,4);
//小球位置所表示的向量
var v_ball:Vector=new Vector(2,50);
var circen:Vector=new Vector(circle.x,circle.y);
stage.addEventListener(Event.ENTER_FRAME,enterhandler);
function enterhandler(ev:Event) {
v_ball.plus(v);//plus是Vector类中的一个方法,用来将两个向量相加
render(ball_mc,v_ball);
//让小球按速度开始运动
if (v_ball.getLength()>=(circle.width-ball_mc.width)/2) {
//如果小球的位置向量大于两球的半径之差的,实际就是说小球和大圆发生了碰撞
v_ball.setLength((circle.width-ball_mc.width)/2);
render(ball_mc,v_ball);
//让v_ball向量的长度正好为两球的半径之差的一半,再刷新,就让小球正好碰到大圆的边缘
v.negate();
//取反
var ang1:Number=v_ball.getAngle();
var ang2:Number=v.getAngle();
//求出的这两个角之差就是上图的α
v.rotate(2*(ang1-ang2));
//让取反后的速度向量旋转两倍的α就得到新的速度向量
}
}
//当v_ball改变时,刷新小球的位置
function render(a:MovieClip,b:Vector) {
var temp:Vector=b.plusNew(circen);
a.x=temp.xV;
a.y=temp.yV;
}
上面的代码,如果在不加注释时,仅为20行。应该算是简单明快了吧
小球碰撞.swf (1.74 KB)
Vector类下载:
Vector.rar (1.28 KB)
———————–
第四课
平面内物体任意角度的反弹
根据初等物理,相互接触的物体在受到外力具有接触面相对方向相对运动趋势的时候,接触面会发生形变从而产生相互作用的弹力。弹力使物体形变或形变同时运动形式发生改变。在知道了这件事情之后,我们开始具体讨论下面这种情况:

小球和矩形框碰撞,碰撞时间极短,墙面无限光滑从而碰撞过程没有摩擦,碰撞时间极短,没有能量损失…总之是一个理想的物理环境。我们在这种理想环境 下讨论,小球与墙面发生了完全弹性碰撞,且反射角和入射角相等:∠2=∠1, ∠4 =∠3, ∠6 =∠5,…。虚线是法线,它和墙面垂直。小球将在矩形框中永无休止的碰撞下去,且每次碰撞过程中入射角和反射角都相等。
我们再具体点,现在假设上面那个矩形墙壁的上下面平行于x轴,左右面平行于y轴。这样太好了,我们在编写程序的时候只要判断当球碰到上下表面的时候将y方 向速度值取反,碰到左右表面时将x方向速度值取反就行了,这种方法常常用在简单物理模型和规则边界框的游戏编程上,这样可以简化很多编程步骤,编写简单游 戏时可以这样处理。可事实不总是像想向中的那么好。如果情况像下面这样:

虽然在碰撞过程中入射角仍然等于反射角,但是边界的角度可没那么“纯”了,它们的角度是任意的,这样就不能简单的将x方向或者y方向的速度取反了,我们要另找解决办法。
在此之前,我们先要复习下前面的知识:
什么叫点积?

看上图,a.b =|a|*|b|*cosβ 也就是说等于a向量长度乘以b向量的长度再乘以夹角的余弦
几何意义:a和b的点积等于a在b上的投影长d的长度乘以b的长度。
那么,我们现在要求a在b上的投影向量c怎么求?
那我们先要求a在b上的投影长,然后再乘上b方向上的单位向量b1
a在b上的投影长,可以用a点乘b的单位向量b1就可以了,因为单位向量的长度为1,a的投影长|a|乘上1还等于投影长自身,即:
|d|=a.b1
好,我们得到了d的投影长,现在就可以求出d:
d = |d|*a1= a.b1*a1
总结一下,就是d = (a.b1)* b1。
用向量类来写一下伪代码:
取得b的单位向量:b1=b.setLength(1);
得到d的长度: |d|= a.b1
得到d向量:d=b1.setLength(|d|)
再看一下,用a与b的同模相反向量c来点积,即:
a.c =|a|*|c|*cosα
很明显,由于α与β之和为π所以cosα与cos互为相反数
故:
a.c =|a|*|c|*cosα= -|a|*|b|*cosβ= -a.b
又由于b和c是同模相反向量:b=-c
也就是可以得出结论:
d = (a.b1)* b1→
d = -(a.c1)* -c1
由此得出结论:
要求出a 向量在另一向量b上的投影向量,可用a向量和b的单位向量b1点积再乘以b1,并且与另一向量b的方向无关。
好了,我们现在来研究一下,怎么利用向量来解决不规则碰撞的问题.
我们现在的任务是:已知物体的速度向量S和边界向量b,求它的反射向量F。我们先来看一下在碰撞过程中都有哪些向量关系,如图:

设b是障碍向量,S是入射速度向量,F是反射速度向量,也就是我们要计算的向量。N是b的法向量,即N垂直于b。n是与N共线的向量,n’是N方向的单位向量。T是垂直于N的向量。根据向量加法,现在有关系:
(1) S + n = T
(2) n + T = F
合并,得
F = 2*T – S
我们已经找到了计算F的公式了。这里S是已知的,我们要计算一下T,看(1)式:
T = S + n
要计算T,S是已知的,就要计算一下n。我们知道,n是S在N方向上投影得到的,S已知所以要得到n就要再计算一下N,而N又是和b垂直的,我们可以利用向量的旋转方法,让b旋转90度,就可以得到N。另外还记得刚才我们导出的使用向量的技巧吧,这里我们要用到:
要求s 向量在另一向量N上的投影向量,可用s向量和N的单位向量n1点积再乘以n1,并且与另一向量N的方向无关。
我们知道了N。利用向量类的setLength(1)计算出n1,再用上述技巧要使s和n1的起点相同,必须要使对S取反,然后用 n = ( -s.n1 ) * n1,这样就计算出了n。然后根据上面的(1)式计算出T,好了,有了T和F = 2*T – S ,一切OK!
计算出的F就是物体碰撞后的速度向量,在2-D中它有两个分量x和y,3-D中有x,y,z三个分量。这里也证明了使用向量的一个好处就是在一些类似这样关系推导过程中不用去考虑坐标问题,而直接的用简单的向量就可以进行。
这里注意我们的障碍向量b在实际的编程中是用障碍的两个端点坐标相减计算出的,计算的时候不需要考虑相减的顺序问题。因为虽然用不同的相减顺序得到b的方向相反,且计算得到的单位法向量n1方向也相反(看上图的虚线部分),但是由前述的技巧可知,这个无关紧要。
那么,什么时候物体和边界碰撞,又怎么把上述知识用到flash中?欲知后事如何,且听下回分解:
———————-
第五课
2D边界碰撞检测
一、使用向量进行障碍检测的原理
上次说了使用向量模拟任意角度的反弹,这次谈谈它的前提—障碍碰撞。
在flsah中进行障碍碰撞检测,基本思路是这样的:给定一个障碍范围,判断物体在这次移动后会不会进入这个范围,如果会,就发生碰撞,否则不发生碰撞。 在实际操作中,一般是用物体的边界来判断。这时候,就可以从物体的位置沿着速度的方向引出一条速度向量线,判断一下这条线段(从检测部位到速度向量终点) 和障碍边界线有没有交点,如果有,这个交点就是碰撞点。

上面物体A,在通过速度向量移动之后将到达B位置。但是,这次移动将不会顺利进行,因为我们发现,碰撞发生了。碰撞点就在那个红色区域中,也就是速度向量和边界线的交点。我们接下来的工作就是要计算这个交点,这是一个解线性方程组的过程,那么我们将要用到一样工具…
二、一个解线性方程组的有力工具—克兰姆(Cramer)法则
首先要说明一下的是,这个法则是有局限性的,它必须在一个线性方程组的系数行列式非零的时候才能够使用。别紧张,我会好好谈谈它们的。首先让我来叙述一下这个法则(我会试着让你感觉到这不是一堂数学课):
如果线性方程组:
A11*X1 + A12*X2 + … + A1n*Xn = b1
A21*X1 + A22*X2 + … + A2n*Xn = b2
……………………………..
An1*X1 + An2*X2 + … + Ann*Xn = bn
的系数矩阵 A =
| A11 A12 … A1n |
| A21 A22 … A2n |
| …………… |
| An1 An2 … Ann |
的行列式 |A| != 0
线性方程组有解,且解是唯一的,并且解可以表示为:
X1 = d1/d , X2 = d2/d , … , Xn = dn/d (这就是/A/=d为什么不能为零的原因)
这里d就是行列式|A|的值,dn(n=1,2,3…)是用线性方程组的常数项b1,b2,…,bn替换系数矩阵中的第n列的值得到的矩阵的行列式的值,即:
| b1 A12 … A1n |
d1 = | b2 A22 … A2n |
| ………….. |
| bn An2 … Ann |
| A11 b1 … A1n |
d2 = | A21 b2 … A2n |
| ………….. |
| An1 bn … Ann |
…
| A11 A12 … b1 |
dn = | A21 A22 … b2 |
| ………….. |
| An1 An2 … bn |
别去点击关闭窗口按钮!我现在就举个例子,由于我们现在暂时只讨论2D的情况(3-D以后会循序渐进的谈到),先来个2D线性方程组示例一下:
(1) 4.0*X1 + 2.0*X2 = 5.0
(2) 3.0*X1 + 3.0*X2 = 6.0
这里有两个方程,两个未知量,则根据上面的Cramer法则:
| 4.0 2.0 |
d = | 3.0 3.0 | = 4.0*3.0 – 2.0*3.0 = 6.0 (2阶行列式的解法,’'对角线相乘减去’/'对角线相乘)
| 5.0 2.0 |
d1 = | 6.0 3.0 | = 5.0*3.0 – 2.0*6.0 = 3.0
| 4.0 5.0 |
d2 = | 3.0 6.0 | = 4.0*6.0 – 5.0*3.0 = 9.0
则
X1 = d1/d = 3.0/6.0 = 0.5
X2 = d2/d = 9.0/6.0 = 1.5
好了,现在就得到了方程组的唯一一组解。
是不是已经掌握了用Cramer法则解2-D线性方程组了?如果是的话,我们继续。
三、深入研究
这里的2-D障碍碰撞检测的实质就是判断两条线段是否有交点,注意不是直线,是线段,两直线有交点不一定直线上的线段也有交点。现在我们从向量的角度,写出两条线段的方程。

现在有v1和v2两条线段,则根据向量加法:
v1e = v1b + s*v1
v2e = v2b + t*v2
v1b和v2b分别是两线段的一端。s,t是两个参数,它们的范围是[0,1],当s,t=0时,v1e=v1b,v2e=v2b;当s,t=1时,v1e和v2e分别是两线段的另一端。s,t取遍[0,1]则v1e和v2e取遍两线段的每一点。
那么我们要判断v1和v2有没有交点,就让v1e=v2e,看解出的s,t是不是在范围内就可以了:
v1e = v2e
=> v1b + s*v1 = v2b + t*v2
=> s*v1 – t*v2 = v2b – v1b
写成分量形式:
s*x_v1 – t*x_v2 = x_v2b – x_v1b
s*y_v1 – t*y_v2 = y_v2b – y_v1b
现在是两个方程式,两个未知数,则根据Cramer法则:
| x_v1 -x_v2 | | 4.0 -2.0 |
d = | y_v1 -y_v2 | = | 1.0 -3.0 | = -10.0
| x_v2b-x_v1b -x_v2 | | 5.0 -2.0 |
d1 = | y_v2b-y_v1b -y_v2 | = | 2.0 -3.0 | = -11.0
s = d1/d = -11.0/-10.0 = 1.1 > 1.0
现在s已经计算出来,没有在[0.0,1.0]内,所以两线段没有交点,从图上看很直观。t没有必要再计算了。所以是物体与障碍没有发生碰撞。如果计算出的s,t都在[0,1]内,则把它们带入原方程组,计算出v1e或者v2e,它的分量就是碰撞点的分量。
四、理论上的东西已经够多的了,开始写程序
我现在要写一个用于处理障碍碰撞检测的函数,为了测试它,我还准备安排一些障碍:

这是一个凸多边形,我让一个质点在某一位置,然后给它一个随机速度,同时检测是否与边界发生碰撞。当碰撞发生时,反弹,并计算它的反弹速度,继续……
——————
第六课
code:(注意:下面的代码可以直接复制到主时间轴上测试,故不再附上swf文件)
import Vector;
var sp:Shape=new Shape();
var matrix:Matrix = new Matrix();
matrix.createGradientBox(40, 40, Math.PI/4, -20,-30);
sp.graphics.beginGradientFill("radial",[0xff0000,0xff0000],[0,1],[0,255],matrix);
sp.graphics.drawCircle(0,0,20);
sp.graphics.endFill();
addChild(sp);
sp.x=sp.y=100;
//初速度
var v:Vector=new Vector(0,10);
//障碍坐标
var arr:Array=[20,80,100,20,250,40,450,200,200,350,40,250,20,80];
//把边界给画出来
drawBorder(arr,this);
stage.addEventListener(Event.ENTER_FRAME,enterhandler);
function enterhandler(event:Event):void {
sp.x+=v.xV;
sp.y+=v.yV;
//创建一个小球位置的向量
var v1:Vector=new Vector(sp.x,sp.y);
for (var j:int=0; j<arr.length/2-1; j++) {
var l1:Vector=new Vector(arr[j*2],arr[j*2+1]);
var l2:Vector=new Vector(arr[j*2+2],arr[j*2+3]);
//注意:小球并不是一个质点,所以要把障碍向量向中心移动小球半径的值
var l:Vector=l2.minusNew(l1);
var N:Vector=l.rotateNew(Math.PI/2);
N.setLength(sp.width/2);
//利用克兰姆(Cramer)法则来解线性方程组
var d:Number=v.xV*(-l.yV)-(-l.xV*v.yV);
var d1:Number=((l1.xV+N.xV-v1.xV)*-l.yV)-
(-l.xV*(l1.yV+N.yV-v1.yV));
var d2:Number=v.xV*(l1.yV+N.yV-v1.yV)-
v.yV*(l1.xV+N.xV-v1.xV);
if (Math.abs(d)<0.001) {
d=0.001;
}
var t:Number=d1/d;
var s:Number=d2/d;
if (t>=0&&t<=1&&s>=0&&s<=1) {
//当t和s均在0和1之间时,向量l和v相交
var dv:Vector=v.scaleNew(t);
sp.x+=dv.xV;
sp.y+=dv.yV;
//速度向量
//var f:Vector=new Vector(1,1);
//速度向量反方向的向量
//var f1:Vector=f.negateNew();
var vNeg:Vector=v.negateNew();
//障碍向量
//var l:Vector=new Vector(-3,-1);
//旋转障碍向量得到法向量
var n1:Vector=l.rotateNew(Math.PI/2);
//向量长度归一
n1.setLength(1);
//得到速度向量在法向量上的投影长
var dotv:Number=vNeg.dot(n1);
//获得和法向量相同,但长度为投影的一个向量
n1.setLength(dotv);
n1.scale(2);
v.plus(n1);
}
}
}
function drawBorder(a:Array,b:Sprite):void {
b.graphics.lineStyle(2,0x000000);
b.graphics.moveTo(a[0],a[1]);
for (var i:int=1; i<a.length/2; i++) {
this.graphics.lineTo(a[i*2],a[i*2+1]);
}
}
现在你就可以结合上次的讨论模拟一个完整的理想物理情景:一个物体在不规则障碍中移动、反弹,永不停息…除非…
至此为止我们讨论了2-D游戏的障碍碰撞检测以及它的编程实现,在此过程中涉及到了线性代数学的知识,以后随着深入还会不断的加入更多的数学、物理知识。下次我们继续讨论,BYE!
——————–
接着是两球碰撞
2-D物体间的碰撞响应(首先说一点,这里所有的代码不用向量都可以写出,但用了向量可以使你思考问题时更能接近于实际,容易理解)
这次我要分析两个球体之间的碰撞响应,这样我们就可以结合以前的知识来编写一款最基本的2-D台球游戏了,虽然粗糙了点,但却是个很好的开始,对吗?
一、初步分析
中学时候上物理课能够认真听讲的人(我?哦,不包括我)应该很熟悉的记得:当两个球体在一个理想环境下相撞之后,它们的总动量保持不变,它们的总机械能也守恒。但这个理想环境是什么样的呢?理想环境会不会影响游戏的真实性?对于前者我们做出在碰撞过程中理想环境的假设:
1)首先我们要排除两个碰撞球相互作用之外的力,也就是假设没有外力作用于碰撞系统。
2)假设碰撞系统与外界没有能量交换。
3)两个球体相互作用的时间极短,且相互作用的内力很大。
有了这样的假设,我们就可以使用动量守恒和动能守恒定律来处理它们之间的速度关系了,因为1)确保没有外力参与,碰撞系统内部动量守恒,我们就可以使用动 量守恒定律。2)保证了我们的碰撞系统的总能量不会改变,我们就可以使用动能守恒定律。3)两球发生完全弹性碰撞,不会粘在一起,没有动量、能量损失。
而对于刚才的第二个问题,我的回答是不会,经验告诉我们,理想环境的模拟看起来也是很真实的。除非你是在进行科学研究,否则完全可以这样理想的去模拟。

现在,我们可以通过方程来观察碰撞前后两球的速度关系。当两球球心移动方向共线(1-D处理)时的速度,或不共线(2-D处理)时共线方向的速度分量满足:
(1)m1 * v1 + m2 * v2 = m1 * v1′ + m2 * v2′ (动量守恒定律)
(2)1/2 * m1 * v1^2 + 1/2 * m2 * v2^2 = 1/2 * m1 * v1′^2 + 1/2 * m2 * v2′^2 (动能守恒定律)
这里m1和m2是两球的质量,是给定的,v1和v2是两球的初速度也是我们已知的,v1′和v2′是两球的末速度,是我们要求的。好,现在我们要推导出v1′和v2′的表达式:
由(1),得到v1′ = (m1 * v1 + m2 * v2 – m2 * v2′) / m1,代入(2),得
1/2 * m1 * v1^2 + 1/2 * m2 * v2^2 = 1/2 * m1 * (m1 * v1 + m2 * v2 – m2 * v2′)^2 + 1/2 * m2 * v2′^2
=> v2′ = (2 * m1 * v1 + v2 * (m2 – m1)) / (m1 + m2),则
=> v1′ = (2 * m2 * v2 + v1 * (m1 – m2)) / (m1 + m2)
我们现在得到的公式可以用于处理当两球球心移动方向共线(1-D处理)时的速度关系,或者不共线(2-D处理)时共线方向的速度分量的关系。不管是前者还是后者,我们都需要把它们的速度分解到同一个轴上才能应用上述公式进行处理。
二、深入分析
首先我要说明一件事情:当两球碰撞时,它们的速度可以分解为球心连线方向的分速度和碰撞点切线方向的分速度。而由于它们之间相互作用的力只是在切点上,也 就是球心连线方向上,因此我们只用处理这个方向上的力。而在切线方向上,它们不存在相互作用的力,而且在理想环境下也没有外力,因此这个方向上的力在碰撞 前后都不变,因此不处理。好,知道了这件事情之后,我们就知道该如何把两球的速度分解到同一个轴上进行处理。

现在看上面的分析图,s和t是我们根据两个相碰球m1和m2的位置建立的辅助轴,我们一会就将把速度投影到它们上面。v1和v2分别是m1和m2的初速 度,v1′和v2′是它们碰撞后的末速度,也就是我们要求的。s’是两球球心的位置向量,t’是它的逆时针正交向量。s1是s’的单位向量,t1是t’的 单位向量。
我们的思路是这样的:首先我们假设两球已经相碰(在程序中可以通过计算两球球心之间的距离来判断)。接下来我们计算一下s’和t’,注意s’和t’的方向 正反无所谓(一会将解释),现在设m1球心为(m1x, m1y),m2球心为(m2x, m2y),则s’为(m1x-m2x, m1y-m2y),t’为s’.rotateNew(PI/2)
再利用setLength(1)可以将两个向量归一,就得到了s和t的单位向量s1,t1.
现在s和t轴的单位向量已经求出了,我们根据向量点乘的几何意义,计算v1和v2在s1和t1方向上的投影值,然后将s轴上投影值代入公式来计算s方向碰 撞后的速度。注意,根据刚才的说明,t方向的速度不计算,因为没有相互作用的力,因此,t方向的分速度不变。所以我们要做的就是:把v1投影到s和t方向 上,再把v2投影到s和t方向上,用公式分别计算v1和v2在s方向上的投影的末速度,然后把得到的末速度在和原来v1和v2在t方向上的投影速度再合 成,从而算出v1′和v2′。好,我们接着这个思路做下去:
先算v1(v1x, v1y)在s和t轴的投影值,分别设为v1s和v1t:
v1s = v1.s1
v1t = v1.t1
再算v2(v2x, v2y)在s和t轴的投影值,分别设为v2s和v2t:
v2s = v2.s1
v2t = v2.t1
接下来用公式
=> v2′ = (2 * m1 * v1 + v2 * (m2 – m1)) / (m1 + m2),则
=> v1′ = (2 * m2 * v2 + v1 * (m1 – m2)) / (m1 + m2)
计算v1s和v2s的末值v1s’和v2s’,重申v1t和v2t不改变:
好,下一步,将v1s’和v1t再合成得到v1′,将v2s’和v2t再合成得到v2′,我们用向量和来做:
首先求出v1t和v2t在t轴的向量v1t’和v2t’(将数值变为向量)
v1t’ = v1t * t1
v2t’ = v2t * t1
再求出v1s’和v2s’在s轴的向量v1s’和v2s’(将数值变为向量)
v1s’= v1s’ * s1
v2s’= v2s’ * s1
最后,合成,得
v1′ = v1t’ + v1s’
v2′ = v2t’ + v2s’
从而就求出了v1′和v2′。下面解释为什么说s’和t’的方向正反无所谓:不论我们在计算s’时使用m1的球心坐标减去m2的球心坐标还是相反的相减顺 序,由于两球的初速度的向量必有一个和s1是夹角大于90度小于270度的,而另外一个与s1的夹角在0度和90度之间或者说在270度到360度之间, 则根据向量点积的定义|a|*|b|*cosA,计算的到的两个投影值一个为负另一个为正,也就是说,速度方向相反,这样就可以用上面的公式区求得末速度 了。同时,求出的末速度也是方向相反的,从而在转换为v1s’和v2s’时也是正确的方向。同样的,求t’既可以是用s’逆时针90度得到也可以是顺时针 90度得到。
三、编写代码
按照惯例,该编写代码了,其实编写的代码和上面的推导过程极为相似。但为了完整,我还是打算写出来(在舞台上画了两个小球MC,分别是mc1,mc2)。
import Vector;
var xx:Number=275;
var yy:Number=200;
//两球的速度
var v1:Vector=new Vector(3,3);
var v2:Vector=new Vector(0,-3);
//两球的位置,随便定的,关键是两球要能碰上
var mc1Pos:Vector=new Vector(165,110);
var mc2Pos:Vector=new Vector(260,310);
//两球的质量
var mass1:Number=1;
var mass2:Number=1;
var rr:Number=(mc1.width+mc2.width)/2;
//记录两小球之间连线向量
var s:Vector;
//小球碰撞时的公切线及垂线为辅助坐标系
var s1:Vector=new Vector(0,0);
var t1:Vector=new Vector(0,0);
//设置小球位置
render(mc1,mc1Pos);
render(mc2,mc2Pos);
stage.addEventListener(Event.ENTER_FRAME,enterHandler);
function enterHandler(e:Event):void {
s=mc1Pos.minusNew(mc2Pos);
mc1Pos.plus(v1);
mc2Pos.plus(v2);
render(mc1,mc1Pos);
render(mc2,mc2Pos);
s1=mc1Pos.minusNew(mc2Pos);
if (s1.getLength()<rr) {
var k:Number=(rr-s1.getLength())/(s.getLength()-s1.getLength())
mc1Pos.minus(v1.scaleNew(k));
mc2Pos.minus(v2.scaleNew(k));
render(mc1,mc1Pos);
render(mc2,mc2Pos);
s1.setLength(1);
t1=s1.rotateNew(Math.PI/2);
var v1s:Number=v1.dot(s1);
var v1t:Number=v1.dot(t1);
var v2s:Number=v2.dot(s1);
var v2t:Number=v2.dot(t1);
var vs:Object=getSpeed(mass1,mass2,v1s,v2s);
var v1s1:Vector=s1.scaleNew(vs.v1);
var v2s1:Vector=s1.scaleNew(vs.v2);
var v1t1:Vector=t1.scaleNew(v1t);
var v2t1:Vector=t1.scaleNew(v2t);
v1=v1s1.plusNew(v1t1);
v2=v2s1.plusNew(v2t1);
}
}
//利用两物的质量和速度根据动量和能量守恒计算出碰撞后的两物速度
function getSpeed(m1:Number,m2:Number,v1:Number,v2:Number):* {
var v1New:Number = (2*m2*v2+v1*(m1-m2))/(m1 + m2);
var v2New:Number = (2*m1*v1+v2*(m2-m1))/(m1 + m2);
return {v1:v1New,v2:v2New};
}
//刷新位置
function render(a:Sprite,b:Vector) {
a.x=b.xV;
a.y=b.yV;
}
呼~~是不是感觉有点乱阿?不管怎么样,我有这种感觉。但我们确实完成了它。希望你能够理解这个计算的过程,你完全可以依照这个过程自己编写更高效的代码,让它看上去更清楚:)至此位置,我们已经掌握了编写一个台球游戏的基本知识了,Let’s make it!
事实上,一切才刚刚起步,我们还有很多没有解决的问题,比如旋转问题,击球的角度问题等等,你还会深入的研究一下,对吗?一旦你有了目标,坚持下去,保持激情,总会有成功的一天:)这次就到这里,下次我们接着研究,Bye for now~~
源文件:
两小球碰撞的研究.rar (6.95 KB)






