用 C++ 从零开始构建游戏引擎

如何制作自己的 C++ 游戏引擎

所以你想了解更多关于游戏引擎的知识并自己编写一个吗?棒极了!为了在您的旅程中为您提供帮助,这里有一些 C++ 库和依赖项的建议,它们将帮助您快速起步。

游戏开发一直是激发我的学生学习更多有关更高级计算机科学主题的动力的好帮手。

我的一位导师塞皮博士曾经说过:“有些人认为游戏是孩子们的东西,但 gamedev 是为数不多的使用标准 CS 课程几乎所有项目的领域之一。”

– Sepideh Chakaveh 博士

一如既往,她是绝对正确的!如果我们揭露隐藏在任何现代游戏开发堆栈下的内容,我们会发现它涉及到许多计算机科学专业学生都熟悉的概念。

游戏开发使用标准 CS 课程中的几个项目

根据游戏的性质,您可能需要更深入地研究更专业的领域,例如分布式系统或人机交互。游戏开发是一项严肃的业务,它可以成为学习严肃的 CS 概念的强大工具。

https://www.youtube.com/embed/qK-GTuhZwUg如何使用 C++ 制作游戏引擎

本文将介绍使用 C++ 创建简单游戏引擎所需的一些基本构建块。我将解释游戏引擎所需的主要元素,并就我喜欢如何从头开始编写游戏引擎给出一些个人建议。

话虽如此,这将不是一个编码教程。我不会涉及太多技术细节或解释所有这些元素是如何通过代码粘合在一起的。如果您正在寻找一本关于如何编写 C++ 游戏引擎的综合视频书,这是一个很好的起点:使用 C++ 和 Lua 创建 2D 游戏引擎

什么是游戏引擎?

如果您正在阅读本文,那么您很可能已经对什么是游戏引擎有了一个很好的了解,甚至可能尝试过自己使用它。但是我们都在同一个页面上,让我们快速回顾一下游戏引擎是什么以及它们帮助我们实现了什么。

游戏引擎是一组优化视频游戏开发的软件工具。这些引擎可以小而极简,只提供一个游戏循环和几个渲染功能,也可以大而全面,类似于开发人员可以编写脚本、调试、自定义关卡逻辑、人工智能、设计、发布、协作的类似 IDE 的应用程序,并最终从头到尾构建游戏,而无需离开引擎。

游戏引擎和游戏框架通常会向用户公开一个API。此 API 允许程序员调用引擎函数并执行硬任务,就好像它们是黑匣子一样。

为了真正理解这个 API 是如何工作的,让我们把它放到上下文中。例如,游戏引擎 API 公开一个名为“ IsColliding() ”的函数并不罕见,开发人员可以调用该函数来检查两个游戏对象是否发生碰撞。程序员不需要知道这个函数是如何实现的,或者正确确定两个形状是否重叠所需的算法是什么。就我们而言,IsColliding函数是一个黑盒子,它会做一些魔术,并在这些对象是否相互碰撞时正确返回truefalse 。这是大多数游戏引擎向其用户公开的功能示例。

if (IsColliding(player, bullet)) {
  lives--;
  if (lives == 0) {
    GameOver();
  }
}

碰撞检测游戏引擎大多数引擎将抽象碰撞检测并将其简单地暴露为真/假函数。

除了编程 API,游戏引擎的另一大职责是硬件抽象。例如,3D 引擎通常构建在专用图形 API 上,如OpenGLVulkanDirect3D。这些 API 为图形处理单元 ( GPU ) 提供了软件抽象。

说到硬件抽象,还有一些低级库(如DirectXOpenALSDL)提供对许多其他硬件元素的抽象和多平台访问。这些库帮助我们访问和处理键盘事件、鼠标移动、网络连接,甚至音频。

游戏引擎的兴起

在游戏行业的早期,游戏是使用自定义渲染引擎构建的,代码的开发是为了尽可能多地从较慢的机器上榨取性能。每个 CPU 周期都至关重要,因此适用于多种场景的代码重用或通用函数并不是开发人员能够负担得起的奢侈品。

随着游戏和开发团队在规模和复杂性方面的增长,大多数工作室最终在他们的游戏之间重用函数和子程序。工作室开发了内部引擎,这些引擎基本上是处理低级任务的内部文件和库的集合。这些功能允许开发团队的其他成员专注于高级细节,如游戏玩法、地图创建和关卡定制。

一些流行的经典引擎是id TechBuildAGI。这些引擎是为了帮助开发特定游戏而创建的,它们允许团队的其他成员快速开发新关卡、添加自定义资产和动态自定义地图。这些定制引擎也被用来为他们的原始游戏修改或创建扩展包。

Id Software 开发了 id Tech。id Tech 是不同引擎的集合,其中每次迭代都与不同的游戏相关联。经常听到开发人员将 id Tech 0 描述为“Wolfenstein3D 引擎”,将 id Tech 1 描述为“Doom 引擎”,将 id Tech 2 描述为“Quake 引擎”。

Build 是另一个帮助塑造 90 年代游戏历史的引擎示例。它由Ken Silverman创建,旨在帮助定制第一人称射击游戏。与 id Tech 发生的事情类似,Build 随着时间的推移而发展,它的不同版本帮助程序员开发了Duke Nukem 3DShadow Warrior 和Blood等游戏。这些可以说是使用 Build 引擎创建的最受欢迎的游戏,通常被称为“三巨头”。构建引擎 dukem nukem由 Ken Silverman 开发的构建引擎在 2D 模式下编辑关卡。

90 年代游戏引擎的另一个例子是“Manic Mansion 的脚本创建实用程序”(SCUMM)。SCUMM 是LucasArts开发的引擎,它是许多经典点击游戏(如Monkey IslandFull Throttle )的基础。

人渣游戏全油门

Full Throttle 的对话框和操作使用 SCUMM 脚本语言进行管理。

随着机器的发展并变得更强大,游戏引擎也在发展。现代引擎包含功能丰富的工具,这些工具需要快速的处理器速度、惊人的内存量和专用显卡。

有了备用电源,现代发动机用机器循环换取更多抽象。这种权衡意味着我们可以将现代游戏引擎视为通用工具,以低成本和较短的开发时间创建复杂的游戏。

为什么要制作游戏引擎?

这是一个非常常见的问题,不同的游戏程序员会对这个话题有自己的看法,具体取决于正在开发的游戏的性质、他们的业务需求以及所考虑的其他驱动力。

开发人员可以使用许多免费、强大且专业的商业引擎来创建和部署自己的游戏。有这么多游戏引擎可供选择,为什么会有人费心从头开始制作游戏引擎?

我写了一篇名为“我应该制作游戏引擎还是使用现有引擎?”的博文。,在这里我解释了程序员可能决定从头开始制作游戏引擎的一些原因。在我看来,主要原因是:

  • 学习机会:对游戏引擎如何在底层工作的低层次了解可以帮助您成长为一名开发人员。
  • 工作流程控制:您可以更好地控制游戏的特殊方面,并调整解决方案以满足您的工作流程需求。
  • 定制:您将能够为独特的游戏需求定制解决方案。
  • 极简主义:较小的代码库可以减少较大游戏引擎带来的开销。
  • 创新:您可能需要实现一些全新的东西或针对其他引擎不支持的非正统硬件。

假设您对游戏引擎的教育吸引力感兴趣,我将继续我们的讨论。从头开始创建一个小型游戏引擎是我向所有 CS 学生强烈推荐的。

如何制作游戏引擎

因此,在快速讨论了使用和开发游戏引擎的动机之后,让我们继续讨论游戏引擎的一些组件,并学习如何自己编写一个。

1. 选择编程语言

我们面临的首要决定之一是选择我们将用于开发核心引擎代码的编程语言。我见过用原始汇编、C、C++ 甚至高级语言(如 C#、Java、Lua 甚至 JavaScript)开发的引擎!

用于编写游戏引擎的最流行的语言之一是 C++。C++ 编程语言将速度与使用面向对象编程 (OOP) 和其他编程范式的能力相结合,帮助开发人员组织和设计大型软件项目。

由于我们开发游戏时性能通常很重要,因此 C++ 具有作为编译语言的优势。编译语言意味着最终的可执行文件将在目标机器的处理器上本地运行。对于大多数现代游戏机(如 PlayStation 或 Xbox),还有许多专用的 C++ 库和开发工具包。

开发人员可以使用 Microsoft 提供的 C++ 库访问 Xbox 控制器。

说到性能,我个人不推荐使用虚拟机、字节码或任何其他中间层的语言。除了 C++,适合编写核心游戏引擎代码的一些现代替代方案是RustOdinZig

对于本文的其余部分,我的建议将假设读者想要使用 C++ 编程语言构建一个简单的游戏引擎。

2.硬件访问

在较旧的操作系统中,例如 MS-DOS,我们通常可以戳内存地址并访问映射到不同硬件组件的特殊位置。例如,用某种颜色“绘制”像素所需要做的就是加载一个特殊的内存地址,该地址的数字代表我的 VGA 调色板的正确颜色,然后显示驱动程序将该更改转换为物理像素CRT 显示器。

随着操作系统的发展,它们开始负责保护硬件免受程序员的伤害。现代操作系统不允许代码修改操作系统为我们的进程提供的允许地址之外的内存位置。

例如,如果您使用的是 Windows、macOS、Linux 或 *BSD,则需要向操作系统请求正确的权限才能在屏幕上绘制和绘制像素或与任何其他硬件组件通信。即使是在操作系统桌面上打开窗口这样简单的任务,也必须通过操作系统 API 来执行。

因此,运行一个进程、打开一个窗口、在屏幕上渲染图形、在该窗口内绘制像素,甚至从键盘读取输入事件都是操作系统特定的任务。

SDL 是一个非常流行的有助于多平台硬件抽象的库。我个人喜欢在教授游戏开发课程时使用 SDL,因为使用 SDL,我不需要为 Windows 创建一个代码版本,为 macOS 创建另一个版本,为 Linux 学生创建另一个版本。SDL 不仅适用于不同的操作系统,而且适用于不同的 CPU 架构(英特尔、ARM、Apple M1 等)。SDL 库抽象了低级硬件访问并“翻译”我们的代码以在这些不同平台上正常工作。

这是使用 SDL 在操作系统上打开窗口的最小代码片段。为了简单起见,我没有处理错误,但下面的代码对于 Windows、macOS、Linux、BSD 甚至 RaspberryPi 都是相同的。

#include <SDL2/SDL.h>

void OpenNewWindow() {
  SDL_Init(SDL_INIT_VIDEO);
  SDL_Window* window = SDL_CreateWindow("My Window", 0, 0, 800, 600, 0);
  SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);
}

但 SDL 只是我们可以用来实现这种多平台硬件访问的库的一个示例。SDL 是 2D 游戏以及将现有代码移植到不同平台和控制台的流行选择。另一个主要用于 3D 游戏和 3D 引擎的多平台库的流行选项是 GLFW。GLFW 库与 OpenGL 和 Vulkan 等加速 3D API 的通信非常好。

3. 游戏循环

打开操作系统窗口后,我们需要创建一个受控游戏循环

简而言之,我们通常希望我们的游戏以每秒 60 帧的速度运行。帧率可能会因游戏而异,但从长远来看,用胶片拍摄的电影以 24 FPS 的速度运行(每秒钟有 24 张图像闪过你的眼睛)。

游戏循环在游戏过程中不断运行,并且在循环的每一次通过,我们的引擎都需要运行一些重要的任务。传统的游戏循环必须:

  • 无阻塞地处理输入事件
  • 更新当前帧的所有游戏对象及其属性
  • 在屏幕上渲染所有游戏对象和其他重要信息
while (isRunning) {
  Input();
  Update();
  Render();
}

这是一个可爱的while循环。我们完了吗?绝对不!

一个原始的 C++ 循环对我们来说还不够好。游戏循环必须与现实世界的时间有某种关系。毕竟,游戏中的敌人在任何机器上都应该以相同的速度移动,而不管它们的 CPU 时钟速度如何。

控制这个帧率并将其设置为固定数量的 FPS,实际上是一个非常有趣的问题。它通常要求我们跟踪帧之间的时间并执行一些合理的计算,以确保我们的游戏以至少 30 FPS 的帧率平稳运行。

4.输入

我无法想象没有从用户那里读取某种输入事件的游戏。这些可以来自键盘、鼠标、游戏手柄或 VR 设备。因此,我们必须在游戏循环中处理和处理不同的输入事件。

要处理用户输入,我们必须请求访问硬件事件,这必须通过操作系统 API 执行。好消息是我们可以使用多平台硬件抽象库(SDL、GLFW、SFML 等)来为我们处理用户输入。

如果我们使用 SDL,我们可以轮询事件并使用几行代码进行相应的处理。

void Input() {
  SDL_Event event;
  while (SDL_PollEvent(&event)) {
    switch (event.type) {
      case SDL_KEYDOWN:
        if (event.key.keysym.sym == SDLK_SPACE) {
          ShootMissile();
        }
        break;
    }
  }
}

同样,如果我们使用 SDL 之类的跨平台库来处理输入,我们不必过多担心特定于操作系统的实现。无论我们的目标平台是什么,我们的 C++ 代码都应该是相同的。

在我们有一个有效的游戏循环和处理用户输入的方法之后,是时候开始考虑在内存中组织我们的游戏对象了。

5. 在内存中表示游戏对象

在设计游戏引擎时,我们需要设置数据结构来存储和访问游戏对象。

程序员在构建游戏引擎时会使用多种技术。一些引擎可能使用类和继承的简单面向对象方法,而其他引擎可能将其对象组织为实体和组件。

如果您的目标之一是更多地了解算法和数据结构,我建议您尝试自己实现这些数据结构。如果您使用 C++,一种选择是使用STL(标准模板库)并利用它附带的许多数据结构(向量、列表、队列、堆栈、映射、集合等)。C++ STL 严重依赖模板,因此这是练习使用模板并在实际项目中看到它们的好机会。

当您开始阅读有关游戏引擎架构的更多内容时,您会发现游戏使用的最流行的设计模式之一是基于实体组件的。实体组件设计将我们游戏场景的对象组织为实体(Unity 称之为“游戏对象”,Unreal 称之为“演员”)和组件(我们可以添加或附加到实体的数据)。

要了解实体和组件如何协同工作,请考虑一个简单的游戏场景。实体将是我们的主要玩家,敌人、地板、射弹和组件将是我们“附加”到实体的重要数据块,如位置、速度、刚体对撞机等。

一种流行的游戏引擎设计模式是将游戏元素组织为实体和组件。

我们可以选择附加到实体的组件的一些示例是:

  • 位置组件:跟踪我们实体在世界中的 xy 位置坐标(或 3D 中的 xyz)。
  • 速度分量:跟踪实体在 xy 轴(或 3D 中的 xyz)上移动的速度。
  • Sprite 组件:它通常存储我们应该为某个实体渲染的 PNG 图像。
  • 动画组件:跟踪实体的动画速度,以及动画帧如何随时间变化。
  • Collider 组件:这通常与刚体的物理特性有关,并定义实体的碰撞形状(边界框、边界圆、网格对撞机等)。
  • 健康组件:存储实体的当前健康值。这通常只是一个数字,在某些情况下是一个百分比值(例如健康条)。
  • 脚本组件:有时我们可以将脚本组件附加到我们的实体,它可能是我们的引擎必须在幕后解释和执行的外部脚本文件(Lua、Python 等)。

这是一种非常流行的表示游戏对象和重要游戏数据的方式。我们有实体,我们将不同的组件“插入”到我们的实体中。

有许多书籍和文章探讨我们应该如何实现实体组件设计,以及我们应该在这个实现中使用哪些数据结构。我们使用的数据结构以及我们如何访问它们对我们游戏的性能有直接影响,你会听到开发人员提到诸如面向数据的设计实体组件系统(ECS)、数据局部性以及许多其他想法与我们的游戏数据如何存储在内存中以及我们如何有效地访问这些数据有关。

在内存中表示和访问游戏对象可能是一个复杂的话题。在我看来,您可以手动编写一个简单的实体组件实现,也可以简单地使用现有的第三方 ECS 库。

我们可以将一些流行的即用型 ECS 库选项包含在我们的 C++ 项目中,并开始创建实体和附加组件,而不必担心它们是如何在后台实现的。C++ ECS 库的一些示例是EnTTFlecs

我个人建议对编程很认真的学生至少尝试手动实现一次非常简单的 ECS。即使您的实现并不完美,从头开始编写 ECS 系统也会迫使您考虑底层数据结构并考虑其性能。

现在,严肃的谈话!一旦您完成了自定义的 ad-hoc ECS 实施,我建议您只使用一些流行的第三方 ECS 库(EnTT、Flecs 等)。这些是经过行业多年开发和测试的专业库。它们可能比我们自己从零开始的任何东西都要好得多。

综上所述,专业的 ECS 很难从零开始实施。它作为一个学术练习是有效的,但是一旦你完成了你的小型学习项目,只需选择一个经过良好测试的第三方 ECS 库并将其添加到你的游戏引擎代码中。

6. 渲染

好吧,看起来我们的游戏引擎的复杂性正在缓慢增长。既然我们已经讨论了在内存中存储和访问游戏对象的方法,我们可能需要谈谈我们如何在屏幕上渲染对象。

第一步是考虑我们将使用我们的引擎创建的游戏的性质。我们是否正在创建一个只开发 2D 游戏的游戏引擎?如果是这种情况,我们需要考虑渲染精灵、纹理、管理图层,并可能利用显卡加速。好消息是 2D 游戏通常比 3D 游戏简单,而且 2D 数学比 3D 数学容易得多。

如果您的目标是开发2D引擎,则可以使用 SDL 来帮助进行多平台渲染。SDL 抽象加速 GPU 硬件,可以在我们的游戏窗口中解码和显示 PNG 图像、绘制精灵和渲染纹理。

现在,如果您的目标是开发3D引擎,那么我们需要定义如何将一些额外的 3D 信息(顶点、纹理、着色器等)发送到 GPU。您可能希望对图形硬件使用软件抽象,最流行的选项是OpenGLDirect3DVulkanMetal。使用哪个 API 的决定可能取决于您的目标平台。例如,Direct3D 将为 Microsoft 应用程序提供动力,而 Metal 将仅适用于 Apple 产品。

3D 应用程序通过图形管道处理 3D 数据来工作。该管道将​​规定您的引擎必须如何将图形信息发送到 GPU(顶点、纹理坐标、法线等)。图形 API 和管道还将决定我们应该如何编写可编程着色器来转换和修改 3D 场景的顶点和像素。

可编程着色器决定 GPU 应如何处理和显示 3D 对象。我们可以为每个顶点和每个像素(片段)设置不同的脚本,它们控制反射、平滑度、颜色、透明度等。

说到 3D 对象和顶点,最好将读取和解码不同网格格式的任务委托给库。大多数第三方 3D 引擎应该了解许多流行的 3D 模型格式。文件的一些示例包括 .OBJ、Collada、FBX 和 DAE。我的建议是从 .OBJ 文件开始。有一些经过良好测试和良好支持的库可以使用 C++ 处理 OBJ 加载。TinyOBJLoaderAssImp是许多游戏引擎使用的绝佳选择。

7. 物理学

当我们将实体添加到我们的引擎时,我们可能还希望它们在我们的场景中移动、旋转和反弹。游戏引擎的这个子系统是物理模拟。这可以手动创建,也可以从现有的即用型物理引擎导入。

在这里,我们还需要考虑我们想要模拟什么类型的物理。2D 物理通常比 3D 简单,但物理模拟的基础部分与 2D 和 3D 引擎非常相似。

如果您只是想在您的项目中包含一个物理库,有几个不错的选项可供选择。

对于 2D 物理,我建议查看Box2DChipmunk2D。对于专业且稳定的 3D 物理模拟,一些好名字是PhysXBullet等库。如果物理稳定性和开发速度对您的项目至关重要,那么使用第三方物理引擎总是一个不错的选择。

Box2D 是一个非常流行的物理库选项,您可以将其与游戏引擎一起使用。

作为一名教育工作者,我坚信每个程序员在他们的职业生涯中都应该至少学习一次如何编写一个简单的物理引擎。再一次,您不需要编写完美的物理模拟,而是专注于确保对象可以正确加速,并且可以将不同类型的力应用于您的游戏对象。而且一旦移动完成,你也可以考虑实现一些简单的碰撞检测和碰撞解决。

如果您想了解有关物理引擎的更多信息,可以使用一些不错的书籍和在线资源。对于 2D 刚体物理,您可以查看Box2D 源代码和 Erin Catto 的幻灯片。但是,如果您正在寻找有关游戏物理的综合课程,从零开始的 2D 游戏物理可能是一个不错的起点。

使用 C++ 创建 2D 物理引擎

pikuma.com

如果您想了解 3D 物理以及如何实现强大的物理模拟,另一个很好的资源是David Eberly的“游戏物理”一书。

David Eberly 的游戏物理学

8. 用户界面

当我们想到 Unity 或 Unreal 等现代游戏引擎时,我们会想到复杂的用户界面,其中包含许多面板、滑块、拖放选项和其他漂亮的 UI 元素,可帮助用户自定义我们的游戏场景。UI 允许开发人员添加和删除实体、即时更改组件值以及轻松修改游戏变量。

为了清楚起见,我们讨论的是用于工具的游戏引擎 UI,而不是我们向游戏用户展示的用户界面(如对话框屏幕和菜单)。

请记住,游戏引擎不一定需要嵌入编辑器,但由于游戏引擎通常用于提高生产力,因此拥有友好的用户界面将帮助您和其他团队成员快速自定义关卡和游戏的其他方面游戏场景。

从头开始开发 UI 框架可能是初学者程序员可以尝试添加到游戏引擎中的最烦人的任务之一。您必须创建按钮、面板、对话框、滑块、单选按钮、管理颜色,还需要正确处理该 UI 的事件并始终保持其状态。不好玩!将 UI 工具添加到您的引擎会增加您的应用程序的复杂性,并为您的源代码添加大量的噪音。

如果您的目标是为您的引擎创建 UI 工具,我的建议是使用现有的第三方 UI 库。快速的 Google 搜索将显示最流行的选项是Dear ImGuiQtNuklear

ImGui 是一个强大的 UI 库,被许多游戏引擎用作编辑工具。

Dear ImGui 是我的最爱之一,因为它允许我们快速设置引擎工具的用户界面。ImGui 项目使用一种称为“即时模式 UI ”的设计模式,它被广泛用于游戏引擎,因为它利用加速 GPU 渲染与 3D 应用程序进行了良好的通信。

总之,如果你想在你的游戏引擎中添加 UI 工具,我的建议是简单地使用 Dear ImGui。

9. 脚本

随着我们游戏引擎的发展,一个流行的选择是使用简单的脚本语言来启用关卡定制。

这个想法很简单;我们将脚本语言嵌入到我们的原生 C++ 应用程序中,这种更简单的脚本语言可以被非专业程序员用来编写实体行为、AI 逻辑、动画和我们游戏的其他重要方面的脚本。

一些流行的游戏脚本语言是LuaWrenC#PythonJavaScript。所有这些语言的运行水平都比我们的原生 C++ 代码高得多。使用脚本语言编写游戏行为脚本的人无需担心诸如内存管理或核心引擎如何工作的其他低级细节之类的事情。他们需要做的就是编写关卡脚本,我们的引擎知道如何解释脚本并在幕后执行艰巨的任务。

Lua 是一种快速且小型的脚本语言,可以轻松地与 C 和 C++ 项目集成。

我最喜欢的脚本语言是 Lua。Lua 体积小、速度快,并且极易与 C 和 C++ 原生代码集成。此外,如果我正在使用 Lua 和“现代”C++,我喜欢使用名为Sol的包装库。Sol 库帮助我开始使用 Lua,并提供了许多帮助函数来改进传统的 Lua C-API。

如果我们启用脚本,我们几乎可以开始在我们的游戏引擎中讨论更高级的主题。脚本帮助我们定义 AI 逻辑、自定义动画帧和运动以及其他不需要存在于我们的原生 C++ 代码中并且可以通过外部脚本轻松管理的游戏行为。

10. 音频

您可能会考虑为游戏引擎添加支持的另一个元素是音频。

毫不奇怪,再一次,如果我们想要戳音频值并发出声音,我们需要通过操作系统访问音频设备。再一次,由于我们通常不想编写特定于操作系统的代码,我将推荐使用一个抽象音频硬件访问的多平台库。

SDL 等多平台库具有可帮助您的引擎处理音乐和音效等内容的扩展。

但是,现在说正经的!我强烈建议您仅在引擎的其他部分已经协同工作后处理音频。发出声音文件很容易实现,但是一旦我们开始处理音频同步,将音频与动画、事件和其他游戏元素联系起来,事情就会变得一团糟。

如果您真的是手动操作,由于多线程管理,音频可能会很棘手。可以做到,但如果你的目标是编写一个简单的游戏引擎,这是我喜欢委托给专门库的一部分

您可以考虑与游戏引擎集成的一些好的音频库和工具是SDL_MixerSoLoudFMOD

Tiny Combat Arena使用 FMOD 库来实现多普勒和压缩等音频效果。

11. 人工智能

我将在我们的讨论中包括的最后一个子系统是 AI。我们可以通过脚本实现人工智能,这意味着我们可以将人工智能逻辑委托给关卡设计师编写脚本。另一种选择是将适当的 AI 系统嵌入到我们的游戏引擎核心原生代码中。

在游戏中,人工智能用于生成对游戏对象的响应、自适应或类似智能的行为。大多数 AI 逻辑被添加到非玩家角色(NPC、敌人)中,以模拟类人智能。

敌人是游戏中人工智能应用的一个流行例子。当敌人在地图上追逐物体时,游戏引擎可以创建对寻路算法或有趣的类人行为的抽象。

一本关于游戏人工智能理论和实现的综合性书籍被 Ian Millington称为AI for Games 。

伊恩·米灵顿 (Ian Millington ) 的游戏人工智能

不要试图一次做所有事情

好吧!我们刚刚讨论了一些重要的想法,您可以考虑将它们添加到简单的 C++ 游戏引擎中。但在我们开始将所有这些部分粘合在一起之前,我只想提一些非常重要的事情。

开发游戏引擎最难的部分之一是大多数开发者不会设定明确的界限,也没有“终点线”的感觉。换句话说,程序员会开始一个游戏引擎项目,渲染对象,添加实体,添加组件,然后这一切都是下坡路。如果他们不定义边界,那么很容易开始添加越来越多的功能并失去对大局的跟踪。如果发生这种情况,那么游戏引擎很有可能永远不会出现。

除了缺乏边界之外,当我们看到代码以闪电般的速度在我们眼前增长时,很容易不知所措。游戏引擎项目的复杂性可能会迅速增长,并且在几周内,您的 C++ 项目可能会有多个依赖项,需要复杂的构建系统,并且随着引擎中添加更多功能,您的代码的整体可读性会下降

我在这里的第一个建议是在编写实际游戏始终编写游戏引擎。以实际游戏为出发点开始和结束游戏的第一次迭代。这将帮助您设置限制并为您需要完成的内容定义清晰的路径。尽你最大的努力坚持下去,不要试图在此过程中改变要求。

花点时间专注于基础知识

如果您正在创建自己的游戏引擎作为学习练习,请享受小胜利!

大多数学生在项目开始时会非常兴奋,随着时间的推移,焦虑开始出现。如果我们从头开始创建游戏引擎,尤其是在使用像 C++ 这样复杂的语言时,很容易不知所措并失去一些动力。

我想鼓励你对抗那种“与时间赛跑”的感觉。深呼吸,享受小胜利。例如,当您学习如何在屏幕上成功显示 PNG 纹理时,请尽情享受那一刻,并确保您了解自己的所作所为。如果您成功地检测到两个物体之间的碰撞,请享受那一刻并反思您刚刚获得的知识。

专注于基础知识并拥有这些知识。不管一个概念多么小或多么简单,拥有它!其他一切都是自我。