此处为前中间件游戏开发人员。要在任何给定平台上进行任何操作,在最低级别,您必须开始访问操作系统或特定于硬件的 API(例如,在 Windows 上,使用 CreateFile 打开文件或 open()在 Linux 上)。 C/C++ 标准库建立在这些 API 之上,以提供一个通用的平台来构建跨平台应用程序。
实际上,C++ 标准库在这方面并没有太大帮助,主要是因为图形、窗口 API 等。它们都在标准库的职权范围之外。对于我们游戏开发者来说,在控制台上使用 C++ 标准库的另一个大问题是,他们的行为往往针对“一般情况”,而不是特定平台。以 cmath 函数为例。在任何情况下,我都不会调用效率极低的 std::sin() 实现。 std::sin 在一个方面很棒——它处理非正规数,正确识别 NAN/INF,并且有一个很好的报告错误的方法。
在游戏引擎世界中,我们倾向于花费大量时间预先烘焙资产,以使这些 INF/NAN 永远无法进入游戏计算。所以在运行时处理这些东西是浪费时间,所以我们通常编写自己的数学近似值(我们不是在月球上着陆,我们只是在屏幕上扔一些多边形,所以我们通常不需要标准库提供的准确性)。
那么如何组织一个典型的跨平台游戏呢?您可能会看到一个类似于此的目录结构:
game/
platform/ //< contains all OS specific code (timers, mutexs, etc)
vpu/ //< wrappers over the SIMD instructions on the platform
maths/ //< fast versions of cmath + Vectors/Quats/Matrices/etc
graphics/ //< wrappers over the core graphics APIs
sound/ //< wrappers over the platform specific audio stuff
这几乎是编写所有其他代码所针对的“平台”(换句话说,我们基本上最终会为每个新平台编写自己的 C++ 标准库版本)。虽然上述工作涉及大量工作,但当出现新的硬件平台(例如 Playstation 6、XBox 99 等)时,通常合理直接重写整个代码库。当然,这比重写整个游戏要少。
在某些情况下,有些工作不会改变(例如,iOS 和 Android 都使用 ARM CPU,因此针对 ARM NEON 优化的数学例程将由两者共享,OpenGLES 图形例程也是如此)。
如果运气好的话,99.99% 的游戏代码都不需要修改。运气好的话——在很多情况下我们没那么幸运:([虽然现在比十年前更容易了!]
通常情况下(尤其是在控制台上),在游戏开发开始时,您会以可爱的抽象核心库为目标,一切都会好起来的。当您接近项目结束时,您可能最终会收到大量#ifdef XBOX 定义,这些定义利用特定硬件的特定性能增益(我们需要达到 60fps 的目标,我们并不真正关心 tbh )。在极端情况下,您可能会发现给定平台需要如此多的特定于平台的优化(例如渲染器),以至于它已经有效地分化为仅适用于该平台的全新库。
无论如何。这种情况在 PC 和 android 上略有不同——仅仅是因为硬件的多样性很重要(不像 XBOX,它们都是相同的!)。在这些情况下,我们将针对已经抽象的 API(例如 OpenGL、OpenAL、D3D 等)编写代码,并且我们将不得不插入比在控制台上更多的运行时错误检查(例如,在控制台上,我们可能知道我们有 256Mb 的内存。在 Android 上可能是 32Mb,也可能是 2Gb,谁知道呢!没关系,我们需要优雅地处理故障)。
对于桌面应用程序,对于窗口 API,绝大多数理智的人现在只使用 QT(如果他们需要 3D 渲染,可能会使用 OpenGL)。