使用 SDL2 库开始游戏开发
编程最棒的部分是创造其他人可以享受的东西。这可能是我们都想至少尝试制作游戏的原因,但学习一个完整的引擎可能会让人望而生畏。
有一些选择可以让开发图形应用程序更容易上手,特别是对于 C 和 C++ 来说。有 OpenGL 和 SDL,它们会抽象出硬件,但给我们更多自由来编写应用程序代码。“游戏开发入门课程”中通常会用到这些。然而,这很快就会变得让人不知所措,因为从头构建这些库本身就是一个挑战。
SDL2 或 Simple DirectMedia Layer 2.0,是一个旨在提供对音频、键盘、鼠标、操纵杆和图形硬件的低级访问的库。它是跨平台和移动友好的,因此有很多选择和机会可以更深入地了解使用 C++ 开发游戏的不同方面。
本教程将引导您完成设置一个基本应用程序的过程,包括键盘控制、图像和文本。这是制作贪吃蛇或吃豆人风格游戏的坚实起点。对于更高级的游戏,我将为您提供一些优秀的参考,以便您在设置完成后继续学习!
制作我们的第一个游戏
SDL2 非常容易上手。基本步骤是初始化、创建渲染循环和清理。我们将在本博客中构建的示例可以在 GitHub conan-examples2 仓库中找到。
您可以通过克隆示例并自行运行安装命令来按照本节操作!
git clone https://github.com/conan-io/examples2.git
cd examples2/examples/libraries/sdl2/introduction
1:创建窗口和渲染器
#include <stdio.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_timer.h>
int main(int argc, char *argv[])
{
// returns zero on success else non-zero
if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
printf("error initializing SDL: %s\n", SDL_GetError());
return 1;
}
SDL_Window* win = SDL_CreateWindow("GAME", // creates a window
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
1000, 1000, 0);
// triggers the program that controls
// your graphics hardware and sets flags
Uint32 render_flags = SDL_RENDERER_ACCELERATED;
// creates a renderer to render our images
SDL_Renderer* rend = SDL_CreateRenderer(win, -1, render_flags);
///
/// Section 2: SDL image loader
///
///
/// Section 4: SDL ttf and rendering text
///
///
/// Section 3: Game Loop and Basic Controls
///
// We add a delay in order to see that our window
// has successfully popped up.
SDL_Delay(3000);
///
/// Section 5: Freeing resources
///
// We destroy our window. We are passing in the pointer
// that points to the memory allocated by the
// 'SDL_CreateWindow' function. Remember, this is
// a 'C-style' API, we don't have destructors.
SDL_DestroyWindow(win);
// We safely uninitialize SDL2, that is, we are
// taking down the subsystems here before we exit
// our program.
SDL_Quit();
}
这是仅打开窗口所需的最低限度。
注意:在 macOS 上,应用程序和窗口的存在方式有所不同,您会在 Dock 中看到它,但很可能不会弹出。
我们先不提窗口关闭时出现的段错误。稍后在确保清理时我们会解决它!
2:使用 SDL_Image 加载图像
确保为该库添加头文件包含 #include SDL2/SDL_image.h>
///
/// Section 2: SDL image loader
///
// creates a surface to load an image into the main memory
SDL_Surface* surface;
// please provide a path for your image
surface = IMG_Load("conan-logo.png");
// loads image to our graphics hardware memory.
SDL_Texture* tex = SDL_CreateTextureFromSurface(rend, surface);
// clears main-memory
SDL_FreeSurface(surface);
// let us control our image position
// so that we can move it with our keyboard.
SDL_Rect dest;
// connects our texture with dest to control position
SDL_QueryTexture(tex, NULL, NULL, &dest.w, &dest.h);
// adjust height and width of our image box.
dest.w /= 6;
dest.h /= 6;
// sets initial x-position of object
dest.x = (1000 - dest.w) / 2;
// sets initial y-position of object
dest.y = (1000 - dest.h) / 2;
///
/// Section 4: SDL ttf and rendering text
///
///
/// Section 3: Game Loop and Basic Controls
/// Note: The rest of this snippet will be removed
while (1)
{
// clears the screen
SDL_RenderClear(rend);
SDL_RenderCopy(rend, tex, NULL, &dest);
// triggers the double buffers
// for multiple rendering
SDL_RenderPresent(rend);
}
///
/// Section 3: Game Loop and Basic Controls
/// Note: The code above will be removed
///
/// Section 5: Freeing resources
///
查看代码,有一些需要注意的地方。图像从磁盘加载,然后转换为纹理。我们要显示的所有内容都需要是纹理。使用纹理,我们可以查询填充的内容并反向工作以将图像居中在窗口上。
成功!我们在窗口中渲染了 Conan 2.0 徽标。
现在,尝试关闭窗口。哦,它没有关闭?!这是因为我们写了一个无限循环 while(1)
,它将永远运行。您需要打开任务管理器并杀死应用程序。任何游戏都需要有一个控制循环,它在每一帧开始时运行,等待用户输入,显示图形,并允许您优雅地关闭。
3:游戏循环和基本控制
现在,我们需要替换一些弹出窗口并显示图像的临时代码。该代码仅限于渲染一次,但现在我们希望能够在每次用户输入时更新和重新渲染图像。
///
/// Section 4: SDL ttf and rendering text
///
///
/// Section 3: Game Loop and Basic Controls
///
// controls animation loop
int close = 0;
// speed of box
int speed = 300;
// animation loop
while (!close) {
SDL_Event event;
// Events management
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
// handling of close button
close = 1;
break;
case SDL_KEYDOWN:
// keyboard API for key pressed
switch (event.key.keysym.scancode) {
case SDL_SCANCODE_ESCAPE:
close = 1;
break;
case SDL_SCANCODE_W:
case SDL_SCANCODE_UP:
dest.y -= speed / 30;
break;
case SDL_SCANCODE_A:
case SDL_SCANCODE_LEFT:
dest.x -= speed / 30;
break;
case SDL_SCANCODE_S:
case SDL_SCANCODE_DOWN:
dest.y += speed / 30;
break;
case SDL_SCANCODE_D:
case SDL_SCANCODE_RIGHT:
dest.x += speed / 30;
break;
default:
break;
}
}
}
// right boundary
if (dest.x + dest.w > 1000)
dest.x = 1000 - dest.w;
// left boundary
if (dest.x < 0)
dest.x = 0;
// bottom boundary
if (dest.y + dest.h > 1000)
dest.y = 1000 - dest.h;
// upper boundary
if (dest.y < 0)
dest.y = 0;
// clears the screen
SDL_RenderClear(rend);
SDL_RenderCopy(rend, tex, NULL, &dest);
///
/// Section 4: SDL ttf and rendering text
///
// triggers the double buffers
// for multiple rendering
SDL_RenderPresent(rend);
// calculates to 60 fps
SDL_Delay(1000 / 60);
}
///
/// Section 3: Game Loop and Basic Controls
///
///
/// Section 5: Freeing resources
///
我们应该有了游戏的雏形!
尝试移动方块并观察它四处移动!
4:添加 SDL_ttf 和字体以在游戏中显示文本
让我们将头文件 #include <SDL2/SDL_ttf.h>
添加到主代码文件的顶部。
文本实际上很复杂!每个字母都是一个字形,每个字形都需要光栅化为纹理,以便我们可以将其添加到渲染循环中。我们将为此创建一个辅助函数。
SDL_ttf 负责很多工作,并为我们提供了 RenderText
和 CreateTexture
,如果您还记得,这正是我们最终用来显示方块的方式。
您可以在 main
函数上方添加以下辅助函数
void render_text(
SDL_Renderer *renderer,
int x,
int y,
const char *text,
TTF_Font *font,
SDL_Rect *rect,
SDL_Color *color
) {
SDL_Surface *surface;
SDL_Texture *texture;
surface = TTF_RenderText_Solid(font, text, *color);
texture = SDL_CreateTextureFromSurface(renderer, surface);
rect->x = x;
rect->y = y;
rect->w = surface->w;
rect->h = surface->h;
SDL_FreeSurface(surface);
SDL_RenderCopy(renderer, texture, NULL, rect);
SDL_DestroyTexture(texture);
}
接下来我们需要加载要使用的字体,对于此演示,我选择了 Roboto,这将在游戏循环之外进行,并且只需要调用一次。
///
/// Section 4: SDL ttf and rendering text
///
// Init TTF
TTF_Init();
TTF_Font *font = TTF_OpenFont("Roboto-Regular.ttf", 24);
if (font == NULL) {
printf("error initializing TTF: %s\n", TTF_GetError());
return 1;
}
///
/// Section 3: Game Loop and Basic Controls
///
现在在游戏循环中调用它
///
/// Section 4: SDL ttf and rendering text
///
// create a rectangle to update with the size of the rendered text
SDL_Rect text_rect;
// The color for the text we will be displaying
SDL_Color white = {255, 255, 255, 0};
// so we can have nice text, two lines one above the next
render_text(rend, 10, 10, "Hello World!", font, &text_rect, &white);
render_text(rend, 10, text_rect.y + text_rect.h, "Conan demo by JFrog", font, &text_rect, &white);
// triggers the double buffers
// for multiple rendering
这里要注意的是,我们编写的辅助函数在循环的每次迭代中重新创建相同的纹理。这不会影响我们的示例,但如果您的游戏变得复杂,这可能会非常低效。
5:清理和释放资源
在使用像 SDL 这样的 C 风格 API 时,最关键的步骤之一是内存管理,许多这些结构是在栈上分配的,我们需要释放它们。
///
/// Section 5: Freeing resources
///
// close font handle
TTF_CloseFont(font);
// close TTF
TTF_Quit();
// destroy texture
SDL_DestroyTexture(tex);
// destroy renderer
SDL_DestroyRenderer(rend);
安装 SDL2 并设置您的项目
第一步是设置 CMake 来构建我们的项目。按照 SDL CMake 指南,我们会注意到有几个 CMake 目标,具体取决于我们使用库的静态或共享选项。
cmake_minimum_required(VERSION 3.15)
project(sdl-example CXX)
find_package(SDL2 REQUIRED CONFIG)
find_package(SDL2_image REQUIRED CONFIG)
find_package(SDL2_ttf REQUIRED CONFIG)
add_executable(sdl-example src/main.c)
# Main SDL library for init
target_link_libraries(${PROJECT_NAME} PRIVATE SDL2::SDL2main SDL2::SDL2-static)
# SDL image to make a surface (aka what we'll render)
target_link_libraries(${PROJECT_NAME} PRIVATE SDL2_image::SDL2_image-static)
# SDL ttf so we can display hello world!
target_link_libraries(${PROJECT_NAME} PRIVATE SDL2_ttf::SDL2_ttf)
现在,我们可以使用 Conan(一个 C 和 C++ 包管理器)来安装库。它不仅会安装 SDL2,还会安装所有必要的传递依赖项。Conan 从默认的 ConanCenter 远程获取这些包,这是开源 Conan 包的官方存储库。
使用 Conan 的原因是,虽然可以本地构建 SDL,但它有很多依赖项。例如 FreeType、libjpeg、libwebp 和 libdeflate 等等。不同的平台和不同的选项也需要不同的依赖项,这是 Windows MSVC 19.3 的 HTML 图形视图,使用下面的 conanfile.txt,您可以使用 conan graph info . --format=html > graph.html
生成任何项目的图形,看看它是什么样子!
要安装所需的 SDL 库,我们可以创建一个 conanfile.txt 文件,声明项目的依赖项。
[requires]
sdl_image/[~2.6]
sdl_ttf/[~2.0]
sdl/[~2.28]
[generators]
CMakeToolchain
CMakeDeps
[layout]
cmake_layout
第一部分 [requires]
列出了我们使用的 3 个 SDL 库:sdl、image 和 ttf。对于这些库,我们使用了 版本范围 来让 Conan 选择可用的最佳补丁版本(这就是 ~
表示的含义)。这是一种有效的方法来处理 依赖项菱形问题,在使用 SDL 时,图形中确实存在此问题。例如,sdl/[~2.0]
表示任何介于 2.0.0
和 2.0.x
之间的 SDL 版本(但不包括 2.1.0
)都是可以接受的。
我们将使用 CMakeDeps
为 CMake 的 find_package
生成配置文件,该配置文件用于 CMakeLists.txt
。此外,还有 CMakeToolchain
用于生成构建系统所需的所有信息。另外,请注意,我们为项目声明了一个 [layout]
作为 cmake_layout,这将有助于在构建时保持项目文件夹的组织。您可以查看 Conan 文档中的 使用包教程部分 以获取更多信息。
conan install . --build=missing
使用 conan install
命令,我们将在本地安装所有必要的包,并生成构建应用程序所需的文件。请注意,我们使用了 --build=missing
参数,以防某些二进制文件无法从远程获取。此外,如果您正在运行 Linux 并且系统上缺少某些必要的缺失系统库,您可能需要将 -c tools.system.package_manager:mode=install
或 -c tools.system.package_manager:sudo=True
参数添加到命令行中(文档参考)。
构建并运行我们的游戏
现在让我们构建项目并运行应用程序。如果您安装了 CMake>=3.23,则可以使用 CMake 预设
# Linux, macOS
cmake --preset conan-release
cmake --build --preset conan-release
cd build/Release
./sdl-example
# Windows
cmake --preset conan-default
cmake --build --preset conan-release
cd build\Release
sdl-example.exe
否则,您可以为 CMake 添加必要的参数
# Linux, macOS
cmake . -G "Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE=build/Release/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release
cmake --build .
./sdl-example
# Windows. Assuming Visual Studio 17 2022
# is your VS version and that it matches
# your default profile
cmake . -G "Visual Studio 17 2022"
-DCMAKE_TOOLCHAIN_FILE=./build/generators/conan_toolchain.cmake
cmake --build . --config Release
sdl-example.exe
结论
SDL2 是一个功能强大的库,它具有易于使用的 API,使各种技能水平的 C 和 C++ 开发人员能够快速创建图形界面。
我们涵盖了安装 SDL、设置项目以及运行我们的第一个游戏!该游戏是一个很好的起点,包含基本窗口、图像、游戏控制和渲染循环,以及事件文本。这为我们提供了一个简单的“游戏”,我们可以使用它在窗口中四处移动我们的 Conan 2.0 方块。
如果您想继续学习,添加更多对象并检测交集将是一个很好的补充。
那么,您还在等什么呢?深入了解,尝试使用 SDL2,看看它如何与您的项目相结合!
额外资料
- https://github.com/MikeShah/SDL2_Tutorials/tree/main
- https://www.geeksforgeeks.org/sdl-library-in-c-c-with-examples/
- https://thenumb.at/cpp-course/index.html https://thenumb.at/cpp-course/sdl2/01/01.html
- https://codereview.stackexchange.com/questions/212296/snake-game-in-c-with-sdl
- https://stackoverflow.com/questions/29064904/how-to-render-fonts-and-text-with-sdl2-efficiently