适合初学者的5款游戏引擎:如何从零开始用 C 开发一款游戏引擎
适合初学者的5款游戏引擎:如何从零开始用 C 开发一款游戏引擎定制:能够根据独特的游戏需求定制解决方案。工作流控制:你可以更好地控制游戏的特殊方面,并调整解决方案以满足工作流的需求。开发者可以使用现有许多免费、强大、专业的商业引擎来创建和部署自己的游戏。那么,既然存在这么多游戏引擎可供选择,为什么还会有人费心从头开始制作游戏引擎呢?我曾经写了一篇博客文章《自己动手编写游戏引擎还是使用现成的?》(pikuma/blog/why-make-a-game-engine)来解释程序员从头开始制作游戏引擎的一些原因。在我看来,最主要的原因包括:学习机会:对游戏引擎工作原理的深层次理解可以让你成为一名优秀的开发人员。
《全速狂飙》游戏的对话框和运作都使用SCUMM脚本语言进行管理
随着机器的发展和功能的不断增强,游戏引擎也随之发展。现代引擎配备了功能丰富的工具,这些工具需要快速的处理器速度、惊人的内存量和专用显卡。
有了备用动力,现代游戏引擎可以用机器循环来换取更多的抽象性。这种权衡意味着,我们可以将现代游戏引擎视为通用工具,以较低的成本和较短的开发时间创建复杂的游戏。
三、为什么要制作游戏引擎?这并不是一个陌生的问题,不同的游戏程序员也各有各的看法。其回答取决于开发的游戏的性质、业务需求以及其他待考虑因素的影响。
开发者可以使用现有许多免费、强大、专业的商业引擎来创建和部署自己的游戏。那么,既然存在这么多游戏引擎可供选择,为什么还会有人费心从头开始制作游戏引擎呢?
我曾经写了一篇博客文章《自己动手编写游戏引擎还是使用现成的?》(pikuma/blog/why-make-a-game-engine)来解释程序员从头开始制作游戏引擎的一些原因。在我看来,最主要的原因包括:
学习机会:对游戏引擎工作原理的深层次理解可以让你成为一名优秀的开发人员。
工作流控制:你可以更好地控制游戏的特殊方面,并调整解决方案以满足工作流的需求。
定制:能够根据独特的游戏需求定制解决方案。
极简主义:较小的代码库可以减少较大游戏引擎带来的开销。
创新:你可能需要实现一些全新的或针对其他引擎不支持的非正统硬件。
四、如何制作游戏引擎下文将继续讨论游戏引擎的一些组件,并指导读者自己编写一个游戏引擎。
1.选择编程语言
开发核心引擎代码的编程语言是首要选择。原始汇编语言、C、C ,甚至C#、Java、Lua,还有JavaScript等高级语言都有用来开发引擎。
编写游戏引擎最流行的语言之一是C 。C 编程语言将速度与使用面向对象编程(OOP)和其他编程范式的能力结合起来,帮助开发人员组织和设计大型软件项目。
因为在我们开发游戏时,性能通常是非常重要的,而C 具有编译语言的优势。使用编译语言意味着最终的可执行文件将在目标机器的处理器上以本机方式运行。此外还有许多专用的C 库和开发工具包可用于大多数现代控制台,如PlayStation或XBox等。
开发者可以使用微软提供的C 库访问XBox控制器
在性能方面,我个人不推荐使用虚拟机、字节码或任何其他中间层的语言。除了C 之外,一些适合编写核心游戏引擎代码的替代品还包括Rust、Odin和Zig等语言。
本文将以C 编程语言构建一个简单的游戏引擎。
2 . 硬件访问
在MS-DOS等较旧的操作系统中,我们通常可以直接操作内存地址并访问映射到不同硬件组件的特殊位置。例如,我要用某种颜色“绘制”一个像素,所要做的就是加载一个特殊的内存地址,其中的数字代表VGA调色板的正确颜色,而显示驱动程序将该变化转换为物理像素,并将其转换到CRT显示器。
随着操作系统的发展,它们开始负责保护硬件免受程序员的攻击。现代操作系统将不允许代码修改操作系统为进程提供允许地址之外的内存位置。
例如,如果你使用的是Windows、macOS、Linux或BSD,则需要向操作系统请求在屏幕上绘制像素或与任何其他硬件组件对话的正确权限。即使是在操作系统桌面上打开窗口这样的简单任务也必须通过操作系统API来执行。
因此,运行进程、打开窗口、在屏幕上渲染图形、绘制窗口内的像素,甚至从键盘读取输入事件都是特定于操作系统的任务。
SDL(Simple DirectMedia Layer,即简易直控媒体层)是一个非常流行的库,它可以帮助实现多平台硬件抽象。在游戏开发课堂上时,通过SDL,我无需为Windows操作系统、macOS和使用Linux系统的学生创建三个不同版本的代码。SDL不仅是不同操作系统的桥梁,也是不同CPU体系结构(英特尔、ARM、苹果M1等)的桥梁。SDL库对底层硬件访问进行抽象,并“翻译”我们的代码以便在这些不同的平台上都能够正确工作。
下面是使用SDL在操作系统上打开窗口的一小段代码。为了简单起见,代码中没有添加处理错误的部分,但是下面的代码对于Windows、macOS、Linux、BSD,甚至RaspberryPi,都是通用的。
SDL只是我们可以用来实现这种多平台硬件访问的游戏库的一个例子。SDL是2D游戏和将现有代码移植到不同平台和控制台的热门选择方案之一。多平台库的另一个流行选择方案是GLFW,它主要用于3D游戏和3D引擎。GLFW库可以很好地与OpenGL和Vulkan等加速3D API进行通信。
3 . 游戏主循环
至此,一旦操作系统方案解决,接下来我们就需要创建一个控制整个游戏的主循环。
简单地说,我们通常希望我们的游戏以每秒60帧的速度运行。根据游戏的不同,帧速率可能会有所不同,但为了让景物变得更加清晰,在胶片上拍摄的电影通常都是以每秒24帧的速度运行(每秒钟24幅图像从你眼前闪过)。
在游戏过程中,游戏循环会持续运行,在循环的每一次过程中,我们的引擎都需要运行一些重要的任务。传统的游戏循环必须确保:
在不阻塞的情况下处理输入事件
更新当前帧的所有游戏对象及其属性
在屏幕上渲染所有游戏对象和其他重要信息
但是,一个原始的C 循环对我们来说还不够。游戏循环必须与现实世界的时间有某种关系。毕竟,游戏中的敌人应该在任何机器上都是以相同的速度移动,而不管这些机器的CPU时钟速度如何。
控制这个帧速率并将其设置为固定的FPS数实际上是一个非常有趣的事情。它通常要求我们跟踪帧与帧之间的时差,并进行一些合理的计算,以确保我们的游戏以至少30 FPS的帧速率顺利运行。
4 . 输入事件处理
很难想象一个游戏不从用户那里读取某种输入事件会是什么情景。所有这些输入事件可能来自键盘、鼠标、游戏板或虚拟现实设备。因此,我们必须在游戏循环中处理不同的输入事件。
要处理用户输入,我们必须请求访问硬件事件,这必须通过操作系统API执行。我们可以使用一些著名的多平台硬件抽象库(SDL、GLFW、SFML等)来处理用户输入。
例如,如果我们使用SDL就可以实现轮询事件,然后仅用几行代码就可以处理各种输入事件。
再强调一次,如果我们使用像SDL这样的跨平台库来处理输入,我们不必太担心特定于操作系统的实现。无论我们的目标平台是什么,C 代码都应该是相同的。
至此,我们已经有了一个可工作的游戏循环和一种处理用户输入的方法。接下来是时候开始考虑在内存中组织游戏对象了。
5 . 在内存中表示游戏对象
在设计游戏引擎时,我们需要设计数据结构来存储和访问游戏对象。
程序员在设计游戏引擎时会使用多种技术。一些引擎可能会使用简单的面向对象方法来处理类和继承,而其他引擎可能会将它们的对象组织为实体和组件。