为 JavaScript 游戏构建一个简单的 2D 物理引擎

458 查看

简介

2D 游戏有许多不同的形状和大小。在某些情况下,构建自己的 2D 物理引擎,生成碰撞检测等系统模拟是一个不错的选择,在使用 JavaScript 时更是如此。在任何平台开发强大的物理引擎都非常困难,而比较简单、简洁的引擎往往更容易。如果您需要一个流行的物理引擎的简化版本,默默地从头开始构建就可以高效地完成此工作。

本文将探索一个物理引擎的实现,从而构建一个平台游戏的基本框架。使用现有的物理引擎(如 Box2D)与示例中的引擎进行比较。代码片段显示了组件间的交互。

您也可以下载本文中使用的示例的源代码。

为什么要更简单?

在游戏开发中,更简单意味着很多东西。在利用物理引擎时,更简单通常是指计算的复杂性。在确定游戏的最低公共标准后,复杂性变得更加彼此相关。在计算方面,复杂性意味着处理可能需要更长的时间,或者物理上的算法可能比较困难。伴随本文的相关操作,您不需掌握微积分知识。

由于 JavaScript 能够很好地与 HTML5 canvas 结合,所以您经常会看到将它应用到基于画布的游戏中。在某些情况下(例如 iOS 或 Android 移动平台),画布的图形会成为游戏中的一个主要因素。需要构建一个较小的资源平台,这意味着您需要尽可能进行压缩处理,为 CPU 执行昂贵的图形计算留出足够的空间。

CPU 利用率

处理是从一个经过测试的、功能强大的库转移到一个内部开发的、简洁的解决方案的主要原因。我们需要关注的处理被称为CPU 利用率。CPU 利用率是程序或游戏运行时中可用或正在使用的处理量。物理引擎可能占用与游戏其他部分同样多的 CPU 处理。更简单的选择意味着更小的 CPU 利用率。

在运行游戏时,您的目标通常是每秒 30-60 帧,这意味着游戏循环必须在 33-16 毫秒内。图 1显示了一个示例。如果遵循更复杂的解决方案,这意味着会影响可能占用游戏的部分 CPU 使用率的其他特性。尽可能地从任何游戏组件中减少 CPU 使用率,从长远来看,这是很有帮助的。

图 1. 示例 CPU 利用率循环步骤

确定组件

使用 2D 时,可以模拟或伪造许多不同的复杂效果,只要您有足够的引擎处理。在构建游戏时,要考虑您需要使用哪些组件。确定您需要构建哪些组件,这样做可以避免强迫引擎完全计算哪些组件。类似于 图 2所示的点重力这样的效果是很难伪造的,而较小的撞击区域则可以轻松地完成。

图 2. 点重力

构建一个物理引擎

本节讨论了什么组成了物理引擎,以及如何确定其特性。

构建物理引擎的第一个重要步骤是选择特性和操作的顺序。确定使用哪些特性看起来似乎微不足道,但特性有助于形成物理引擎的组件,指出可能会有困难的领域。在示例应用程序中,您要构建一个类似于 图 3 中所显示的游戏引擎。

图 3. 平台游戏

在图 3中的框表示:

  • 玩家:包含对角线的框
  • 获胜条件、目标:实心的黑框
  • 普通平台:实线框
  • 弹性平台:虚线框

除了可视化简单的编程图形,您还可可视化游戏的功能。当玩家达到获胜条件/目标时,他们将会获胜。在构建该游戏平台中,所以您需要一些最基本的物理引擎构建块:

  • 速度、加速度和重力
  • 碰撞检测
  • 弹跳碰撞
  • 正常的碰撞

位置属性用于驱动玩家。碰撞检测允许玩家在游戏中达到目标和左右移动。碰撞类型允许游戏有不同类型的地面。但在游戏中只有一名玩家,并且基本上只有一个动态的对象,所以您可以在代码中减少碰撞量。

现在,游戏的特性和物理方面特性已经确定,您可以开始映射出物理引擎的结构。

选择正确的引擎运行时

物理引擎主要有两种形式:高精度和实时。高精度的引擎用于模拟困难或关键物理计算;实时引擎是您在视频游戏中看到的类型。对于本例中的物理引擎,您将使用一个实时引擎,它会无限地运行其引擎计算,直到要求它停止。

实时引擎提供了两个选项来控制物理引擎的计时:

静态
始终为引擎提供对传递每一帧所预期的不变的时间量。静态实时引擎以不同的速度在不同的计算机上运行,所以它们有不同的行为,这很常见。
动态
将经过的时间馈送到引擎。

在本文中的示例物理引擎需要连续运行,所以您需要设置一个针对引擎运行的无限循环。这种处理模式被称为游戏循环。在本文中,在这个循环中要运行的每一个操作被称为步骤。使用requestAnimationFrame API 来使用动态选项。清单 1显示了运行requestAnimationFrame所需的代码。它使用存储在 Khronos Group CVS Repository 中的一个 polyfill。

清单 1. 包含 polyfill 的requestAnimFrame

物理引擎循环

决定循环中的操作顺序看起来似乎很简单,但它不是一个简单的决定。虽然这一步有若干个不同的可用选项,但您应该根据目前已经确定的特性来设计引擎,如图 4所示。

图 4. 物理循环步骤

清单 2显示,该步骤执行了计算,以便它在单次传递中执行每种类型的计算。此计算的另一种方法是单独执行每个对象的计算,但由于依赖于其他计算,这种访问通常会产生奇怪的结果。

清单 2. 物理循环步骤的伪代码

现在,您已经有了工作流和特性,并确定了要构建的引擎类型,然后您可以开始构建设各个部分。

刚体物理

物理作为一门科学,范围非常广阔,包括几种不同类型的计算。牛顿物理学由常见的位置、速度和加速度计算组成,而电磁学由磁力或电力组成,并且可以用于在一个物理系统中模拟重力。这些领域的物理本身都非常好,但这些计算的复杂性超出了本文的讨论范围。

在决定物理系统时,引擎的构造取决于您想要执行的计算类型。例如,示例引擎将实现刚体物理,即不变形的物理。如果使用刚体物理,可以避免计算在软体动力学中看到的以力为基础的变形,也可以避免在任何形式的多引力系统中都能看到的额外力量修改。

引擎的各组成部分

物理引擎的计算相当复杂,但是,如果知道了模式,它的构造就相当简单。清单 2包括一个高层次的循环步骤伪代码。在该步骤中的每个计算都可以包括它自己的对象或 API。该引擎对象图包括以下主要组件:

  • 物理实体
  • 碰撞检测程序
  • 碰撞求解器
  • 物理引擎

实体是使用引擎的对象、主体或模型,是最不活跃的一部分。相当于 Box2D 中的b2Body类。检测程序和求解器配合工作,首先发现实体之间的碰撞,然后将其转换为应用于受碰撞影响的任何实体的修改。

物理引擎虽然在其整体上涵盖了引擎,但实际上,系统中的每一个阶段都要管理、准备每个组件,并和每个组件通信。图 5显示了物理引擎中的每个元素之间的关系。

图 5. 物理引擎

四个组件构成了我们的引擎背后的主要力量。您要实现的第一个组件是物理实体,它代表在屏幕上的每一个对象。

物理实体

虽然物理实体组成了引擎中最小和最简单的组件,但它是最重要的。正如前面提到的,实体将代表在屏幕上的每个元素。实体,在游戏和物理中均表示对象的状态,它保存与该实体的所有相关元数据,如 清单 3 所示。

清单 3. 实体 JavaScript 对象