ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

直线与圆碰撞检测:游戏开发必知技巧

直线与圆碰撞检测:游戏开发必知技巧 游戏中直线与圆的关系经常用来处理子弹会不会击中角色激光是否碰到护盾移动路线会不会穿过危险区域。我们先讨论2D 游戏或把场景投影到地面的情况。在 3D 中对应的问题通常是直线与球体的关系。一、案例激光会不会碰到圆形护盾假设角色周围有一个圆形护盾圆心O角色的位置半径r护盾的大小直线激光所在的路线。直线与圆有三种位置关系。1. 相离激光从旁边经过激光 ───────────────── ◯ 护盾直线与圆没有公共点不会碰到护盾边界。2. 相切激光刚好擦到护盾直线刚好碰到圆的边缘只有一个公共点这个点叫切点。在数学上属于接触游戏是否把“擦边”算命中可以按规则决定。3. 相交激光穿过护盾所在区域圆形护盾 / \ 激光 ────P────O────Q──── \ /直线与圆有两个公共点P一侧的交点Q另一侧的交点。如果激光从左向右、从圆外射来那么P就是首次碰到护盾的位置。注意相交不要求经过圆心偏离圆心但穿过圆内部也算相交。二、最简单的判断比较距离与半径关键不是先找交点而是求圆心到直线的最短距离d。这个距离是垂直距离O 圆心 │ │ d │ ──────────H────────── 直线然后比较条件关系游戏中的直观含义d r相离从旁边经过d r相切刚好擦边d r相交穿过圆形区域具体数值护盾半径是2 米圆心离激光 3 米 → 没碰到 圆心离激光 2 米 → 刚好擦边 圆心离激光 1 米 → 穿过护盾这就是最核心的判断方法。如果直线方程是[axbyc0]圆心是 ((x_0,y_0))那么[d\frac{|ax_0by_0c|}{\sqrt{a2b2}}]其中a、b不能同时为零。三、游戏中的关键区别直线、射线、线段直线会碰到圆不代表子弹真的能碰到。1. 直线两头无限延伸适合用来分析位置关系但多数实际攻击不是无限直线。2. 射线从起点出发只沿一个方向例如护盾 ◯ 枪口 S ─────────→护盾在枪口后方。即使把弹道向后延长能碰到护盾向右发射的子弹也不会击中它。所以射线检测还要确认交点是不是在发射方向的前方。3. 线段有起点也有终点例如枪口 S ─────── E ◯ 护盾 射程终点弹道延长后能碰到护盾但实际射程不够。所以线段检测还要确认碰撞位置是否在起点与终点之间。四、案例子弹太快为什么会“穿人”假设每帧只检查一次子弹位置上一帧位置 A 敌人 ◯ 下一帧位置 BA 在敌人外面B 也在敌人外面。如果只检查这两个位置就可能认为上一帧没碰到 下一帧也没碰到 → 没命中但子弹实际已经从敌人身体中穿过了。解决办法把这一帧内的移动看成线段A ─────────── ◯ ─────────── B检查A 到 B 的整段路径是否接触圆形碰撞区域而不是只检查终点。对于这一帧内沿直线运动的点状子弹以及位置不变的圆形目标这能避免这种漏检。五、实际检测找圆心到线段的最近点设A子弹上一帧位置B子弹下一帧位置O敌人圆心r敌人碰撞半径。判断思路在线段AB上找到离圆心O最近的点P计算O到P的距离如果距离不大于r就接触或进入了圆形区域。Unity 2D 可以这样写boolSegmentHitsCircle(Vector2a,Vector2b,Vector2center,floatradius){Vector2abb-a;floatlengthSquaredab.sqrMagnitude;// 子弹没有移动退化为点与圆形区域的检测if(lengthSquared0f)return(a-center).sqrMagnituderadius*radius;floattVector2.Dot(center-a,ab)/lengthSquared;// 限制最近点必须在线段上tMathf.Clamp01(t);Vector2closestat*ab;return(closest-center).sqrMagnituderadius*radius;}这里比较的是距离的平方不用开平方也能判断。注意这检测的是实心的圆形区域即使整条线段都在圆内部也会返回true。它不只是检查有没有穿过圆周。六、子弹本身也有大小怎么办如果子弹也是圆形的半径为r₁敌人半径为r₂可以把敌人的检测半径扩大为[Rr_1r_2]然后把子弹中心当成一个点检查它的移动线段是否接触这个扩大后的圆形区域。例如子弹半径0.1 米 敌人半径0.5 米 用于检测的半径0.6 米最后记住两句话数学上看圆心到直线的距离与半径比较。游戏里还必须考虑发射方向、射程以及物体本身的大小。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进