【翻译】SDL2教程 Lesson 01 环境配置与窗口创建
本文翻译自 https://thenumb.at/cpp-course/sdl2/01/01.html 感谢作者的博文,在我 SDL 学习路上的指导。原文以正体翻译,个人注解则以斜体标出,在不影响阅读的情况下,辅助初学者的理解
导言
本节课程主要介绍 SDL2(Simple DirectMedia Layer 2 简单直接媒体抽象层)库的使用。它是一个(相对)易于使用的库,可以在不依赖特定操作系统功能的情况下,为程序添加多媒体功能。
SDL 提供的功能:
窗口管理
基于软件(CPU)和硬件(GPU)的 2D 图形渲染
输入事件处理机制
时间管理 (时间函数处理)
音频处理
文件 I/O 与库加载
多线程支持
用于 3D 图形的 OpenGL API
SDL 还提供了一些扩展库,用于实现更多功能——例如网络支持、更完善的音频系统、图像加载等。
本节课程我会引用许多 SDL 提供的函数。课程的重点是教你如何使用 SDL 的功能,而不是讲解每个函数的细枝末节等等。因此,每当提到一个 SDL 对象时,都会附上对应的官方文档链接。如果你对函数参数、返回值、副作用,或者对某个结构有疑问,请查阅对应文档——它是非常有价值的学习材料。
SDL官方文档阅读提示:
如果对某些内容有疑问,请查阅文档。
示例代码非常有用!
一定要阅读备注!他们可能展示了意想不到的特性并描述了何时应该释放/保存内存
每个页面底部的 “相关函数” 部分会告诉你还能使用哪些函数。但这不会在每一课里涵盖所有函数。
环境配置
指南:
在使用 SDL 的函数或对象时,你必须包含它们的头文件。SDL.h
文件会自动包含几乎所有需要的内容——在大多数情况下,这就足够了。不过,也有一些函数是在特定的头文件中声明的。如果不确定需要包含什么,请查阅文档。
如果你的编译器设置正确,你就可以使用尖括号(例如 #include <SDL.h>
)。这会告诉编译器去它指定的 include 目录中查找头文件。
此部分作者引用上述指南的链接来指引配置
SDL
环境,译者简单讲解一下各个平台配置 SDL的方法,如有需要,则专门开一个教程进行说明
macOS:
使用homebrew
进行安装,运行brew install sdl2 sdl2_image sdl2_mixer sdl2_ttf
即可
Windows:
在 github 的 SDL官方仓库 中,下载对应版本的 sdl2 库,解压后,就可以看到其对应的文件,docs
中是对应的文档,include
中则是相关的头文件,lib
中则是对应的dll动态库。然后再将对应的头文件与动态库,根据不同 IDE 的配置,或者是自己的编译脚本中,即可完成添加
初始化 SDL
在进行其他操作前,你必须先整体初始化 SDL 。即通过调用 SDL_Init()
来完成。如果你想初始化 SDL 的所有部分,可以传入参数 SDL_INIT_EVERYTHING
。
SDL 也允许你单独初始化库里面的某些子模块。要指定要初始化的部分,可以直接在 SDL_Init()
中传入对应的标志(flags),或者使用 SDL_InitSubSystem()
函数。如果你打算分别初始化各个子系统,那么在最初调用 SDL_Init()
时只需传入 0
即可。
SDL_Init( SDL_INIT_EVERYTHING );
创建窗口
你必须创建一个窗口,供程序的多媒体输入输出使用。从头开始编写一个多窗口应用的时候,你必须定义一个 WinMain
函数,来调用操作系统去获取 句柄 (handles
)、创建窗口等操作。而 SDL 提供了一个更简单、跨平台的窗口管理 API,让这些步骤大大简化。
为了管理窗口,SDL 以便利的方式提供了一个结构体 SDL_Window
,以及相关函数,例如 SDL_CreateWindow()
。
稍微插一句:你可能会注意到,SDL 文档中没有 SDL_Window
结构的详细说明链接。这是因为该结构是不透明(opaque)的,也就是说——你的程序无法直接访问或查看 SDL_Window
内部的实际内容。你只需要持有并操作一个指向 SDL_Window
的指针即可。
SDL_CreateWindow()
的功能也符合直觉:它接收窗口的标题、大小、位置、选项等参数,然后返回一个指向新建 SDL_Window
结构的指针。详细参数说明请参考 SDL 官方文档。
SDL_Window* win = SDL_CreateWindow( "my window", 100, 100, 640, 480, SDL_WINDOW_SHOWN );
大多数 SDL 函数在执行失败时会返回一个特定的值,用于表示错误。例如返回的是指针的函数, NULL
就是这个特定的值。因此,你可以很容易地检查操作是否成功。
在任何 SDL 的函数内发生了错误之后,SDL_GetError() 这个函数允许你接收捕获到的一个 string
的错误信息。
if ( !win ) {
cout << "Failed to create a window! Error: " << SDL_GetError() << endl;
}
表面
当你创建了一个窗口后,你需要一种方法在其上进行绘制。SDL 将任何可以被绘制的区域——包括已加载的图像——抽象为一个“表面(surface)”。(这适用于软件渲染——在后续章节中,我们将讨论不使用表面的 GPU 渲染。)
结构体 SDL_Surface 以及函数 SDL_LoadBMP() 和 SDL_GetWindowSurface() 提供了软件渲染(也称为 blitting, 位图操作)的 API。
正如你所料,可以使用SDL_GetWindowSurface()来获取窗口的表面(surface)。在你在这个表面上完成绘制之后,通过调用 DL_UpdateWindowSurface() 就能在窗口中看到绘制结果。
SDL_Surface* winSurface = SDL_GetWindowSurface( win );
// do drawing
SDL_UpdateWindowSurface( win );
绘制一个矩形
为了测试你的窗口表面是否有正常工作,你可以用一个颜色填充它。最简单的方法是使用 SDL_FillRect() 。如果想填充整个窗口,简单情况下只需传入 NULL,而无需用一个 SDL_Rect 指针。此外 SDL_FillRect() 接受一个特定格式的数字来表示颜色。要获得这种格式的颜色值,可以调用 SDL_MapRGB() ,并传入表面的格式以及期望的 RGB 数值。
SDL_FillRect( winSurface, NULL, SDL_MapRGB( winSurface->format, 255, 90, 120 ));
关闭
当你的程序完成了所有操作之后,它必须销毁窗口并释放所有资源。你可能会想到,用 SDL_DestroyWindow() 来处理。这个函数会关闭你的窗口,并释放关联的内存资源(包括窗口表面)。
SDL_DestroyWindow( win );
win = NULL;
winSurface = NULL;
最后,要彻底关闭整个 SDL,只需调用 SDL_Quit()。很好理解这段:
SDL_Quit();
附录
文章中所用到的代码完整内容如下:
#include <iostream>
#include <SDL.h>
// You shouldn't really use this statement, but it's fine for small programs
using namespace std;
// You must include the command line parameters for your main function to be recognized by SDL
int main(int argc, char** args) {
// Pointers to our window and surface
SDL_Surface* winSurface = NULL;
SDL_Window* window = NULL;
// Initialize SDL. SDL_Init will return -1 if it fails.
if ( SDL_Init( SDL_INIT_EVERYTHING ) < 0 ) {
cout << "Error initializing SDL: " << SDL_GetError() << endl;
system("pause");
// End the program
return 1;
}
// Create our window
window = SDL_CreateWindow( "Example", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1280, 720, SDL_WINDOW_SHOWN );
// Make sure creating the window succeeded
if ( !window ) {
cout << "Error creating window: " << SDL_GetError() << endl;
system("pause");
// End the program
return 1;
}
// Get the surface from the window
winSurface = SDL_GetWindowSurface( window );
// Make sure getting the surface succeeded
if ( !winSurface ) {
cout << "Error getting surface: " << SDL_GetError() << endl;
system("pause");
// End the program
return 1;
}
// Fill the window with a white rectangle
SDL_FillRect( winSurface, NULL, SDL_MapRGB( winSurface->format, 255, 255, 255 ) );
// Update the window display
SDL_UpdateWindowSurface( window );
// Wait
system("pause");
// Destroy the window. This will also destroy the surface
SDL_DestroyWindow( window );
// Quit SDL
SDL_Quit();
// End the program
return 0;
}