首先介绍一下默认碰撞检测存在的问题。先不提【我只想要你丫告诉我碰撞盒撞上了,却非得要我挂个钢体组件,还得接受重力,阻尼,角速度的影响。不是在逗我?】,那么我们手动勾选上钢体的 Is Kinematic 属性,自己来控制。
我们看下边的图:
当这个圆走到实线圆圈位置的时候,就会触发碰撞检测回调。当然,如果是在虚线圆圈的位置,回调是不会被触发的。那么问题来了,如果一帧的移动距离刚好是从虚线位置移动到实线位置,情况就是这样的,移动前一帧不会触发碰撞回调,而后一帧两个碰撞体已经嵌到一起了。
当然,如果只是小小的插入一下,也并没有什么问题,接下来看下面的图:
当一帧的移动距离超过碰撞目标的时候,奇迹发生了,这时碰撞检测的回调根本不会触发。也就是说在速度足够快的情况下,你的墙是挡不住你的球的。
为了实现一个在理想条件下的匀速直线运动,我就用 Unity 本身的射线检测功能来解决这个问题。
首先取出自己的碰撞盒
var selfCollider = GetComponent<Collider2D>();
Collider 中有 Raycast 和 Cast 两个方法,参数差不多,具体的区别,还得画两张图:
图一
图二
图三
图一、图二中蓝色的线就是 Raycast 的结果,是从自己的中心射到目标碰撞盒上,如果按照这个射线的距离运动,结果就是图二的样子。
图三种的蓝色线是 Cast 方法的结果,射到目标碰撞盒上的是自己碰撞盒与目标碰撞盒的交点。这个距离就是从当前点移动到发生碰撞的点的距离。
简单一点就用 Cast 方法就够了,Raycast 比 Cast 提供了更多细节操作,比如专门针对某一层的检测等,如果需要做更细节的优化,就得自己用 Raycast 方法来处理。
RaycastHit2D[] casthits = new RaycastHit2D[maxRaycastCache];
int num = selfCollider.Cast(forward, casthits);
RaycastHit 中比较重要的属性:collider —— 目标碰撞盒distance —— 到碰撞点的距离(上面图3中蓝线的长度)normal —— 碰撞点的法线point —— 目标碰撞点
接下来的逻辑就是:
比较本帧移动距离和 distance ,如果本帧移动距离比较小,就直接移动。
float stepDistance = speed * Time.fixedDeltaTime;
if (stepDistance < distance)
{
gameObject.transform.Translate(forward * stepDistance); distance -= stepDistance;
}
当然之后要把总距离减去这步的移动距离。
如果这一帧的距离大于剩余的距离了,就需要处理反射了。
先移动到碰撞点→方向向量反射→继续移动完。
while (stepDistance > nextDistance)
{
gameObject.transform.Translate(forward * nextDistance);
stepDistance -= nextDistance;
forward = Vector2.Reflect(forward, normal);
/* 找下一个碰撞目标 */
……
}
打完收工。
当然这只是简单的描述,实际上还会遇到一些坑,比如当前自己和一个碰撞盒有重叠的时候,取到的第一个 RaycastHit 永远是有接触的这个碰撞盒,此时与方向向量无关,所以需要根据自己的逻辑处理。还有同时碰撞两个或多个碰撞盒等,都要小心处理。