“碰乜鬼嘢啊,碰走晒我滴靓牌”。想到“碰”就自然联想到了“麻将”这一伟大发明。当然除了“碰”,洗牌的时候也充满了各种『碰撞』。

好了,不废话。直入主题——碰撞检测。

在 2D 环境下,常见的碰撞检测方法如下:

  • 外接图形判别法
    • 轴对称包围盒(Axis-Aligned Bounding Box),即无旋转矩形。
    • 圆形碰撞
    • 圆形与矩形(无旋转)
    • 圆形与旋转矩形(以矩形中心点为旋转轴)
  • 光线投射法
  • 分离轴定理
  • 其他
    • 地图格子划分
    • 像素检测

下文将由易到难的顺序介绍上述各种碰撞检测方法:外接图形判别法 > 其他 > 光线投射法 > 分离轴定理。

另外,有一些场景只要我们约定好限定条件,也能实现我们想要的碰撞,如下面的碰壁反弹:

See the Pen Boundary collision detection by Jc (@JChehe) on CodePen.

当球碰到边框就反弹(如x/y轴方向速度取反)。

1
2
if(ball.left < 0 || ball.right  > rect.width)  ball.velocityX = -ball.velocityX
if(ball.top < 0 || ball.bottom > rect.height) ball.velocityY = -ball.velocityY

再例如当一个人走到 100px 位置时不进行跳跃,就会碰到石头等等。

因此,某些场景只需通过设定到适当的参数即可实现碰撞检测。

外接图形判别法

轴对称包围盒(Axis-Aligned Bounding Box)

概念:判断任意两个(无旋转)矩形的任意一边是否无间距,从而判断是否碰撞。

算法:

1
2
3
4
rect1.x < rect2.x + rect2.width &&
rect1.x + rect1.width > rect2.x &&
rect1.y < rect2.y + rect2.height &&
rect1.height + rect1.y > rect2.y

两矩形间碰撞的各种情况:
轴对称包围盒

在线运行示例(先点击运行示例以获取焦点,下同):

See the Pen AxisAlignedBoundingBox collision detection by Jc (@JChehe) on CodePen.

缺点:

  • 相对局限:两物体必须是矩形,且均不允许旋转(即关于水平和垂直方向上对称)。
  • 对于包含着图案(非填满整个矩形)的矩形进行碰撞检测,可能存在精度不足的问题。
  • 物体运动速度过快时,可能会在相邻两动画帧之间快速穿越,导致忽略了本应碰撞的事件发生。

适用案例:

  • (类)矩形物体间的碰撞。

圆形碰撞(Circle Collision)

概念:通过判断任意两个圆形的圆心距离是否小于两圆半径之和,若小于则为碰撞。

两点之间的距离由以下公式可得:
两点之间距离

判断两圆心距离是否小于两半径之和:

1
2
3
Math.sqrt(Math.pow(circleA.x - circleB.x, 2) +
Math.pow(circleA.y - circleB.y, 2))
< circleA.radius + circleB.radius

图例:
圆形间的碰撞检测

在线运行示例:

See the Pen EZrorG by Jc (@JChehe) on CodePen.

缺点:

  • 与『轴对称包围盒』类似

适用案例:

  • (类)圆形的物体,如各种球类碰撞。

圆形与矩形(无旋转)

概念:通过找出矩形上离圆心最近的点,然后通过判断该点与圆心的距离是否小于圆的半径,若小于则为碰撞。

那如何找出矩形上离圆心最近的点呢?下面我们从 x 轴、y 轴两个方向分别进行寻找。为了方便描述,我们先约定以下变量:

1
2
3
矩形上离圆心最近的点为变量:closestPoint = {x, y};
矩形 rect = {x, y, w, h}; // 左上角与宽高
圆形 circle = {x, y, r}; // 圆心与半径

首先是 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
2
3
4
var distance = Math.sqrt(Math.pow(closestPoint.x - circle.x, 2) + Math.pow(closestPoint