“碰乜鬼嘢啊,碰走晒我滴靓牌”。想到“碰”就自然联想到了“麻将”这一伟大发明。当然除了“碰”,洗牌的时候也充满了各种『碰撞』。
好了,不废话。直入主题——碰撞检测。
在 2D 环境下,常见的碰撞检测方法如下:
- 外接图形判别法
- 轴对称包围盒(Axis-Aligned Bounding Box),即无旋转矩形。
- 圆形碰撞
- 圆形与矩形(无旋转)
- 圆形与旋转矩形(以矩形中心点为旋转轴)
- 光线投射法
- 分离轴定理
- 其他
- 地图格子划分
- 像素检测
下文将由易到难的顺序介绍上述各种碰撞检测方法:外接图形判别法 > 其他 > 光线投射法 > 分离轴定理。
另外,有一些场景只要我们约定好限定条件,也能实现我们想要的碰撞,如下面的碰壁反弹:
See the Pen Boundary collision detection by Jc (@JChehe) on CodePen.
当球碰到边框就反弹(如x/y轴方向速度取反
)。
1 |
if(ball.left < 0 || ball.right > rect.width) ball.velocityX = -ball.velocityX |
再例如当一个人走到 100px
位置时不进行跳跃,就会碰到石头等等。
因此,某些场景只需通过设定到适当的参数即可实现碰撞检测。
外接图形判别法
轴对称包围盒(Axis-Aligned Bounding Box)
概念:判断任意两个(无旋转)矩形的任意一边是否无间距,从而判断是否碰撞。
算法:
1 |
rect1.x < rect2.x + rect2.width && |
两矩形间碰撞的各种情况:
在线运行示例(先点击运行示例以获取焦点,下同):
See the Pen AxisAlignedBoundingBox collision detection by Jc (@JChehe) on CodePen.
缺点:
- 相对局限:两物体必须是矩形,且均不允许旋转(即关于水平和垂直方向上对称)。
- 对于包含着图案(非填满整个矩形)的矩形进行碰撞检测,可能存在精度不足的问题。
- 物体运动速度过快时,可能会在相邻两动画帧之间快速穿越,导致忽略了本应碰撞的事件发生。
适用案例:
- (类)矩形物体间的碰撞。
圆形碰撞(Circle Collision)
概念:通过判断任意两个圆形的圆心距离是否小于两圆半径之和,若小于则为碰撞。
两点之间的距离由以下公式可得:
判断两圆心距离是否小于两半径之和:
1 |
Math.sqrt(Math.pow(circleA.x - circleB.x, 2) + |
图例:
在线运行示例:
See the Pen EZrorG by Jc (@JChehe) on CodePen.
缺点:
- 与『轴对称包围盒』类似
适用案例:
- (类)圆形的物体,如各种球类碰撞。
圆形与矩形(无旋转)
概念:通过找出矩形上离圆心最近的点,然后通过判断该点与圆心的距离是否小于圆的半径,若小于则为碰撞。
那如何找出矩形上离圆心最近的点呢?下面我们从 x 轴、y 轴两个方向分别进行寻找。为了方便描述,我们先约定以下变量:
1 |
矩形上离圆心最近的点为变量:closestPoint = {x, y}; |
首先是 x 轴:
如果圆心在矩形的左侧(if(circle.x < rect.x)
),那么 closestPoint.x = rect.x
。
如果圆心在矩形的右侧(else if(circle.x > rect.x + rect.w)
),那么 closestPoint.x = rect.x + rect.w
。
如果圆心在矩形的正上下方(else
),那么 closestPoint.x = circle.x
。
同理,对于 y 轴(此处不列举图例):
如果圆心在矩形的上方(if(circle.y < rect.y)
),那么 closestPoint.y = rect.y
。
如果圆心在矩形的下方(else if(circle.y > rect.y + rect.h)
),那么 closestPoint.y = rect.y + rect.h
。
如果圆心在矩形的正左右两侧(else
),那么 closestPoint.y = circle.y
。
因此,通过上述方法即可找出矩形上离圆心最近的点了,然后通过『两点之间的距离公式』得出『最近点』与『圆心』的距离,最后将其与圆的半径相比,即可判断是否发生碰撞。
1 |
var distance = Math.sqrt(Math.pow(closestPoint.x - circle.x, 2) + Math.pow(closestPoint |