2D 平台游戏的碰撞检测方法

“闪回”,以网格形式展示

这篇文章里介绍了几种 2D 平台跳跃的碰撞检测的实现方法。包括:基于网格的碰撞、基于图像位掩码的检测方法、基于向量的碰撞检测、基于包围体的碰撞检测。

声明: 我大部分内容是参考了这篇文章 这里,再加上我自己的理解。对于其中一些游戏的实现方式可能不是文中说的那样,因为这是由反向推断得到的。

第一种:基于网格(简单)

在基于网格的2D场景中,人物移动受限于网格,人物永远不可能站在两个网格的中间。动画能使人物的移动看起来更平滑。

这是最简单的一种平台游戏的实现方式,但是这种方式对于人物控制有着很多限制,所以它不适合大多是基于动作的游戏,但是它很适合解谜类的平台游戏。

“闪回”,以网格形式展示

例子:波斯王子、闪回、淘金者(Lode Runner)、小鸡快跑

它是如何实现的

整个游戏地图是由一系列的网格组成,每个网格都储存了一系列的信息,比如说:这是否是一个障碍物,它所使用的贴图是什么,行走在这上面的声音是什么,诸如此类。

玩家和其他人物由一系列的网格组成。例如,在淘金者中,玩家是一个单独的网格。在小鸡快跑中,玩家是由一个 2X2 的网格组成的。在闪回中,玩家站着的时候,是两个网格宽,五个网格高(看上面的图片);玩家蹲下的时候,是三个网格高。

在这类游戏中,玩家几乎不会沿着对角线移动,但是如果他要沿着对角线移动的话,整个移动可以别分解为两个独立的步骤 – 分别沿着水平和竖直方向移动。同样的,玩家一次性只能移动一个网格,如果要移动多个网格的话,就要分解为多次的一个网格的移动。算法在下面展示出来了:

  1. 在目的地创建一个人物的复制体。例如:要向右移动一个格子的话,你就将玩家所属的每个格子向右移动一格的位置制造一个玩家的复制体。
  2. 在复制体上检查复制体和背景还有其他人物体的碰撞。
  3. 如果检测到一个交互,那么玩家的移动就停止,执行相应的动作。
  4. 否则,将玩家移至目的地,同时做出动画来使得玩家的移动看起来更加平滑。

这种移动方式非常不适合一般的弧形跳跃 – 所以在这种游戏中,玩家通常无法跳跃 (小鸡快跑、淘金者)或者只能在竖直方向上跳跃(闪回、波斯王子)。

这种方式的优点在于其容易实现,而且也很精确。因为场景的确定性很高,所以因碰撞产生的抖动或其他的小故障几乎不会发生,游戏体验能得到更好的控制 – 因为不太需要基于不同环境去调整数值。尤其是相较于其他复杂的移动方式时, 这种方法在实现一些特定的物理学运动的时候(比如抓住墙壁,单方向的平台)变得十分简单 – 你需要做的一切就是检查玩家是否以一种特定的方式接近一个特殊的网格,以此来触发一个特殊的动作。

从理论上来说,这种方式不允许玩家移动的距离小于一个网格,但是我们有一些方法来减轻这个限制。比如:网格大小可以小于玩家大小(就像:玩家是 2X6 大小);或者,你可以用一种仅仅只是视觉上的表现去让玩家站在格子中间,而不影响碰撞逻辑。

第二种:基于网格(平滑)

碰撞仍然由网格进行检测,但是人物可以在游戏世界中进行平滑移动(一般来说一次移动是以像素为基本单位的,由整数控制,但是在文章最后会介绍一种平滑移动的方式)。这种移动方式通常是实现 8-bit 或 16-bit 的平台游戏的实现方式,但是在现在仍然很受欢迎,因为它的实现十分简单,而且能够使关卡编辑简化。它也允许斜坡和平滑的跳跃轨迹。

如果你不太确定你想要实现什么种类的平台游戏,但你想做一个动作游戏,那么这种方法是一个很好的选择。它非常灵活,也很容易实现。在介绍的四种方式中,这种方式会给你最多的控制权。毫无疑问,大部分的动作游戏都是基于这种类型的。

“洛克人”, 展示了网格边框和玩家的碰撞盒

例子:超级马里奥、刺猬索尼克、洛克人、超级银河战士、魂斗罗、合金弹头、还有几乎所有的 16-bit 的平台游戏。

它是如何实现的

和简单的基于网格的碰撞一样,它的地图信息也是储存于一个二维数组里的,唯一的不同之处就是玩家和背景的交互方式。 玩家的碰撞盒现在是一个 AABB 包围盒(Axis-Aligned Bounding Box, 一个无法旋转的矩形),而且,一般来说,是网格大小的整数倍。通常的大小有一个网格宽,加上一个网格(小马里奥,变形的球型的银河战士)、两个网格(大马里奥,洛克人,蹲下的银河战士)、或者三个网格(站着的银河战士)高。大多数情况下,人物精灵的大小会比碰撞盒大。在上面的图片中,你会发现,角色精灵的在 X 方向上的大小是两个网格宽,但是它的包围盒是一个网格宽。

假设现在没有斜坡和单向平台(one-way platform),移动的算法很直接:

  1. 将移动分解为水平方向的移动和竖直方向的移动,一次移动一个方向。如果你想要实现沿着斜坡移动的话,先向水平方向上移动,然后是竖直方向。否则的话,移动次序没有要求。 然后,在每个方向上:
  2. 得到面向前方的碰撞盒边缘的点的坐标。例如:如果向左走,那么就是碰撞盒的左边那条线的 X 坐标。如果向右边走,就是右边那条线的 X 坐标。如果向上走,就是上面那条线的 Y 坐标。
  3. 确定碰撞盒会与哪些网格相交。例如,如果我们在向左边走,有可能玩家会与水平方向上的第32、33、34 个网格相交(也就是说,Y 轴坐标为 32 TS、33 TS、34 * TS 的网格,其中 TS = 网格大小)。
  4. 沿着移动方向遍历这些网格,找到一个离你最近的的静态碰撞体。然后遍历所有的移动的碰撞体。最后确定离你最近的,而且在你的移动方向上的碰撞体。
  5. 玩家的最终沿着该方向的移动距离就是:玩家到最近碰撞体的距离和玩家想要移动的距离的最小值。
  6. 将玩家移动到一个新位置。在到了进行下一个方向上的移动。