前言:物理碰撞库是为了实现在多个不同的平台之间的计算结果统一,原有的collider或者character controller是基于浮点数运算的,在不同的平台里边有不同的计算结果。由于moba demo中使用到帧同步,浮点数会造成不同的平台之间产生不同的结果,会导致游戏逻辑结果的差异,就必须要自己去实现这一套物理碰撞的算法。整个物理碰撞的算法我们是基于定点数数学库PEMath,就可以得到确定性的结果
碰撞环境生成
实现Player运动控制后,继而实现环境碰撞。
做法:先生成环境,转化成代码中可以使用的BoxCollider。圆形以及box形等不动的东西,可以把它全部并到一起来进行处理,将其存下来。思维导图中EnvColliders可以读取到相应的配置,场景里面物体的摆放,生成配置数据Collider后供其他的模块使用

public class EnvColliders
{
public List<ColliderConfig> envColliCfgLst;
//将所有碰撞体放在List中,存box or cylinder的父类
List<PEColliderBase> envColliLst;
public void Init()
{
//根据相应的配置生成cylinder、box的Collider
envColliLst = new List<PEColliderBase>();
for(int i = 0; i < envColliCfgLst.Count; i++)
{
ColliderConfig cfg = envColliCfgLst[i];
if (cfg.mType==ColliderType.Box)
{
envColliLst.Add(new PEBoxCollider(cfg));
}else if (cfg.mType==ColliderType.Cylinder)
{
envColliLst.Add(new PECylinderCollider(cfg));
}
else
{
//TODO
}
}
}
//提供接口,获取PEColliderBase
public List<PEColliderBase> GetEnvColliders()
{
return envColliLst;
}
}
接着实现碰撞检测
先实现单个物体与当个物体的碰撞,对碰撞进行检测,方形圆形的碰撞算法实现
进行计算的时候,是要根据不同的类型来去计算,没办法直接去调研到DetectSphereContact(),因为不知道CalcCollidersInteraction()中colliders什么类型,那么还需要Base中实现所有类型都可以通用的一个方法DetectContact(),在子类进行碰撞的时候调用此方法进行探测
public virtual bool DetectContact(PEColliderBase collider, ref PEVector3 normal, ref PEVector3 borderAdjust)
{
if (collider is PEBoxCollider)
{
return DetectBoxContact((PEBoxCollider)collider, ref normal, ref borderAdjust);
}else if (collider is PECylinderCollider)
{
return DetectSphereContact((PECylinderCollider)collider, ref normal, ref borderAdjust);
}
else
{
//TODO
return false;
}
}
使用的时候CalcCollidersInteraction()计算交互时,动的是自己的角色。GameStart.cs中调用CalcCollidersInteraction()。跟环境的一个碰撞交互,在碰撞过程当中就直接调用ColliderBase中的DetectContact()类,把整个要进行碰撞的物体传进去
思路过程:
首先做物体的运行移动进行计算,位置的预先计算,更新位置信息后(位置1)

进入到这个运算的过程中计算所有碰撞体的交互(位置2),PECylinderCollider.cs中CalcCollidersInteraction()的colliders参数是由(位置3传入),及环境碰撞类中的所有碰撞环境,碰撞环境是根据在配置List<ColliderConfig> GenerateEnvColliCfgs()配置生成出来,配置是通过去读取场景通过transEnvRoot这个根节点

也就是根节点

查找下面的所有的子物体,首先找Box类型,找出所有BoxCollider后,获取组件Scale属性的值,取半除2后,得到的就是方形物体的长宽。圆形同理,最后有配置文件后生成的碰撞环境EnvColliders.cs中Init()类,增加一个API就可以供其他地方使用。
使用的时候首先在计算碰撞的时候,把玩家物体可能要进行计算的所有的碰撞环境全部传入logicEnv.GetEnvColliders()。如果场景非常大,比如有很多人很多环境,那么这种情况下不适合把所有的人都传进去,可以通过一些特殊的算法,比如说八叉树或者划分成九宫格等等,只用计算旁边的。Moba demo此环境不是很大,就直接把所有的环境碰撞体全部给它传入logicEnv.GetEnvColliders(),当做一个参数来进行运算。
计算速度矫正
校正后根据检测情况获取碰撞信息,校正完成后再对它预先执行的位置移动做修正,物体碰撞发生了穿插,这种情况下较正就应该把它矫正回来,整个碰撞环境不同种的类型构成的,计算的时候计算方法也是不一样的,所以在做检测的时候就必须针对不同的类型做不同的分支处理。为了方便去使用,是把它封装到了父类ColliderBase.cs中DetectContact(),调用的时候再根据不同的碰撞体的类型去分别调用不同的函数进行判断检测,获取它相应的碰撞体,计算出来碰撞的法线速度,法线较正位置校正等等。

这里进行检测跟哪个物体进行碰撞,这里只做了一种物体的碰撞,传进去以后给进去(位置1)函数在ColliderBase父类中,判断是什么类型,做什么检测。比如是box类型,那么就会跑到这个DetectBoxContact分支检测与方形碰撞体的一个接触,这个是abstract函数,在子类中去覆盖,即会调用到PECylinderCollider.cs中DetectBoxContact()。
圆形与圆形之间的碰撞检测
public override bool DetectSphereContact(PECylinderCollider col, ref PEVector3 normal, ref PEVector3 borderAdjust)
{
//原理:大小圆碰撞法线相加是否小于大小圆半径相加
PEVector3 disOffset = mPos - col.mPos;
if (PEVector3.SqrMagnitude(disOffset) > (mRadius + col.mRadius) * (mRadius + col.mRadius))
{
return false;
}
else
{
normal = disOffset.normalized;
borderAdjust = normal * (mRadius + col.mRadius - disOffset.magnitude);
return true;
}
}
进行矫正
//矫正后速度,法线
public PEVector3 CorrectVelocity(PEVector3 velocity,PEVector3 normal)
{
//判断法线是否为0,则刚好接触
if (normal==PEVector3.zero)
{
return velocity;
}
//确保靠近,不是原理,依据法线与速度的夹角大于90度说明远离
if (PEVector3.Angle(normal, velocity) > PEArgs.HALFPI)
{
PEInt prjLen = PEVector3.Dot(velocity, normal);//计算速度在法线上的投影
if (prjLen != 0)
{
velocity -= prjLen * normal;
}
}
return velocity;
}
之后赋值给velocity,可在GameStart中利用
//速度矫正
if (LogicDir != moveDir)
{
LogicDir = moveDir;
}
if (LogicDir != PEVector3.zero)
{
LogicPos = playerCollider.mPos + borderAdjust;//矫正的量
}
playerCollider.mPos = LogicPos;//矫正完之后的逻辑速度赋值给playerCollider
多个物体之间的碰撞
public void CalcCollidersInteraction(List<PEColliderBase> colliders, ref PEVector3 velocity, ref PEVector3 borderAdjust)
{
if (velocity == PEVector3.zero)
{
return;
}
List<CollisionInfo> collisionInfoList = new List<CollisionInfo>();
PEVector3 normal = PEVector3.zero;
PEVector3 adj = PEVector3.zero;
for (int i = 0; i < colliders.Count; i++)
{
if (DetectContact(colliders[i], ref normal, ref adj))
{
CollisionInfo info = new CollisionInfo
{
collider = colliders[i],
normal = normal,
borderAdjust = adj
};
collisionInfoList.Add(info);
}
}
if (collisionInfoList.Count == 1)
{
//单个碰撞体,修正速度
CollisionInfo info = collisionInfoList[0];
velocity = CorrectVelocity(velocity, info.normal);
borderAdjust = info.borderAdjust;
this.Log("单个碰撞体,校正速度:" + velocity.ConvertViewVector3().ToString());
}
//计算多个碰撞体的法线
else if (collisionInfoList.Count > 1)
{
PEVector3 centerNormal = PEVector3.zero;
CollisionInfo info = null;
//计算中心法线到边缘的角度
PEArgs borderNormalAngle = CalcMaxNormalAngle(collisionInfoList, velocity, ref centerNormal, ref info);
PEArgs angle = PEVector3.Angle(-velocity, centerNormal);
if (angle > borderNormalAngle)
{
velocity = CorrectVelocity(velocity, info.normal);
this.Log("多个碰撞体,校正速度:" + velocity.ConvertViewVector3());
PEVector3 adjSum = PEVector3.zero;
for (int i = 0; i < collisionInfoList.Count; i++)//所有矫正向量累加
{
adjSum += collisionInfoList[i].borderAdjust;
}
borderAdjust = adjSum;
}
else
{
velocity = PEVector3.zero;
this.Log("速度方向反向量在校正法线夹角内,无法移动:" + angle.ConvertViewAngle());
}
}
else
{
//this.Log("no contact objs.");
}
}
//计算中心法线与边缘法线的夹角,返回PEArgs,速度velocity
private PEArgs CalcMaxNormalAngle(List<CollisionInfo> infoList, PEVector3 velocity, ref PEVector3 centerNormal, ref CollisionInfo info)
{
for (int i = 0; i < infoList.Count; i++)
{
centerNormal += infoList[i].normal;//法线相加
}
centerNormal /= infoList.Count;//中心法线计算
PEArgs normalAngle = PEArgs.Zero;
PEArgs velocityAngle = PEArgs.Zero;
for (int i = 0; i < infoList.Count; i++)
{
PEArgs tmpNorAngle = PEVector3.Angle(centerNormal, infoList[i].normal);
if (normalAngle < tmpNorAngle)//找最大角度
{
normalAngle = tmpNorAngle;
}
//找出速度方向与法线方向夹角最大的碰撞法线,速度校正由这个法线来决定
PEArgs tmpVelAngle = PEVector3.Angle(velocity, infoList[i].normal);
if (velocityAngle < tmpVelAngle)
{
velocityAngle = tmpVelAngle;
info = infoList[i];
}
}
return normalAngle;
}