你想了解更多关于游戏引擎的知识、并自己来写一个吗?
这可是非常牛皮的一件事。为了帮助你学习,这里有一些C++库和依赖项的推荐,可以帮助你快速上手。
游戏开发一直是我的学生学习更高级计算机科学主题的好帮手。我的一位导师Sepi博士曾经说过:
“有些人认为游戏是孩子的东西,但游戏开发是少数几个几乎使用了标准CS课程所有内容的领域之一。”- Sepideh Chakaveh博士
她说的完全不假!如果将隐藏在现代游戏开发栈下的内容暴露出来,我们会发现,它触及了许多计算机科学专业学生所熟悉的概念。
算法与数据结构
游戏程序员需要在内存中表示游戏对象,并在考虑性能(数据结构和数据局部性)的前提下访问对应的数据。
数学
游戏开发需要不少应用数学领域的助力。很多游戏都利用了离散数学、线性代数、微积分、概率、数值分析以及其他数学分支。
计算机体系结构
引擎开发人员需要很好地理解目标机器的结构,以获得尽可能快的速度。
图形
图形技术对游戏开发至关重要。着色器、顶点、并行加速、2D&3D渲染和GPU api都是开发者使用的图形技术的典型例子。
编译器和形式语言
引擎通常需要把脚本语言暴露给关卡设计师,这通常意味着解释某种高级定制语法。
人工智能
游戏对象(敌人和npc)通常需要表现出类似人类的智能。典型例子是寻路算法、追踪、机器学习和可见性方法。
网络
多人游戏和社交整合在现代游戏中很常见。大多数游戏必须利用好设备之间的互联,通过网络协议发送/接收多个数据包。
UI和UX
UI和UX也是游戏开发技术的重要组成部分,这意味着向用户展示信息以及提供合适的反馈。
根据你所制作的游戏的本质,你可能还需要深入到更专业的领域,例如分布式系统或人机交互。游戏开发是一项严肃的业务,它也完全可以成为学习严肃的CS概念的强大工具。
本文将介绍使用C++创建简单游戏引擎所需的一些基本构建模块。我将解释游戏引擎中所需要的主要元素,并就如何从头开始编写游戏引擎给出一些个人建议。
话虽如此,这并非一个编程教程。我不会介绍太多的技术细节,也不会解释如何用代码将所有这些元素粘合在一起。如果你正在寻找一本关于编写C++游戏引擎的综合教程,这是一个很好的起点:
什么是游戏引擎?
如果你正在阅读这篇文章,那么你可能已经很清楚什么是游戏引擎,甚至可能自己也用过。但为了达成共识,我们来快速回顾一下什么是游戏引擎,以及它们能帮助我们实现什么。
游戏引擎是一组优化游戏开发的软件工具。它们可以是小型且极简的,只提供一个游戏循环和一些渲染功能,也可以是大型且综合性的,类似于IDE类应用,在当中开发者可以编写脚本,调试,定制关卡逻辑,AI,设计,发布,协作,并最终从头到尾构建游戏,整个过程不需要离开引擎。
游戏引擎和游戏框架通常会将API暴露给用户。API允许程序员调用引擎函数执行困难的任务,就像它们是黑盒一样。
为了真正理解这样的API是如何工作的,先来点铺垫。例如,游戏引擎API把名为“IsColliding()”的函数暴露出来并不罕见,开发者可以调用该函数来检查两个游戏对象是否发生碰撞。程序员不需要知道这个函数是如何实现的,也不需要知道正确确定两个形状是否重叠所需的算法是什么。就我们所关心的而言,IsColliding函数就是一个黑盒,它做了一些神奇的事情,并在这些对象可能相互碰撞时正确地返回true或false。这是大多数游戏引擎都会向用户暴露的功能。
if (IsColliding(player, bullet)) {
lives--;
if (lives == 0) {
GameOver();
}
}
除了编程API,游戏引擎的另一个重要职责是硬件抽象。例如,3D引擎通常构建在专用的图形API(例如OpenGL、Vulkan或Direct3D)之上。这些API为图形处理单元(GPU)提供了抽象。
说到硬件抽象,还有一些低级库(如DirectX、OpenAL和SDL)提供了对许多其他硬件元素的抽象和多平台访问。这些库帮我们访问和处理键盘事件、鼠标移动、网络连接,甚至音频。
游戏引擎的崛起
在游戏行业的早期,游戏是用自定义的渲染引擎构建的,代码的开发是为了从较慢的机器上榨取尽可能多的性能。每个CPU时钟周期都是至关重要的,因此代码重用或适用于多种场景的通用函数并非开发者可以负担得起的奢侈品。
而随着游戏、开发团队的规模和复杂性的增长,大多数工作室最终都会在游戏之间重用功能和子程序。工作室开发的内部引擎基本上是处理低级任务的内部文件和库的集合。这些功能让开发团队的其他成员能够专注于游戏玩法、地图创建和关卡定制等高级细节。
一些流行的经典引擎包括id Tech、Build和AGI。创建这些引擎是为了帮助特定游戏的开发,它们让团队其他成员能快速开发新关卡,添加自定义资产,以及动态自定义地图。这些自定义引擎也用于mod或为原始版本创建扩展包。
Id Tech由Id Software开发。Id Tech是不同引擎的集合,每次迭代都与不同的游戏相关联。我们经常听到开发者将id Tech 0描述为“德军总部3D引擎”,将id Tech 1描述为“毁灭战士引擎”,id Tech 2描述为“Quake引擎”。
Build是另一个塑造90年代游戏历史的引擎例子。它由Ken Silverman创造,用于帮助定制第一人称射击游戏。与id Tech类似,Build也随着时间的推移而发展,其不同版本帮助程序员开发了《毁灭公爵3D》、《影子武士》和《Blood》等游戏。它们可以说是用Build引擎创建的最受欢迎的游戏,通常被称为“三巨头”。
90年代的另一个游戏引擎例子是“Manic Mansion的脚本创建工具”(SCUMM)。SCUMM是LucasArts开发的引擎,它是许多经典的点击类游戏(如《猴岛小英雄》和《极速天龙》)的基础。
SCUMM脚本语言用于管理《极速天龙》中的对话和操作。
随着机器进化得越来越强大,游戏引擎也随之发展。现代引擎装满了功能丰富的工具,这些工具需要更快的处理器速度、巨量的内存和专用显卡。
有了多余的动力,现代引擎用时钟周期换取了更多的抽象。这种权衡意味着我们可以将现代游戏引擎视为以低成本和短开发周期创作复杂游戏的通用工具。
问题来了,我们为什么要制作游戏引擎呢?
这是一个非常常见的问题,不同的游戏程序员会根据所开发游戏的性质、业务需求和其他驱动力,对这个议题有着自己的看法。
开发者可以使用很多免费、强大且专业的商业引擎来创作和部署自己的游戏。既然有这么多游戏引擎可供选择,为什么还会有人不厌其烦地从头开始制作游戏引擎呢?
我在之前的一篇博文里,解释了程序员决定从头开始制作游戏引擎的一些可能原因。在我看来,最主要的原因是:
学习机会:对游戏引擎工作原理的底层理解可以帮助你成长为一名开发者。
工作流控制:你可以更好地控制你自己游戏的特殊要素,并根据自己的工作流需求调整解决方案。
自定义:你将能够为独特的游戏需求量身定制解决方案。
极简化:较小的代码库可以减少大型游戏引擎带来的开销。
创新:你可能需要实现一些全新的东西,或者瞄准其他引擎不支持的非传统硬件。
在接下来的讨论中,我将假设你对游戏引擎在教育学习层面的吸引力感兴趣。从头开始创造一个小型游戏引擎是我向所有CS学生强烈推荐的内容。
如何制作游戏引擎
所以,在快速讨论了使用和开发游戏引擎的动机之后,我们继续来讨论游戏引擎的一些组件,并学习如何自己编写一个游戏引擎。
1. 选择编程语言
我们面临的第一个抉择,是挑选用于开发核心引擎代码的编程语言。我见过用原始汇编、C、C++以及高级语言(如c#、Java、Lua,甚至JavaScript)来开发引擎!
编写游戏引擎最流行的语言之一是C++。C++编程语言将速度与运用OOP及其他编程范式的能力相结合。这些编程范式能帮助开发者组织和设计大型软件项目。
当我们开发游戏时,性能通常是非常重要的,C++具有编译语言的优势。编译语言意味着最终的可执行文件将在目标机器的处理器上原生运行。还有许多专门的C++库和开发工具包,适用于大多数现代主机,如PlayStation或Xbox。
说到性能,我个人不推荐使用虚拟机、字节码或任何其他中间层的语言。除了C++,一些适合编写核心游戏引擎代码的现代替代方法是Rust、Odin和Zig。
在本文的剩余部分,我的建议将假设读者希望使用C++编程语言构建一个简单的游戏引擎。
2. 硬件访问
在老式的操作系统(如MS-DOS)中,我们通常可以直接定位到内存地址并访问映射到不同硬件组件的特殊位置。例如,我要做的就是用表示VGA调色板正确颜色的数字来加载一个特殊的内存地址,然后显示驱动程序将更改为物理像素的内容转换到CRT监视器中。
随着操作系统的进化,它们开始负责保护硬件不受程序员的侵害。现代操作系统不允许代码修改操作系统允许给到进程的地址之外的内存位置。
例如,如果你使用的是Windows、macOS、Linux或BSD,则需要向操作系统请求正确的权限,以便在屏幕上绘制像素或与任何其他硬件组件对话。即使是形如“在操作系统桌面上打开一个窗口”这样的简单任务,也必须通过操作系统API来执行。
因此,运行进程、打开窗口、在屏幕上呈现图形、在窗口内绘制像素,甚至从键盘读取输入事件都是特定于操作系统的任务。
SDL是一个非常流行的库,可以帮忙实现多平台硬件抽象。我个人喜欢在教授游戏开发课程时使用SDL,因为用SDL,我不需要为Windows学生创建一个版本的代码,为macOS学生创建一个版本的代码,又为Linux学生创建另一个版本的代码。SDL不仅是不同操作系统之间的桥梁,也是不同CPU架构(Intel、ARM、Apple M1等)之间的桥梁。SDL库抽象了底层硬件访问,并“翻译”了我们的代码,以在这些不同的平台上正确工作。
下面是“用SDL在操作系统上打开一个窗口”的一小段代码。下面的代码对于Windows、macOS、Linux、BSD甚至RaspberryPi都是一样的。
#include
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只是我们可以用来实现这种多平台硬件访问的库的众多例子之一。对于2D游戏和将现有代码移植到不同平台,SDL是一个流行的选择。另一个流行的多平台库选项是GLFW,主要用于3D游戏和3D引擎。GLFW库与加速3D api(如OpenGL和Vulkan)之间的通信非常好。
3.游戏循环
打开操作系统窗口后,我们需要创建一个可控制的游戏循环。
简而言之,我们通常希望游戏以每秒60帧的速度运行。帧速率可能因游戏而异,但从整体来看,电影胶片拍摄的帧速率为24 FPS(每秒钟闪过24张图像)。
游戏循环在gameplay中持续运行,在每次循环中,我们的引擎都需要跑一些重要的任务。传统的游戏循环必须:
处理输入事件,不阻塞
更新当前帧的所有游戏对象及其属性
在屏幕上渲染出所有游戏对象和其他重要信息
while (isRunning) {
Input();
Update();
Render();
}
这是一个很袖珍的while循环。完事儿了吗?明显没有。
原始的C++循环对我们来说还不够好。游戏循环必须与现实世界的时间有某种关系。毕竟游戏中的敌人在任何机器上都应该以相同的速度移动,不管它们的CPU时钟速度怎样。
控制帧率并将其设置为固定FPS,实际上是一个非常有趣的问题。这通常需要我们跟踪帧与帧之间的时间,并进行一些合理的计算,以确保我们的游戏在至少30帧/秒的帧速率下平稳运行。
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(标准模板库),充分利用它自带的许多数据结构(向量、列表、队列、堆栈、map、set等)。C++ STL在很大程度上依赖于模板,所以这是一个练习使用模板并在实际项目中接触它们的好机会。
而当你开始阅读更多关于游戏引擎架构的内容时,你便会发现,游戏中最受欢迎的设计模式是基于实体和组件。实体组件设计将游戏场景的对象组织为实体(Unity称之为“游戏对象”,而虚幻称之为“actor”)和组件(我们能添加或附到实体上的数据)。
要理解实体和组件如何协同工作,请考虑一个简单的游戏场景。实体将会是主要玩家、敌人、地板、抛射物,而组件将是我们“附加”到实体上的重要数据块,如位置、速度、刚体碰撞器等等。
一种流行的游戏引擎设计模式便是将游戏元素组织为实体和组件
下面列出可以选择附加到实体上的组件的部分例子:
位置组件:跟踪实体在游戏世界中的x-y位置坐标(或3D中的x-y-z)。
速度组件:跟踪实体在x-y轴(或3D中的x-y-z轴)中的移动速度。
精灵组件:它通常存储我们应该为某个实体呈现的PNG图像。
动画组件:跟踪实体的动画速度,以及动画帧如何随时间变化。
碰撞器组件:这通常与刚体的物理特性有关,并定义了实体的碰撞形状(边界框、边界圆、网格碰撞器等)。
健康组件:存储实体的当前健康值。这通常只是一个数字,在某些情况下是一个百分比值(例如一个血条)。
脚本组件:有时我们可以将脚本组件附加到实体,这可能是引擎必须在幕后解释和执行的外部脚本文件(Lua, Python等)。
这是表示游戏对象和重要游戏数据的一种非常流行的方式。先是实体,然后会将不同的组件“插入”到实体中。
许多书籍和文章都探讨了我们应该如何实现实体组件设计,以及在这样的实现中应该使用什么数据结构。我们用到的数据结构以及访问它们的方式直接影响着游戏性能。你可能经常听到开发者提到诸如“面向数据的设计”,“实体-组件-系统”(ECS),“数据本地性”和其他许多与游戏数据如何存储在内存中、以及如何有效访问这些数据有关的idea。
表示和访问内存中的游戏对象可以是一个复杂的主题。在我看来,你可以手动编写一个简单的实体组件实现,也可以简单使用现有的第三方ECS库。
我们可以在C++项目中包含一些流行的现成ECS库,并开始创建实体和附加组件,而不必担心它们在底层是如何实现的。形如EnTT和Flecs都是C++ ECS库的一些典型例子。
我个人建议那些认真对待编程的学生至少尝试一次手动实现简易版ECS。即使你的实现并不完美,从头开始编写ECS系统也会迫使你考虑底层数据结构以及其性能。
现在,严肃来讲,一旦你完成了自定义ECS实现,我会鼓励你使用一些流行的第三方ECS库(EnTT、fecs等)。这些是经过业界多年开发和测试的专业库。它们可能比我们自己从零开始做的任何东西都要好得多。
总之,一个专业的ECS很难从头开始实现。这是一个有效的学术练习,但一旦你完成了你的小型学习项目,那么不用多想,选择一个经过良好测试的第三方ECS库,并将其添加到你的游戏引擎代码中。
6. 渲染
好吧,看起来我们的游戏引擎的复杂性正在缓慢增长。既然已经讨论了在内存中存储和访问游戏对象的方法,我们可能还需要讨论如何在屏幕上渲染对象。
第一步是考虑我们将用引擎创造的游戏的性质。我们是否创造了一个只用于开发2D游戏的游戏引擎?如果是这种情况,我们就需要考虑渲染精灵、纹理、管理图层,可能还需要利用显卡加速。好消息是,2D游戏通常比3D游戏简单,2D数学也比3D数学简单得多。
如果你的目标是开发一个2D引擎,你可以使用SDL来帮助多平台渲染。SDL抽象了加速GPU硬件,可以解码和显示PNG图像,绘制精灵,并在游戏窗口内渲染纹理。
而如果你的目标是开发一个3D引擎,那么我们需要定义如何向GPU发送一些额外的3D信息(顶点、纹理、shader等)。你可能想使用软件抽象图形硬件,最流行的选项是OpenGL、Direct3D、Vulkan和Metal。使用哪个API可能取决于你的目标平台。例如,Direct3D支持微软的应用程序,而Metal将只与苹果产品兼容。
3D应用程序通过图形管线处理3D数据。该管线将指示你的引擎必须如何向GPU发送图形信息(顶点、纹理坐标、法线等)。图形API和管线还将指示我们应该如何编写可编程shader来变换和修改3D场景的顶点和像素。
可编程shader指示GPU应该如何处理和显示3D对象。我们可以为每个顶点和每个像素(片段)运用不同的脚本,它们可以控制反射、平滑度、颜色、透明度等等。
说到3D对象和顶点,把读取和解码不同网格格式的任务委托给库是个好主意。有许多流行的3D模型格式,大多数第三方3D引擎应该都知道。文件的一些例子是. obj、Collada、FBX和DAE。我建议从. obj文件开始。有一些经过良好测试和支持的库可以用C++处理OBJ加载。TinyOBJLoader和AssImp是很多游戏引擎都用的一个很棒的选择。
7. 物理
当我们向引擎添加实体时,我们可能还希望它们在场景中移动、旋转和弹跳。这个游戏引擎的子系统就是物理模拟。它既可以手动创建,也可以从现有的现成物理引擎导入。
在这里,我们还需要考虑我们想要模拟的物理类型。2D物理通常比3D简单,但物理模拟的底层部分2D和3D引擎是非常相似的。
如果你只是想在你的项目中包含一个物理库,有几个很好的选择。
对于2D物理,我建议看看Box2D和Chipmunk2D。对于专业和稳定的3D物理模拟,则可以瞅瞅像是PhysX和Bullet这样的库。如果物理稳定性和开发速度对你的项目至关重要,那么使用第三方物理引擎总是不错的选择。
GIF
作为一名教育者,我坚信,每个程序员都应该在职业生涯中至少学习一次如何编写简单的物理引擎。同样地,你不需要编写一个完美的物理模拟,但要专注于确保物体能够正确加速,并确保不同类型的力可以应用到你的游戏物体上。一旦移动搞定了,你还可以考虑实现一些简单的碰撞检测和碰撞解决方案。
如果你想了解更多关于物理引擎的知识,可以把一些好书和在线资源充分利用起来。对于2D刚体物理,你可以查看Box2D源代码和Erin Catto的PPT。但如果你正在寻找一门关于游戏物理的综合课程,《2D game physics from Scratch》可能是一个不错的开始。
如果你想学习3D物理以及如何实现一个强大的物理模拟,另一个很好的资源是David Eberly的《Game physics》一书。
8. UI
一提到Unity或虚幻等现代游戏引擎,我们总会想到带有各种面板、滑块、拖放选项及其他帮用户定制游戏场景的漂亮界面元素的复杂UI。UI能让开发者添加和删除实体,动态更改组件值,并轻松修改游戏变量。
需要明确的是,我们谈论的是用于工具的游戏引擎UI,而不是向玩家展示的用户界面(如对话屏幕和菜单)。
请记住,游戏引擎不一定要嵌入编辑器,但由于游戏引擎通常用于提高工作效率,友好的UI会帮你和其他团队成员快速定制关卡及游戏场景的其他内容。
从头开发UI框架可能是新手程序员尝试添加到游戏引擎中的最烦人的任务之一。你必须创建按钮、面板、对话框、滑块、单选按钮、管理颜色,还需要正确处理该UI的事件并始终保持其状态。一点都不好玩。在引擎中添加UI工具还将会增加应用程序的复杂性,并为源代码添加大量的噪声。
如果你的目标是为你的引擎创建UI工具,我的建议是使用现有的第三方UI库。在Google上搜一把会立刻出现最受欢迎的一些选择,例如Dear ImGui, Qt和Nuklear。
GIF
Dear ImGui是我最喜欢的工具之一,因为它让我们能为引擎工具快速设置用户界面。ImGui项目使用一种被称为“即时模式UI”的设计模式,它被广泛用于游戏引擎,因为它利用加速GPU渲染能与3D应用程序进行良好的通信。
总之,如果你想在游戏引擎中添加UI工具,我的建议是使用Dear ImGui。
9. 脚本
随着游戏引擎的发展,一个很流行的选择是使用简单的脚本语言进行关卡定制。
这个想法理念很简单:我们将脚本语言嵌入到原生C++应用中,非专业程序员可以用这种更简单的脚本语言编写实体行为、AI逻辑、动画和游戏的其他重要元素。
一些流行的游戏脚本语言是Lua, Wren, C#,Python和JavaScript。所有这些语言的操作级别都比我们的原生C++代码高得多。无论谁使用脚本语言编写游戏行为,都不需要担心内存管理或核心引擎如何工作的其他底层细节。他们所需要做的就是编写关卡脚本,咱们的引擎知道如何解释脚本并在幕后执行困难的任务。
GIF
我最喜欢的脚本语言是Lua。Lua体积小、速度快,并且非常容易与C和C++原生代码集成。此外,如果我使用Lua和“现代”C++,我喜欢用一个名为Sol的wrapper库。Sol库帮我地道地运用Lua,并提供许多帮助函数来改进传统的Lua C API。
启用脚本后,我们就可以开始在游戏引擎中讨论更高级的主题。脚本帮我们定义AI逻辑,定制动画框架和移动,以及其他不需要存在于原生C++代码中、可以通过外部脚本轻松管理的游戏行为。
10. 音频
另一个可以考虑为游戏引擎添加的支撑元素是音频。
毫无疑问,若想要插入音频值并发出声音,我们需要通过操作系统访问音频设备。同样地(二回目),由于我们通常不会想编写针对特定操作系统的代码,所以我建议用一个多平台库来抽象音频硬件访问。
像SDL这样的多平台库有各种扩展组件,能帮你的引擎处理音乐和音效等内容。
但是现在,严肃来讲(二回目),我强烈建议在你的引擎的其他部分已经协同工作之后再处理音频。让声音文件响起来可能很容易实现,但一旦我们开始处理音频同步,将音频与动画、事件和其他游戏元素链接起来,事情就会变得混乱。
如果你非常务实地全手动处理,由于多线程管理的缘故,音频部分可能会很棘手。它并非不能实现,但如果你的目标是编写一个简单的游戏引擎,那么这部分内容是最值得委托给专业库的。
你可以考虑将SDL_Mixer、SoLoud和FMOD等优秀的音频库和工具整合到游戏引擎中。
《Tiny Combat Arena》就是一款用FMOD库实现多普勒效应和压缩效应等音频效果的游戏。你可以听到加力燃烧器的声音,以及其他喷气机经过时的3D效果。
11. 人工智能
我要讨论的最后一个子系统是AI。可以通过脚本实现AI,这意味着我们能将AI逻辑委托给关卡设计师编写脚本。另一种选择便是将适当的AI系统嵌入到我们的游戏引擎核心原生代码中。
在游戏中,AI是用来产生对游戏对象的响应性、适应性或智能行为。大多数AI逻辑被添加到非玩家角色(npc,敌人)中,以模拟类似人类的智能。
敌人是AI在游戏中应用的一个常见例子。当敌人在地图上追逐目标时,游戏引擎可以通过寻路算法或有趣的类人行为来创建抽象。
Ian Millington的《AI for games》是一本关于游戏人工智能理论和执行的综合性书籍。
不要试图同时做所有的事情
好了,我们前开你讨论了一些重要的概念,你可以考虑将它们添加到你的简单C++游戏引擎中。但在开始把这些东西粘在一起之前,我想提一点非常重要的事情。
开发游戏引擎最困难的部分之一是,大多数开发者不会设定明确的边界,也没有“终点线”的概念。换句话说,程序员会开始一个游戏引擎项目,渲染对象,添加实体,添加组件,然后便走入不归路了。如果没有定义边界,很容易添加越来越多的功能,而失去对大局的掌控。如果出现这种情况,游戏引擎很有可能永远都见不到天日。
除了缺乏边界之外,当我们看到代码以迅雷不及掩耳盗铃之势在眼前增长时,我们很容易不知所措。游戏引擎项目的复杂性可能会在几周内迅速增长,你的C++项目可能会有多个依赖项,需要一个复杂的构建系统,随着引擎添加更多功能,你的代码的整体可读性也会下降。
我的最重要建议之一是,在编写一款实际游戏的同时编写游戏引擎。在开始和完成引擎的第一次迭代时,脑中要有一款真正的游戏。这将帮助你设定限制,为你需要完成的事情定义一条清晰的道路。尽你最大的努力坚持下去,不要在中途改变需求。
慢慢来,专注于基础
如果你正在创建自己的游戏引擎作为学习练习,那就尽情享受这样的小小胜利吧!
大多数学生在项目开始时都非常兴奋,随着时间的推移,焦虑开始浮现。如果我们从头开始创造游戏引擎,特别是使用像C++这样复杂的语言时,我们很容易就会不知所措并失去动力。
我想鼓励你战胜那种“与时间赛跑”的感觉。深呼吸,享受每一个微小的胜利。例如,当你学会如何成功在屏幕上显示PNG纹理时,享受这一刻,并确保你明白你做了什么。如果你成功地检测到了两个物体之间的碰撞,那就再次享受这一刻,并反思你刚刚获得的知识。
专注于基础并确保切实拥有这些知识。不管一个概念多小、多简单,都要切实拥有它!!其他一切都是浮云。