引入头文件
要使用 SDL2 的任何功能之前,你需要引入 SDL2 的头文件:
#include <SDL2/SDL.h>
【推荐】另外还需要stdio.h,用于 printf、scanf 等常规 C 函数:
#include <stdio.h>
定义宏
然后,我们需要定义两个整数宏,指定之后要创建的窗口大小:
#define SCREEN_WIDTH 640 //横向像素个数
#define SCREEN_HEIGHT 480 //纵向像素个数
另外,我们可以采用宏定义,将 printf 函数重定向输出到 stderr(标准错误)流中,输出的速度更快:
#define printf(...) fprintf(stderr,__VA_ARGS__)
定义 main 函数
注意:在这里,你必须严格按照以下指定的参数格式定义这个函数。否则,可能会出现Undefined Reference to 'main'的错误。
// 必须按顺序指定以下两个参数,且返回值必须为 int
int main(int argc, char* argv[])
{
/*内容*/
return 0;
}
初始化 SDL2
要使用 SDL 库,您必须首先初始化这个库,才能后续使用它的一切功能。它的初始化函数是 SDL_Init(Uint32 flags) 。
这个函数要求我们至少传入一个标志位,这些标志位表示你之后要使用的功能,如视频、音频、游戏手柄等。
在这里,我们想要创建一个窗口,因此这里需要至少填写SDL_INIT_VIDEO(初始化视频功能)这个标志位。
若要填写多个标志位,请在它们之间用竖杠“|”连接。
例如,我们想要使用 SDL 中的视频和音频的功能,则可以写成:
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
【推荐】判断函数是否执行成功是个好习惯
SDL_Init 的返回值为整数。初始化成功返回 0,否则返回一个负数。则我们可以使用 if 语句判断,如果失败则搭配 SDL_GetError 函数输出错误原因。并让 main 函数返回 -1:
if (SDL_Init(SDL_INIT_VIDEO) < 0)
{
printf("SDL_Init error: %s\n", SDL_GetError());
return -1;
}
SDL 的面向对象思想
SDL 虽然使用 C 语言开发,但它仍然采用面向对象的思想,所有的类都用结构体指针声明,你创建的变量就是它们的实例。
创建窗口
我们首先创建了一个 SDL_Window 对象,用于创建并管理 SDL 窗口的属性与行为。 同时,还调用了 SDL_CreateWindow 创建窗口。当创建成功时返回 SDL_Window 的实例,否则返回空值:
SDL_Window* window = SDL_CreateWindow("SDL2教程范例",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
SCREEN_WIDTH, SCREEN_HEIGHT,
SDL_WINDOW_SHOWN | SDL_WINDOW_VULKAN);
上面这个函数有六个参数,分别是:
1: 窗口标题。它支持 UTF-8,但是您需要将 C 源文件的格式也以 UTF-8 存储,以保证不会乱码。

2、3: 窗口摆放位置。可以是自己填写一个整数,用于表示相对于屏幕左上角的偏移像素。或者使用一个宏定义,用于居中对齐或保持默认。这里启用了水平和垂直都居中(SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED)。
4、5: 窗口的宽与高,单位是像素。这里指定了 640x480(我们自己定义了宽和高的宏)。
6: 标志位。用于指定窗口的属性,如是否有边框、是否全屏等。这里指定了窗口默认是显示在前台的,且使用了高性能的 Vulkan 图形 API。(SDL_WINDOW_SHOWN | SDL_WINDOW_VULKAN)
SDL_CreateWindow 一旦失败则返回假值。和上面的道理一样,我们还可以对它的返回值做个判断,出错时输出错误信息:
if (!window)
{
printf("SDL_CreateWindow error: %s\n", SDL_GetError());
return -1;
}
创建 Surface(表面)
与其他图形库不同的是,SDL2 采用了 surface 进行 2D 绘制,而不能直接绘制到窗口中。 Surface 的中文意思是“表面”,是之后您绘制任何 2D 图形的场所,例如图片、矩形等。您可以将它想象成一张“画布”,所有的 2D 图形都在此呈现。 我们首先定义一个 SDL_Surface 对象,然后为它实例化:
SDL_Surface* screenSurface = SDL_GetWindowSurface(window);
if(!screenSurface)
{
printf("SDL_GetWindowSurface error: %s",SDL_GetError());
return -1;
}
在这里我们使用 SDL_GetWindowSurface 这个函数,直接将整个窗口作为一个 surface 使用,用以实现全屏 2D 绘制。 这个函数本身并不知道我们到底创建了几个窗口,也不知道我们希望把 Surface 放置在哪个窗口上,因此它要求我们将 SDL_Window 对象作为参数传入来告诉它。 【推荐】像往常一样,我们依然使用 if 语句判断函数是否执行成功。在后面的教程中不再赘述。
SDL 的 surface 对象默认是采用 CPU 来绘制的,渲染性能可能远远不及 GPU(显卡)。但我们只是在做新手练习,对性能的要求不是很高。具体如何调用 GPU 进行绘制,我们以后再谈论。
为 Surface 填充颜色
本案例将会为 Surface 填充红色(#FF0000)。使用 SDL_FillRect 函数可以做到填充颜色的功能。
SDL_FillRect(
screenSurface,
NULL,
SDL_MapRGB(
screenSurface->format,
0xFF, 0x00, 0x00)
);
此函数有三个参数:
- 目标 surface,指定你要填充的 surface,在这里是 screenSurface。
- 指定是完整填充还是部分填充。若为空值,则填充整个 Surface;若要部分填充,则需要指定其他值(此处暂不讨论)。
- 用于指定填充的颜色,但不是直接传入 RGB 值。 此处嵌套了 SDL_MapRGB 函数,它有四个参数:第一个参数暂时无需关心,直接照着抄;剩下的三个参数分别为你要指定的 RGB 值。
交换缓冲区
SDL 采用双缓冲技术。显卡是按照从左到右、从上到下的顺序依次绘制像素的,而这种技术可以避免人眼看到显卡绘制不完全时的状态。这种不良现象也被俗称为“画面撕裂”现象。 两个帧缓冲区一个在前台,另一个在后台。SDL 默认都在后台缓冲区绘制图形,并不能立即显示在屏幕上。必须调用 SDL_UpdateWindowSurface 这个函数交换这两个缓冲区的位置,将前台缓冲区置入后台,而后台的置到前台,呈现到屏幕上。
SDL_UpdateWindowSurface(window);
如不调用此函数,那么在本案例中,您的窗口将会是透明、黑色或白色的(因平台或硬件而异)。 在以后案例中,如果您要绘制动态的图形,则必须逐帧调用它。否则画面将静止,直到您再次调用才会发生改变。
线程阻塞
当我们完成了创建窗口和 surface,并交换了缓冲区后,窗口会瞬间关闭,程序退出。解决此问题的办法是将程序停顿一下,从而让我们有足够的时间看到这个窗口。 我们使用 SDL_Delay 函数来实现。我们需要传入想要停顿的毫秒数。1 秒等于 1000 毫秒,所以我们想要停顿 10 秒,则此处应填写 10000:
SDL_Delay(10000);
注意:在阻塞程序期间,程序无法做任何工作,包括响应键盘、鼠标等事件,但是有相应的解决办法,我们以后再谈论。
关闭这个窗口
SDL2 采用动态分配堆内存来实现面向对象。当您的程序完成了所有的工作后,需要销毁关于这个窗口的一切内容,并释放这些内存:
SDL_DestroyWindow(window);
在本案例中无论是否调用它都没什么影响。但是如果你以后要开发大型项目,这请您及时调用它,可以及时清理多余的内存,防止内存占用过高而崩溃。
退出 SDL2
在程序退出前,我们调用 SDL_Quit 函数继续销毁其他的所有堆内存,理由同上。
SDL_Quit();
后记
本案例详细讲述了从初始化到创建窗口、显示画面,再到程序退出的全过程。在之后的教程中,我们都要以本案例为基础,希望大家认真研习这一课的内容。
本案例完整代码
#include<SDL2/SDL.h>
#include<stdio.h>
#define SCREEN_WIDTH 640 //横向像素个数
#define SCREEN_HEIGHT 480 //纵向像素个数
//将printf重定向到stderr
#define printf(...) fprintf(stderr,__VA_ARGS__)
//必须严格按照这种格式定义main函数,否则会出错
int main(int argc, char* argv[])
{ //初始化SDL(视频)并判断是否成功
if (SDL_Init(SDL_INIT_VIDEO) < 0)
{
//若失败,输出错误信息
printf("SDL_Init error: %s\n", SDL_GetError());
//直接退出整个程序
return -1;
}
//创建窗口
SDL_Window* window = SDL_CreateWindow(
"SDL2教程范例",//窗口标题(支持UTF-8,但是必须将你的源文件也保存为UTF-8)
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,//SDL窗口默认位置(指定一个偏移量或居中)
SCREEN_WIDTH, SCREEN_HEIGHT,//窗口大小
SDL_WINDOW_SHOWN | SDL_WINDOW_VULKAN
);
//判断窗口是否创建成功
if (!window)
{
printf("SDL_CreateWindow error: %s\n", SDL_GetError());
return -1;
}
//创建一个 surface,直接将整个屏幕当作一个 surface 处理
SDL_Surface* screenSurface = SDL_GetWindowSurface(window);
if (!screenSurface)
{
printf("SDL_GetWindowSurface error: %s", SDL_GetError());
return -1;
}
//向 surface 填充颜色
SDL_FillRect(
screenSurface,
NULL,
SDL_MapRGB(
screenSurface->format,//暂不研究
0xFF, 0x00, 0x00//#FF0000 红色
)
);
//交换缓冲区
SDL_UpdateWindowSurface(window);
//延迟10秒钟
SDL_Delay(10000);
//销毁窗口
SDL_DestroyWindow(window);
//退出SDL
SDL_Quit();
return 0;
}
编译并运行此代码,您将会看见一个红色(#FF0000)的窗口,停留 10 秒钟(1万毫秒)后自动退出。
