游戏引擎学习第11天
视频参考:https://www.bilibili.com/video/BV1QLmDYQE3n
平台层的编写 应该是平台可移植什么的吧
逐项补充说明:
-
存档位置
在游戏或应用程序中,需要保存用户的进度、设置和数据,存档位置是指存放这些数据的文件夹路径。通常,平台层需要处理不同操作系统或设备的存档目录,并确保数据能够正确保存和加载。 -
获取自己可执行文件的句柄
在某些情况下,程序需要获得自己的可执行文件路径或句柄。这对于进行文件操作、获取程序资源或与操作系统交互非常有用。例如,在Windows平台下,可以通过GetModuleFileName
获取当前程序的路径。 -
资源加载路径
在平台层中,资源加载路径指的是存储游戏或应用程序资源(如图像、音频文件等)的目录。平台层通常会确定资源文件的位置,并确保正确的路径设置,从而使程序能够正确加载所需的资源。 -
线程(启动线程)
多线程编程是提高程序效率和响应速度的重要方法。平台层可能需要提供对线程的支持,例如通过操作系统的API启动、管理和销毁线程。Windows平台可以使用CreateThread
或 C++ 标准库中的线程支持。 -
原始输入(支持多个键盘)
原始输入支持指的是直接访问输入设备(如键盘、鼠标等)的能力,绕过操作系统的标准输入处理。这对于需要精确控制的应用程序(如游戏)非常重要。支持多个键盘意味着应用程序能够同时接收来自多个输入设备的事件。 -
睡眠/TimeBeginPeriod
Sleep
是一种暂停当前线程执行的方式。在某些情况下,程序可能需要改变系统的时钟频率,以便提高时间分辨率,特别是与游戏或动画帧速率相关的部分。TimeBeginPeriod
是Windows API,用于提高系统时钟精度,通常会影响定时器的行为。 -
ClipCursor()(多显示器支持)
ClipCursor
是一个 Windows API 函数,它可以限制鼠标光标的活动范围,通常用于多显示器环境。通过限制光标在某个特定区域内移动,应用程序可以避免用户无意中把光标移出屏幕。 -
全屏支持
全屏模式在游戏和某些多媒体应用中常见,它能够将应用程序窗口扩展到整个屏幕,并且不显示任务栏或其他桌面元素。全屏支持涉及到窗口管理和图形渲染的设置,通常需要通过平台层的API来调整显示模式。 -
WM_SETCURSOR(控制光标可见性)
WM_SETCURSOR
是 Windows 消息之一,用于控制鼠标光标的显示与隐藏。平台层需要能够管理和更新鼠标光标的状态,以便在不同的应用场景中(如游戏内或应用界面)决定是否显示光标。 -
QueryCancelAutoplay
QueryCancelAutoplay
是用于询问操作系统是否应该取消自动播放功能的一种机制。通常与多媒体设备(如CD/DVD驱动器)有关,平台层可以使用它来防止自动启动的程序干扰当前应用。 -
WM_ACTIVATEAPP(当我们不是活动应用程序时)
WM_ACTIVATEAPP
是 Windows 消息之一,用于处理应用程序的激活和失去激活状态。当应用程序从前台切换到后台或从后台切换到前台时,这条消息会被触发。平台层需要对这种情况做出反应,以便调整程序的行为(如暂停游戏、降低处理器负载等)。 -
Blit速度优化(BitBlt)
BitBlt
是一种常见的图像处理函数,用于在屏幕或图形缓冲区之间复制像素数据。Blit速度优化涉及通过优化BitBlt
的调用或使用硬件加速,来提高图像渲染速度。对于需要大量图形更新的应用程序,如游戏或视频播放器,这是至关重要的。 -
硬件加速(OpenGL或Direct3D或两者?)
硬件加速指的是使用图形处理单元(GPU)而不是中央处理单元(CPU)来处理图形渲染任务。常见的图形API有 OpenGL 和 Direct3D,平台层需要决定支持哪个API,或者是否同时支持两者,以便充分利用硬件加速的优势。 -
GetKeyboardLayout(支持法语键盘、国际化WASD键支持)
GetKeyboardLayout
是一个 Windows API 函数,用于获取当前系统的键盘布局。这对于需要支持多语言、多地区的程序非常重要,特别是在游戏中,可能需要根据用户的键盘布局调整控制映射,例如在法语键盘上调整 WASD 键的默认位置。
这些功能是平台层的一部分,用于解决不同操作系统和硬件之间的兼容性问题,并确保应用程序能够在各种环境下平稳运行。
关于平台移植定义宏
不推荐大量使用平台相关的宏(如 #if GAME_WIN32
、#elif GAME_LINUX
等)来控制代码的原因有几个,尤其是在跨平台开发中,这种做法可能会带来一些潜在问题和挑战:
1. 增加代码复杂性和可维护性
- 大量的条件编译:当代码中有多个平台的宏控制时,代码的可读性和维护性会大大降低。你必须在每次修改时考虑到不同平台下的代码,增加了调试的复杂度。
- 冗余代码:每个平台可能需要相似的代码,但由于平台差异,你不得不为每个平台写一套不同的实现。这样会导致重复代码,增加了维护的难度。
2. 隐藏平台差异
- 使用宏进行平台判断虽然可以解决跨平台开发的需求,但它本质上是在不同平台间硬编码了差异。这会使得开发人员忽视了平台之间的真正差异,可能导致平台特定的优化和设计无法被显式考虑,进而错失提升性能或稳定性的机会。
- 在有多个平台时,这种方法可能会导致不同平台的行为不一致,可能发生潜在的隐藏 bug,尤其是对于一些细微的系统差异。
3. 阻碍跨平台框架的使用
- 如果使用大量的条件编译,可能会使你放弃使用一些成熟的跨平台库或框架(如 SDL、Qt、Boost 等),因为这些框架本身已经处理了大部分平台相关的差异。
- 使用这些库时,通常不需要手动管理
#if PLATFORM_X
类型的宏,框架已经为你屏蔽了不同平台的差异。
4. 编译时间增加
- 每个平台特定的代码路径都需要编译,这会使编译时间变长。在某些大型项目中,频繁的条件编译可能会影响整体编译性能。
5. 可能带来难以调试的问题
- 条件编译可能导致一些代码路径在不同的编译环境中不一致,特别是在调试时,开发者可能会遇到某些平台特有的 bug,这些 bug 在其他平台上是不存在的,可能会导致调试变得更加复杂。
6. 错误的宏定义
- 如果宏定义没有正确配置或者被错误地引用,可能导致程序运行时出现无法预料的行为,尤其是在大型项目或团队协作中,调试此类问题会非常困难。
7. 跨平台的可移植性问题
- 在一些特殊情况下,条件编译会使代码无法在某些平台上直接运行。例如,如果某个平台的编译器不支持某些特性或库,那么
#if
指令会把它们排除在外,导致无法运行或移植到新平台时遇到麻烦。
更好的跨平台开发方式
为了避免上述问题,可以考虑以下几种更好的跨平台开发方式:
1. 使用跨平台库和框架
- 使用像 SDL, Qt, Boost, GLFW 等跨平台库来屏蔽平台特有的差异,这样大部分条件编译都可以被框架内部处理,你只需要编写平台无关的代码。
2. 分层抽象
- 通过层次化抽象来解决平台差异。例如,创建一个平台无关的接口层,然后在每个平台上实现特定的功能。这样,你可以在平台特定代码中处理差异,而不是将这些差异散布在整个代码中。
- 示例:
// PlatformAbstraction.h class IPlatform { public: virtual void RunMainLoop() = 0; }; // WindowsPlatform.cpp class WindowsPlatform : public IPlatform { public: void RunMainLoop() override { // Windows-specific code } }; // LinuxPlatform.cpp class LinuxPlatform : public IPlatform { public: void RunMainLoop() override { // Linux-specific code } };
3. 利用动态链接库(DLL / .so)
- 在平台特定的功能上使用动态链接库,平台相关的代码可以在运行时通过动态加载不同的库来解决,而不是通过编译时的宏定义。
4. 预处理器和宏的适度使用
- 尽量减少宏的使用,只有在必须的情况下才使用宏。可以用常规的
#include
和多文件结构来处理不同平台的差异,尽量保持每个平台的实现代码与其余平台代码相对独立,减少交叉。
总结
使用大量的 #if GAME_WIN32
等宏定义来进行平台控制是一种快速解决问题的方法,但它会导致代码的复杂性增加,维护性变差,并且容易带来一些调试和移植问题。通过合理使用跨平台库、抽象和动态链接等方式,可以有效提高代码的可移植性和可维护性,从而减少平台相关宏的使用,保持代码的简洁性和可扩展性。
这段代码体现了一个典型的 平台抽象层(Platform Abstraction Layer, PAL) 的设计模式。通过将平台特定的功能与核心游戏逻辑分开,代码变得更具可移植性。下面对这段代码的结构和模式进行详细解释:
1. 平台相关的实现与非平台相关的逻辑分离
在这种设计中,游戏的核心功能与平台相关的功能被分开,平台相关的代码被封装在不同的文件中,如 Win32_Game.cpp
和 Linux_Game.cpp
。这样做的好处是,当移植到不同的平台时,只需要修改平台特定的部分,而不需要修改游戏的核心逻辑。
2. Win32_Game.cpp
和 Linux_Game.cpp
中的平台相关代码
这两份文件分别包含了平台特定的实现:
-
Win32_Game.cpp:
PlatformLoadFile
函数是平台特定的实现,可能是用来加载文件的方法,这里你可以使用 Windows API,例如CreateFile
或LoadLibrary
等。WinMain
函数是 Windows 平台的入口点,所有的 Windows 应用程序都需要一个WinMain
函数,而不是main
函数。
-
Linux_Game.cpp:
- 这里的
PlatformLoadFile
实现可能使用 Linux 特有的文件加载方式,例如通过open
系统调用等。
- 这里的
3. game.cpp
中的游戏逻辑
game.cpp
文件中包含了游戏的核心逻辑,和平台无关。MainLoop
函数负责游戏的主循环,游戏中的文件加载操作是通过 PlatformLoadFile
函数来实现的,但这个函数并不关心具体的实现,它只依赖于外部的定义。
在 game.cpp
中,PlatformLoadFile("foo.bmp")
是一个平台无关的调用。无论你是运行在 Windows 还是 Linux,PlatformLoadFile
都会根据不同的定义来调用不同的实现。
4. game.h
中的声明
game.h
中声明了 PlatformLoadFile
函数,并提供给 game.cpp
调用。这里通过 #pragma once
防止头文件重复包含。这样一来,game.cpp
就可以直接调用 PlatformLoadFile
,而不需要关心具体平台的实现。
5. 平台抽象层(PAL)模式
这种模式有几个关键点:
-
核心逻辑与平台相关实现分离:游戏的核心逻辑在
game.cpp
中实现,而平台相关的实现(例如文件加载、窗口管理等)则封装在Win32_Game.cpp
或Linux_Game.cpp
中。这种做法确保了平台无关的代码不会因为平台差异而发生变化。 -
接口层(
game.h
):game.h
作为一个接口层,提供了一个统一的接口(如PlatformLoadFile
)供game.cpp
调用。这样在game.cpp
中无需了解平台差异,只需要依赖接口层。 -
平台特定实现:每个平台的特定实现代码只在对应的
.cpp
文件中,减少了平台间的耦合度,使得移植代码时更加方便。你只需为新的平台编写一个新的PlatformLoadFile
实现并确保它遵循统一接口即可。
6. 如何理解和使用这种模式
这种模式适合于跨平台应用开发,特别是游戏开发中的平台相关功能处理。它通过分离平台相关的代码与游戏核心逻辑,提高了代码的可移植性和可维护性。
例如,假设你现在想将这款游戏从 Windows 移植到 macOS 或 Android,你只需要为每个平台实现一个 PlatformLoadFile
函数,并且保持 game.cpp
和 game.h
的不变。这样,你就不需要修改任何游戏逻辑代码。
总结
这种设计模式通过将平台相关的代码(如文件加载、窗口管理等)封装到不同的 .cpp
文件中,并且通过接口层暴露给游戏逻辑层,确保了游戏代码的跨平台可移植性。平台抽象层(PAL)有效地减少了平台之间的差异,使得在不同平台上运行相同的游戏变得更加简单和清晰。
把RenderWeirdGradient 包装到平台无关的game.cpp中
后面有讲一点excel用法
非平台相关
理解
- 四个主要功能:
- 时间管理(Timing):游戏循环中的时间控制,确保每一帧按预期时间运行,通常涉及帧率管理、定时器等。
- 控制器/键盘输入(Controller/Keyboard Input):处理玩家通过控制器、键盘等输入设备与游戏交互的部分。
- 位图缓冲区(Bitmap Buffer):涉及游戏渲染图像的缓存管理,通常用于存储当前画面图像的位图数据。
- 声音缓冲区(Sound Buffer):用于存储游戏中的音效或背景音乐的声音数据缓冲区,可能涉及音频播放或处理。
这些是游戏开发中的核心部分,每一部分都涉及到与硬件、输入输出、渲染等相关的操作。
- TODO(casey):未来,渲染将特别变成一个三层抽象:
- 三层抽象(Three-tiered abstraction):这表示渲染系统将被设计成三个层次,通常指的是更细粒度的模块化分层,使得渲染过程更加灵活、可扩展。这可以理解为将渲染从底层硬件、图形API(如OpenGL、DirectX)和高层渲染逻辑(如渲染管线、场景管理)分开,以便未来能更方便地进行优化、扩展或更换技术栈。
- “特别”:这个词强调渲染模块的复杂性,说明未来的设计会更加具体和具有针对性。
这段代码可能出现在一个正在开发的游戏引擎中,计划将渲染部分模块化,以便将来能够更加灵活地进行调整和扩展。
平台相关
/**
T这不是最终版本的平台层
- 存档位置
- 获取自己可执行文件的句柄
- 资源加载路径
- 线程(启动线程)
- 原始输入(支持多个键盘)
- Sleep/TimeBeginPeriod
- ClipCursor()(多显示器支持)
- 全屏支持
- WM_SETCURSOR(控制光标可见性)
- QueryCancelAutoplay
- WM_ACTIVATEAPP(当我们不是活动应用程序时)
- Blit速度优化(BitBlt)
- 硬件加速(OpenGL或Direct3D或两者?)
- GetKeyboardLayout(支持法语键盘、国际化WASD键支持)
只是一个部分清单
*/