碰撞引擎如何工作?
这是一个极为广泛的问题。什么代码使事物相互碰撞,什么代码使玩家走进墙壁而不是穿过墙壁?代码如何不断刷新玩家的位置和物体的位置,以保持重力和碰撞正常进行?
如果您不知道碰撞引擎是什么,基本上它通常用于平台游戏中,以使玩家疯狂撞墙等。有2D类型和3D类型,但是它们都完成相同的事情:碰撞。
那么,是什么让碰撞引擎滴答答答呢?
碰撞引擎如何工作?
这是一个极为广泛的问题。什么代码使事物相互碰撞,什么代码使玩家走进墙壁而不是穿过墙壁?代码如何不断刷新玩家的位置和物体的位置,以保持重力和碰撞正常进行?
如果您不知道碰撞引擎是什么,基本上它通常用于平台游戏中,以使玩家疯狂撞墙等。有2D类型和3D类型,但是它们都完成相同的事情:碰撞。
那么,是什么让碰撞引擎滴答答答呢?
Answers:
碰撞引擎和物理引擎之间有很大的区别。尽管物理引擎通常依赖于碰撞引擎,但是它们并没有做同样的事情。
然后将碰撞引擎分为两部分:碰撞检测和碰撞响应。后者通常是物理引擎的一部分。这就是为什么碰撞引擎和物理引擎通常会合并到同一库中的原因。
碰撞检测有两种形式,离散形式和连续形式。先进的引擎支持这两者,因为它们具有不同的属性。通常,连续碰撞检测非常昂贵,并且仅在真正需要的地方使用。大多数碰撞和物理学都是使用离散方法来处理的。在离散方法中,对象最终会相互渗透,然后物理引擎将它们推开。因此,引擎实际上并不会阻止游戏者部分地穿过墙壁或地板,而只是在检测到游戏者部分位于墙壁/地板中后才将其固定。在这里,我将重点介绍离散冲突检测,因为这是我从头开始实现的最丰富经验。
碰撞检测
碰撞检测相对容易。每个对象都有一个变换和一个形状(可能有多个形状)。天真的方法会使碰撞引擎对所有对象对进行O(n ^ 2)循环,并测试对象对之间是否存在重叠。在更智能的方法中,存在多个空间数据结构(例如,针对静态对象与动态对象),每个对象的边界形状以及每个对象的多部分凸子形状。
空间数据结构包括KD树,动态AABB树,八叉树/四叉树,二进制空间分区树等。每个引擎都有其优点和缺点,这就是为什么某些高端引擎使用多个引擎的原因。例如,动态AABB树确实非常快,并且非常适合处理许多移动的对象,而KD-Tree可能更适合对象碰撞的静态关卡几何。还有其他选择。
广义阶段为每个对象使用空间数据结构和抽象边界量。包围体积是一种简单的形状,它包围了整个对象,通常目标是尽可能“紧密”地包围它,同时保持便宜以进行碰撞测试。最常见的边界形状是“轴对齐的边界框”,“对象对齐的边界框”,“球体”和“胶囊”。AABB通常被认为是最快和最容易的(在某些情况下球体更容易,更快,但是无论如何,这些空间数据结构中的许多都需要将球体转换为AABB),但它们也往往很难适应许多对象。胶囊在3D引擎中很流行,用于处理字符级碰撞。有些引擎会使用两个边界形状
碰撞检测的最后一个阶段是精确检测几何图形相交的位置。尽管并非总是如此,这通常意味着使用网格(或2D中的多边形)。此阶段的目的是找出对象是否真的发生碰撞,是否需要精细的细节(例如,射击者的子弹碰撞,在此您希望能够忽略几乎不会错过的射击),以及还可以找出对象碰撞的确切位置,这将影响对象的响应方式。例如,如果一个盒子坐在桌子的边缘,引擎必须知道桌子在盒子的哪个点上推。根据盒子悬挂的距离,盒子可能会开始倾斜并掉落。
联系歧管生成
这里使用的算法包括流行的GJK和Minkowski Portal精炼算法,以及“分离轴”测试。由于流行的算法通常仅适用于凸形,因此有必要将许多复杂的对象分解为凸形子对象,并对每个对象分别进行碰撞测试。这就是为什么简化网格经常用于碰撞以及减少使用较少三角形的处理时间的原因之一。
这些算法中的某些算法不仅告诉您对象确实发生了碰撞,而且还告知它们发生碰撞的位置-它们相互穿透的距离以及“接触点”的位置。一些算法需要其他步骤(例如多边形裁剪)才能获取此信息。
身体反应
此时,已发现一个联系人,并且有足够的信息供物理引擎处理该联系人。物理处理会变得非常复杂。较简单的算法适用于某些游戏,但即使看起来似乎很简单的方法(如保持一堆盒子稳定)也相当困难,并且需要大量工作和不明显的破解。
在最基本的层次上,物理引擎将执行以下操作:它将处理碰撞对象及其接触流形,并计算分离碰撞对象所需的新位置。它将对象移动到这些新位置。它还将计算此推的速度变化,并结合恢复(弹跳)和摩擦值。物理引擎还将施加任何其他作用在对象上的力(例如重力)来计算对象的新速度,然后(下一帧)它们的新位置。
更高级的物理响应很快变得复杂。上面的方法在许多情况下都会失效,包括一个对象位于另外两个对象之上。单独处理每对将导致“抖动”,并且对象将反弹很多。最基本的技术是对碰撞对象对进行多次速度校正迭代。例如,在其他两个“ B”和“ C”框上方放置一个“ A”框的情况下,将首先处理碰撞AB,使框A进一步倾斜到框C中。然后在晚上处理AC碰撞开箱即用,但将A下拉到B。然后进行另一个迭代,因此可以稍微解决由AC校正引起的AB误差,从而在AC响应中产生更多的误差。再次处理AC时处理。完成的迭代次数不是固定的,并且在任何时候它都不会变得“完美”,而是无论任何迭代次数停止给出有意义的结果。10次迭代是典型的首次尝试,但需要进行调整才能找出特定引擎和特定游戏需求的最佳数量。
联系缓存
在处理多种类型的游戏时,还有其他技巧很方便(或多或少)。联系人缓存是更有用的一种。使用联系人缓存,每组冲突对象都保存在查找表中。在检测到冲突时,每帧都将查询此高速缓存,以查看先前是否接触过对象。如果对象先前未接触过,则可以生成“新碰撞”事件。如果对象先前已接触,则可以使用该信息提供更稳定的响应。联系人缓存中未在框架中更新的任何条目都表示两个对象已分离,并且可以生成“分离对象”事件。游戏逻辑通常可用于这些事件。
游戏逻辑还可以响应新的碰撞事件并将其标记为已忽略。这对于实现平台中的一些常见功能确实很有帮助,例如可以跳升但可以站立的平台。天真的实现可能只是忽略具有向下平台的碰撞->角色正常碰撞(表明玩家的头部撞到平台的底部),但是如果没有接触缓存,如果玩家的头部在平台上戳了一下,然后他开始,则碰撞会中断跌倒。在那一点上,接触法线可能最终指向上方,导致玩家在不应该通过平台时弹出。通过接触缓存,引擎可以可靠地观察初始碰撞法线,而忽略所有其他接触事件,直到平台和玩家再次分离为止。
睡眠
另一种非常有用的技术是如果对象不与之交互,则将其标记为“睡眠中”。睡眠中的对象不会获得物理更新,不会与其他睡眠中的对象发生碰撞,基本上只是及时冻结在那里,直到另一个非睡眠中的对象与它们发生碰撞。
这样的影响是,所有坐在那里什么都不做的碰撞对象对都不会占用任何处理时间。另外,由于没有恒定的微小物理校正量,因此堆栈将保持稳定。
当一个物体的速度接近零且超过一帧时,它就是睡眠的候选者。请注意,用于测试该接近零速度的epsilon可能会比通常的浮点比较epsilon稍高,因为您应该期望堆叠的对象产生一些抖动,并且如果它们重叠,您希望整个对象进入睡眠状态重新保持“足够接近”稳定。该阈值当然需要调整和试验。
约束条件
许多物理引擎的最后一个主要方面是约束求解器。这种系统的目的是促进诸如弹簧,电动机,轮轴,模拟软体,布料,绳索和链条之类的东西的实现,甚至有时甚至是流体的实现(尽管流体通常被实现为完全不同的系统)。
甚至约束解决的基础知识都可能需要大量数学知识,并且超出了我在该主题上的专业知识。我建议阅读Randy Gaul的出色物理学文章系列,以对该主题进行更深入的解释。
我使用的一个“碰撞引擎” 非常容易掌握。
基本上,API提供了一种具有method的对象collidesWith
,从而
在我的应用程序中,我只是定期调用collidesWith
以查明是否发生了碰撞。
是不是很简单?
也许唯一需要一点想象力的事情就是当普通的边界矩形不足以模拟碰撞对象的几何形状时。在这种情况下,只需使用多个可碰撞对象而不是一个即可。
通常,当您发现单个碰撞矩形无法满足您的需要时,您便发明了一种将事物分解为更多矩形子元素的方法,以便在组合时,这些元素可以模拟/近似所需的行为。
我认为您对正在谈论的内容有些困惑,并且在谈论一些不同的事情。
说这个项目从位置X移到位置Y的能力是基于物理原理的(这也攻击了它们如何移动以及为什么移动)
用于冲突检测的方法是根据游戏的结构确定的。如果您的游戏是一个开放的大世界,那么您应该非常考虑空间分区(四叉树(用于3D的八叉树),BSP,传统网格或老式的测试一切方法)
实现碰撞检测系统的最佳方法是分步骤进行。
将所有对象放入通用的边界体积/形状中,然后进行测试
如果1通过,则重复进行更复杂的体积/形状重复,直到准备测试绝对几何
测试绝对几何形状将根据形状的复杂程度以及这些形状的精确度确定重复步骤2的时间。
您应该考虑将这些步骤中的每个步骤都提早进行,并且是为了消除所发生的碰撞,并且只有在它们确实接触时才在步骤3中返回true。
最后是碰撞解决。这确定了您发现碰撞并证明它确实是碰撞之后发生的事情,以及如何处理。这通常由物理学处理。
传统循环如下所示:
receive input
update physics
collision detection
collision resolution
render
repeat
在图形卡级别(您通常要处理三角形),总体思路是对场景进行某种划分,从而不必检查所有N个三角形(这可以作为预处理步骤完成) ),然后找出您在场景中的位置,然后仅检查该分区中的那10-50个三角形。
有关更多信息,请参见BSP和Kd树。还有其他分区方法。