转:http://school.ogdev.net/ArticleShow.asp?id=6638&categoryid=3
人物移动控制是单机和网游中比较重要的部分,但前单机游戏使用动力学以及IK动画等已经达到了非常逼真的地步,在大型网络游戏中这样的物理模拟同步是很实现的,因此在目前多数网游中仍旧是采取使用一个包围体(盒子或者胶囊)来模拟人物。一个好的移动系统是很重要的,平滑的贴墙滑动以及下滑,跳跃等会带给玩家顺畅的手感否则则会有种奇怪的感觉,本文具体介绍了一下碰撞反应,包括贴墙滑动等的具体实现细节。包括一个demo实例 。
目前物理引擎里面大多自带独立于刚体的人物角色控制,但是物理引擎需要特定的物理模型命名以及比较大的物理模拟开销度。如果需要定制自己的特别功能或者需要简化计算(同时模拟多个延迟或者玩家的反应)。就必须自己完成人物碰撞反应控制的代码。
要完成人物发生碰撞以后的行为控制,需要碰撞检测系统提供以下的碰撞信息,对于每一个碰撞:
1. 碰撞发生的时间
2. 碰撞的法向量
3. 碰撞点
对于基本的人物碰撞控制反应来说,以上3点是必须的,有时还需要提供和包围体发生碰撞的具体三角形信息。
对于场景上的物体首先使用胶囊所在的包围盒AABB或者OBB和场景中的碰撞体包围盒作层次碰撞裁减,至于具体怎么组织可以任意,比如可以采用AABB或者OBB树,也可以采用简单的球树。但碰撞进行到树的叶子节点后开始检测人物的AABB盒和该AABB盒所包围的OBJECT的碰撞情况。如果发现这2个AABB(OBB)盒将会发生碰撞,那么开始使用人物的胶囊体和景物所带的三角面片进行精确到polygon soup级别的比较。这时候仍旧可以优化,比如说我还做了一步把一个Object中的三角形面片打成BSP树的形式存储起来,这样可以大大减少胶囊和三角形碰撞检测的次数,因为这种动态检测是十分耗时的。有关胶囊和三角形面片的比较可以参考:http://dev.gameres.com/Program/Abstract/Arithmetic/capsule.mht中的方法。 对于BSP的划分以及AABB碰撞检测就不用多说了~到处都可以找到文章。
对于地形而言,也是采用同样的方法,只不过对于地形而言三角形信息不用额外存储,只需要使用和渲染相同的三角形(对于景物来说一般不会使用渲染用的三角形而会使用更加简化数量更少的简化网格碰撞模型)。这里可以有很多优化的技巧,因为地形本身是规则的cell一个地形是由若干个patch(一般是16X16)组成的,而每个patch是由若干cell(一般是16X16)组成的。对于patch来说一般已经组织到了一颗QUADTREE中了因为视棱锥裁减也需要这种结构,因此碰撞检测中的AABB-AABB阶段使用这颗已经存在的QUADTREE就可以快速的完成层次碰撞检测了。但发现某个patch中的AABB和人物的AABB发生碰撞后需要检测每一个CELL所在的AABB和人物AABB盒的碰撞,这里可以使用点小技巧比如说首先将AABB盒投影到CELL所在的XY平面上,找出被投影覆盖的那些CELL然后再检测这些CELL的AABB盒是否和人物的发生碰撞。一但确定了某个CELL和人物发生碰撞那么就可以将该CELL中的三角形取出(一般为2个)依次和人物所在的胶囊进行三角形-胶囊的碰撞检测。
这样当碰撞检测系统完成任务以后我们将会获得一个碰撞信息的数组:
class CollideInFo{
public:
GFVECTOR_3D m_worldcdnorm;//碰撞法向量
GFPOINT_3D m_worldpoint;//碰撞点
float m_cdtime;//碰撞时间
};
CollideInFo collidearray[];
然后使用这个数组就可以进行碰撞后的处理了包括,沿墙滑动下滑等等。在具体说明整个人物移动控制算法之前,首先说下动态碰撞检测和静态碰撞检测的区别,动态碰撞检测是指物体A以速度V前进了T时间,在这期间第一次和物体B发生碰撞的时间。这样的碰撞检测必须返回第一次2个物体发生碰撞的时间。而静态检测是指2个不动的物体是否互相相交对于这种检测是不需要返回时间的。动态检测算法比静态的复杂而且也耗用更多的时间。一个完善的碰撞系统需要解决以上2种碰撞检测,如果你不想自己写检测代码,目前比较流行的有OPCODE,SOLID库等检测库 。你可以直接使用他们提供的功能,这里我采用的是自己写检测代码的方法,目前只用到三角形-胶囊,AABB-AABB,OBB-OBB的碰撞检测。 [Page]
完成了包围体(用的是胶囊)和三角形的碰撞,胶囊和BSP,地形的碰撞检测之后,拥有了碰撞的信息 1。碰撞时间。2。碰撞法向量。3。碰撞点。接着就可以处理人物在碰撞后的反应了。
首先人物的一次移动分2个阶段,第一个是初始阶段,使用静态碰撞检测获得该阶段的速度(具体做法在后面说)。第2阶段使用该速度去做动态碰撞检测得到碰撞信息,根据这些碰撞信息去处理碰撞后的反应。
先来看第一阶段,过程对于一个胶囊我们需要获取他周围的临近面片的信息,以决定这个胶囊目前所处平面的倾斜度,是否贴着不可通过的墙等等。我采用的方法是将胶囊体略为膨胀一些,然后调用静态碰撞检测的代码获取和该膨胀后的胶囊体相交的三角形面片碰撞信息如图:
棕色的是原始的胶囊体,红色的表示将胶囊半径略为增加以后的胶囊体,蓝色的2个地形是将胶囊膨胀以后所发生相交的2个三角形,而如果不采用膨胀的话该胶囊是不和任何三角形相交的,具体膨胀数值可以设为胶囊下落的最小高度,比如你设定胶囊离底部物体超过0.6单位属于腾空状态的话你就将膨胀数值设为0.6。在这个例子中我们将会检测到2个碰撞(蓝色部分)这2个碰撞法向量正好是这2个三角面的法向量(在很多情况下也可能不是这个看你的碰撞代码中法向量是如何计算的了)。其中底部的那个是可行走平面,另外一个是不可行走平面,有了这2个碰撞平面就很简单了,如果用户输入的速度和那个不可行走的平面相反(也就是撞向那个不可行走平面),那么那个不可行走平面是有效的,这样他和底部那个可行走的平面所组成的交线就是人物的初始速度,如果用户输入的速度和那个不可行走的平面法向量相同,那么这个不可行走平面没有作用人物最终的速度就是把用户速度投影到该可行走平面上的速度。
0顶一下
具体计算是否能够水平移动以及移动速度的算法:当给出M个不可行走平面和N个可行走平面时:
1首先将速度在N个可行走平面上分解,检查这些分解的速度是否有效(如何判断速度有效下面会说道)
2如果在1的时候存在一个有效的速度直接返回该速度
3没有有效速度,这时检查NXM个平面的交线,将速度在上面分解同时检查是否存在有效速度
4如果存在有效速度返回该速度
5否则检查MXM条交线的,将速度在上面分解同时检查是否存在有效速度
6如果存在有效速度返回该速度
7否则返回0速度
那么如何判定速度是否有效呢,首先我们知道了所有碰撞的信息,给定一个速度,如果该速度和所有碰撞的法向量的夹角都是小于90度那么这就是个有效速度,(说明该分解后速度不会引起和这些碰撞面的在一次碰撞,因为该速度是将物体拉下远离该平面的方向的)。如果只要有一个夹角大于90度那么该速度就是非有效速度,同时在移动时还要判断该速度的倾斜角是否大于最大下滑倾斜角。注意 5是很重要的因为2个不可行走的平面所形成的交线仍旧可能是可以行走的,甚至是水平的。
以上的部分是检查是否能够水平移动的,如果不能水平移动那么该物体会下滑,1如果没有检测到碰撞平面说明物体处于腾空状态,这时候给物体加上重力加速度,产生一个往下的速度和原来的水平速度结合起来(如果存在)。
如图棕色是原始状态胶囊经过上一帧移动后到达蓝色的位置这时通过上诉算法可以检测到胶囊腾空,这时他的速度为水平速度(红色)+下滑速度(绿色)。
2如果检测到碰撞平面但是平面以及它们的交线都是不可行走的(倾斜角大于可行走角度)那么依次将当前速度在碰撞平面分解,以及检测每一条可能下滑的交线。得出速度后检测是否是有效速度具体过程和前面检查水平速度时的方法一样,只是将速度投影到平面上的方法不一样具体方法可以根据程序需要,我这里采用首先去掉原始速度在碰撞平面法线的分量,然后将速度分解为2个速度一个是贴着平面水平移动的速度另外一个是贴着平面下滑的速度。注意如果上一帧更新后物体处于下滑状态那么当前速度就因该是该下滑速度,否则则是用户输入的速度。
如果用户输入的速度是跳跃也就是带Z分量的速度那么,计算初始速度这一步需要略过直接输入给下一阶段用户起跳的速度。
阶段2计算初始速度引起的碰撞
对于多数情况来说,计算完初始速度就不会再发生碰撞了,一旦发生,那么我们依旧传入碰撞面的那些法向量,碰撞点的信息,同样采用前面计算初始速度时所采用的方法,计算出碰撞后的调整速度。 [Page]
整个处理过程基本上就是这样的,其中可能还会出现一些小问题比如说误差控制等。总结一条就是人物行走要么研着平面分解速度要么沿着2个平面的交线行走或者下滑。这个是Demo示例画面(由于没有人帮我做动画。。所以demo中目前只有行走动画,没有起跳,下落等动画。。)
人物移动控制是单机和网游中比较重要的部分,但前单机游戏使用动力学以及IK动画等已经达到了非常逼真的地步,在大型网络游戏中这样的物理模拟同步是很实现的,因此在目前多数网游中仍旧是采取使用一个包围体(盒子或者胶囊)来模拟人物。一个好的移动系统是很重要的,平滑的贴墙滑动以及下滑,跳跃等会带给玩家顺畅的手感否则则会有种奇怪的感觉,本文具体介绍了一下碰撞反应,包括贴墙滑动等的具体实现细节。包括一个demo实例 。
目前物理引擎里面大多自带独立于刚体的人物角色控制,但是物理引擎需要特定的物理模型命名以及比较大的物理模拟开销度。如果需要定制自己的特别功能或者需要简化计算(同时模拟多个延迟或者玩家的反应)。就必须自己完成人物碰撞反应控制的代码。
要完成人物发生碰撞以后的行为控制,需要碰撞检测系统提供以下的碰撞信息,对于每一个碰撞:
1. 碰撞发生的时间
2. 碰撞的法向量
3. 碰撞点
对于基本的人物碰撞控制反应来说,以上3点是必须的,有时还需要提供和包围体发生碰撞的具体三角形信息。
对于场景上的物体首先使用胶囊所在的包围盒AABB或者OBB和场景中的碰撞体包围盒作层次碰撞裁减,至于具体怎么组织可以任意,比如可以采用AABB或者OBB树,也可以采用简单的球树。但碰撞进行到树的叶子节点后开始检测人物的AABB盒和该AABB盒所包围的OBJECT的碰撞情况。如果发现这2个AABB(OBB)盒将会发生碰撞,那么开始使用人物的胶囊体和景物所带的三角面片进行精确到polygon soup级别的比较。这时候仍旧可以优化,比如说我还做了一步把一个Object中的三角形面片打成BSP树的形式存储起来,这样可以大大减少胶囊和三角形碰撞检测的次数,因为这种动态检测是十分耗时的。有关胶囊和三角形面片的比较可以参考:http://dev.gameres.com/Program/Abstract/Arithmetic/capsule.mht中的方法。 对于BSP的划分以及AABB碰撞检测就不用多说了~到处都可以找到文章。
对于地形而言,也是采用同样的方法,只不过对于地形而言三角形信息不用额外存储,只需要使用和渲染相同的三角形(对于景物来说一般不会使用渲染用的三角形而会使用更加简化数量更少的简化网格碰撞模型)。这里可以有很多优化的技巧,因为地形本身是规则的cell一个地形是由若干个patch(一般是16X16)组成的,而每个patch是由若干cell(一般是16X16)组成的。对于patch来说一般已经组织到了一颗QUADTREE中了因为视棱锥裁减也需要这种结构,因此碰撞检测中的AABB-AABB阶段使用这颗已经存在的QUADTREE就可以快速的完成层次碰撞检测了。但发现某个patch中的AABB和人物的AABB发生碰撞后需要检测每一个CELL所在的AABB和人物AABB盒的碰撞,这里可以使用点小技巧比如说首先将AABB盒投影到CELL所在的XY平面上,找出被投影覆盖的那些CELL然后再检测这些CELL的AABB盒是否和人物的发生碰撞。一但确定了某个CELL和人物发生碰撞那么就可以将该CELL中的三角形取出(一般为2个)依次和人物所在的胶囊进行三角形-胶囊的碰撞检测。
这样当碰撞检测系统完成任务以后我们将会获得一个碰撞信息的数组:
class CollideInFo{
public:
GFVECTOR_3D m_worldcdnorm;//碰撞法向量
GFPOINT_3D m_worldpoint;//碰撞点
float m_cdtime;//碰撞时间
};
CollideInFo collidearray[];
然后使用这个数组就可以进行碰撞后的处理了包括,沿墙滑动下滑等等。在具体说明整个人物移动控制算法之前,首先说下动态碰撞检测和静态碰撞检测的区别,动态碰撞检测是指物体A以速度V前进了T时间,在这期间第一次和物体B发生碰撞的时间。这样的碰撞检测必须返回第一次2个物体发生碰撞的时间。而静态检测是指2个不动的物体是否互相相交对于这种检测是不需要返回时间的。动态检测算法比静态的复杂而且也耗用更多的时间。一个完善的碰撞系统需要解决以上2种碰撞检测,如果你不想自己写检测代码,目前比较流行的有OPCODE,SOLID库等检测库 。你可以直接使用他们提供的功能,这里我采用的是自己写检测代码的方法,目前只用到三角形-胶囊,AABB-AABB,OBB-OBB的碰撞检测。 [Page]
完成了包围体(用的是胶囊)和三角形的碰撞,胶囊和BSP,地形的碰撞检测之后,拥有了碰撞的信息 1。碰撞时间。2。碰撞法向量。3。碰撞点。接着就可以处理人物在碰撞后的反应了。
首先人物的一次移动分2个阶段,第一个是初始阶段,使用静态碰撞检测获得该阶段的速度(具体做法在后面说)。第2阶段使用该速度去做动态碰撞检测得到碰撞信息,根据这些碰撞信息去处理碰撞后的反应。
先来看第一阶段,过程对于一个胶囊我们需要获取他周围的临近面片的信息,以决定这个胶囊目前所处平面的倾斜度,是否贴着不可通过的墙等等。我采用的方法是将胶囊体略为膨胀一些,然后调用静态碰撞检测的代码获取和该膨胀后的胶囊体相交的三角形面片碰撞信息如图:
棕色的是原始的胶囊体,红色的表示将胶囊半径略为增加以后的胶囊体,蓝色的2个地形是将胶囊膨胀以后所发生相交的2个三角形,而如果不采用膨胀的话该胶囊是不和任何三角形相交的,具体膨胀数值可以设为胶囊下落的最小高度,比如你设定胶囊离底部物体超过0.6单位属于腾空状态的话你就将膨胀数值设为0.6。在这个例子中我们将会检测到2个碰撞(蓝色部分)这2个碰撞法向量正好是这2个三角面的法向量(在很多情况下也可能不是这个看你的碰撞代码中法向量是如何计算的了)。其中底部的那个是可行走平面,另外一个是不可行走平面,有了这2个碰撞平面就很简单了,如果用户输入的速度和那个不可行走的平面相反(也就是撞向那个不可行走平面),那么那个不可行走平面是有效的,这样他和底部那个可行走的平面所组成的交线就是人物的初始速度,如果用户输入的速度和那个不可行走的平面法向量相同,那么这个不可行走平面没有作用人物最终的速度就是把用户速度投影到该可行走平面上的速度。
0顶一下
具体计算是否能够水平移动以及移动速度的算法:当给出M个不可行走平面和N个可行走平面时:
1首先将速度在N个可行走平面上分解,检查这些分解的速度是否有效(如何判断速度有效下面会说道)
2如果在1的时候存在一个有效的速度直接返回该速度
3没有有效速度,这时检查NXM个平面的交线,将速度在上面分解同时检查是否存在有效速度
4如果存在有效速度返回该速度
5否则检查MXM条交线的,将速度在上面分解同时检查是否存在有效速度
6如果存在有效速度返回该速度
7否则返回0速度
那么如何判定速度是否有效呢,首先我们知道了所有碰撞的信息,给定一个速度,如果该速度和所有碰撞的法向量的夹角都是小于90度那么这就是个有效速度,(说明该分解后速度不会引起和这些碰撞面的在一次碰撞,因为该速度是将物体拉下远离该平面的方向的)。如果只要有一个夹角大于90度那么该速度就是非有效速度,同时在移动时还要判断该速度的倾斜角是否大于最大下滑倾斜角。注意 5是很重要的因为2个不可行走的平面所形成的交线仍旧可能是可以行走的,甚至是水平的。
以上的部分是检查是否能够水平移动的,如果不能水平移动那么该物体会下滑,1如果没有检测到碰撞平面说明物体处于腾空状态,这时候给物体加上重力加速度,产生一个往下的速度和原来的水平速度结合起来(如果存在)。
如图棕色是原始状态胶囊经过上一帧移动后到达蓝色的位置这时通过上诉算法可以检测到胶囊腾空,这时他的速度为水平速度(红色)+下滑速度(绿色)。
2如果检测到碰撞平面但是平面以及它们的交线都是不可行走的(倾斜角大于可行走角度)那么依次将当前速度在碰撞平面分解,以及检测每一条可能下滑的交线。得出速度后检测是否是有效速度具体过程和前面检查水平速度时的方法一样,只是将速度投影到平面上的方法不一样具体方法可以根据程序需要,我这里采用首先去掉原始速度在碰撞平面法线的分量,然后将速度分解为2个速度一个是贴着平面水平移动的速度另外一个是贴着平面下滑的速度。注意如果上一帧更新后物体处于下滑状态那么当前速度就因该是该下滑速度,否则则是用户输入的速度。
如果用户输入的速度是跳跃也就是带Z分量的速度那么,计算初始速度这一步需要略过直接输入给下一阶段用户起跳的速度。
阶段2计算初始速度引起的碰撞
对于多数情况来说,计算完初始速度就不会再发生碰撞了,一旦发生,那么我们依旧传入碰撞面的那些法向量,碰撞点的信息,同样采用前面计算初始速度时所采用的方法,计算出碰撞后的调整速度。 [Page]
整个处理过程基本上就是这样的,其中可能还会出现一些小问题比如说误差控制等。总结一条就是人物行走要么研着平面分解速度要么沿着2个平面的交线行走或者下滑。这个是Demo示例画面(由于没有人帮我做动画。。所以demo中目前只有行走动画,没有起跳,下落等动画。。)