2D 碰撞检测系统
具有 VB.NET 的 2D 碰撞检测系统

引言
本文介绍了如何在 2D 环境中检测各种对象之间的碰撞。我创建这个碰撞检测系统已有 2 年了,用于我目前正在开发的引擎。编程是我的爱好——我不是专业的开发者,也不熟悉游戏物理学中存在的所有技术,也许这就是为什么我从未见过像我的这种碰撞检测方法。我猜已经存在类似的碰撞检测系统,但我还没有找到,所以如果您知道描述了类似的碰撞检测方法的文章,请告诉我。另外,我非常乐意听取您对我的 2D 碰撞检测方法的意见和建议。谢谢。
本文附带的存档包含一个演示程序,展示了我碰撞检测系统的一些能力。运行 'Rust_Engine.exe' 可执行文件来启动演示应用程序。以下是有关如何使用演示应用程序的一些说明
密钥
- E - 创建对象
- W - 移动对象
- Del - 删除所有对象
鼠标
- 在对象上单击鼠标中键 - 删除当前对象
- 在对象上单击鼠标左键 - 选择当前对象
- 在对象上双击鼠标左键 - 选择所有对象
- 在对象上单击鼠标右键 - 打开当前对象的属性
- 单击游戏区域 - 取消选择所有对象
Rust Engine 演示还展示了声音处理、动画、纹理支持、排序绘图(等轴测投影)和 Act - React 系统,但无论如何这些东西与本主题无关,也许我以后会解释如何创建这些有趣的东西 :)。请注意,使用的语言是 VB.NET,但描述的方法是通用的,可以在 Visual Studio 系列的所有语言中编写。
背景
我的碰撞检测系统不只使用对象坐标来检测碰撞,而是向您展示如何主要利用游戏区域作为信息来源,判断场地上是否存在对象。我将要解释的碰撞检测方法在检测碰撞时不需要知道对象的确切数量。现在想象一下,你站在浴室的瓷砖上。你脚下有多少块瓷砖?假设有 4 块。
现在让我们将这些浴室瓷砖视为游戏世界游戏区域的像素。我们可以为游戏区域中的所有像素关联一个整数数组。为此,我们可以声明一个类似 intWorldArr(224) As Integer
的数组。如果我们有一个 15 x 15 像素的游戏区域,那么我们的数组将包含 225 个元素,每个像素一个元素。如果我们有一个 2 x 2 像素的对象,那么我们的对象将占据游戏区域的 4 个像素。如果每个像素对应一个代表游戏区域的整数数组元素——intWorldArr(224)
——那么如何找到该像素所引用的元素呢?如果我们需要找到所需的元素,遍历所有 225 个元素会很愚蠢。为了解决这个问题,我们将使用像素的坐标和另一个整数数组,该数组的元素数量将等于我们游戏区域的高度 - 15。所以,我们将声明另一个整数数组——有点像 intStartNum(14) As Integer
。现在看下面的图片——您可以看到,从左边开始的第一个垂直列中的像素编号用绿色着色。这些绿色数字是 intStartNum(14)
数组中每个元素的值。第一个元素的值为 1
,第二个元素的值为 16
,依此类推,每个值都增加 15
(我们游戏区域的宽度),直到达到 211
。让我们看一下紫色对象——它的坐标是 x = 9
,y = 9
,大小为 2 x 2 像素。让我们给这个对象起个名字,例如 1。对象 1 放置在我们的游戏区域的 145
、146
、160
和 161
像素上。因此,对象 1 将在 intWorldArr
的 145
、146
、160
和 161
元素中写入其名称。
intWorldArr(145) = 1
intWorldArr(146) = 1
intWorldArr(160) = 1
intWorldArr(161) = 1
这就是对象 1 如何通知其他对象它的存在。看起来不错,但如何找到对象 1 所在的像素编号呢?这时就轮到第二个整数数组 intStartNum(14)
来帮忙了。如果您已经在下面的图片中注意到——将某个像素的 x 坐标添加到其 y 坐标的绿色数字将返回您正在寻找的像素的编号。例如,对象 1 的坐标是 x = 9,y = 9。要找到 intWorldArr
中与该位置对应的元素,我们可以进行这个简单的操作(objPrpl
将是下面图片中的紫色对象)。
intWorldArr(objPrpl.x + intStartNum(objPrpl.y)) = 1
此操作还将 objPrpl
的名称赋给 intWorldArr
的 145
元素。现在,检测两个或多个对象之间的碰撞就像小菜一碟。您可以使用 0
作为可通行区域——如果您的对象“看到”它想移动到的像素中有 0
或它的名称“在里面”,则允许移动;如果在我们对象前面的像素中有另一个数字,则禁止移动。

现在让我们总结一下以上所有内容
1. 您需要创建一个整数数组(intWorldArr
),其大小等于您游戏区域中的像素数量。
2. 创建另一个数组(intStartNum
),其大小等于您游戏区域的高度(以像素为单位)。intStartNum
将包含游戏区域第一个左侧列中所有像素的起始编号——参见上方的第一张图片。
3. 创建一个数组来保存您的所有对象(对象数组)。每个对象的名称与其在对象数组中的下标相等非常重要——这将帮助您确定您的哪个对象发生了碰撞并在碰撞发生后采取适当的行动。
4. 为您的游戏世界选择特殊编号。例如,' -1 ' = 可通行区域。如果 ' 0 ' 是您的可通行区域,那么在为对象命名时,请加 1 以避免创建一个“幽灵”对象,该对象会用可通行编号 ' 0 ' 填充游戏区域。
空中单位问题
您可能会说这个碰撞检测系统只为检测地面单位之间的碰撞而设计,您基本上是对的。这种碰撞检测方法最大的问题是处理飞行对象。由于游戏区域只有一个层,如果空中单位位于地面单位上方,地面单位的位置将被上方飞行单位的名称覆盖。地面单位将变成“幽灵”对象,其他单位将停止注意到它的存在。为了避免这种混淆,您可以添加第二个游戏区域层,飞行单位将在其中移动——例如 AirWorldArr(224)
。我知道这不是一个非常优雅的解决方案,因为它会导致更多的内存消耗,但我仍在努力解决这个问题。非常欢迎您提出任何想法!
代码示例
REM: the method that fills intWorldArr with the name of the moving object:
Sub FillWrldArr(ByVal HeroBody As ObjectBody, ByVal szeWrldSize As Size)
Dim intStartPos = HeroBody.intLeft + intStartNum(HeroBody.intTop)
Dim intEndPos = HeroBody.intRight + intStartNum(HeroBody.intTop)
For i = 0 To HeroBody.intHeight
For i2 = intStartPos To intEndPos
intWorldArr(i2) = HeroBody.intName
Next
intStartPos += szeWrldSize.Width
intEndPos += szeWrldSize.Width
Next
End Sub
历史
- 首次发布:2010 年 4 月 7 日