在上一篇文章中我们已经利用 SDL 的日志接口实现了简单的字符串输出,实际上是解决了开发环境搭建问题,接下来我们将在已有代码的基础上继续开发,实现第一个窗口的创建和背景色绘制。 初始化 首先设置日志输出级别: SDL_SetLogPriorities(SDL_LOG_PRIORITY_VERBOSE
在上一篇文章中,我们利用SDL的日志接口实现了简单的字符串输出,解决了开发环境搭建问题。接下来,我们将在已有代码的基础上继续开发,实现第一个窗口的创建和背景色绘制。
SDL_SetLogPriorities(SDL_LOG_PRIORITY_VERBOSE);
由于仍处于开发阶段,我们将输出日志级别设置为最低的VERBOSE。这样可以使所有的日志都输出,有助于观察SDL的运行情况。当出现错误时,可以获得尽可能详细的出错信息,有助于快速定位问题。
接下来,初始化SDL库,参数
SDL_INIT_VIDEO
指定初始化的子系统为视频系统:
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
SDL_Log("SDL_Init failed: %s", SDL_GetError());
return -1;
}
实际上,这一步可以省略,因为在调用SDL API时其内部会自行检查和初始化所需使用的子系统。比如接下来要使用的
SDL_CreateWindow
函数,内部有这样的代码:
if (!_this) {
/* Initialize the video system if needed */
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
return NULL;
}
...
}
其中
_this
是视频子系统初始化完成后设置的全局变量。虽然不是必须的,但我们仍然建议调用
SDL_Init
对主要用到的子系统进行显式初始化。这样做有两个目的:清晰完整地展示出执行过程,有助于理解代码;如果执行失败,可以在第一时间定位出错位置,提高排障效率。
SDL_Window* window = SDL_CreateWindow("Hello, SDL3!", 800, 600, SDL_WINDOW_RESIZABLE);
if (!window) {
SDL_Log("Could not create a window: %s", SDL_GetError());
return -1;
}
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
创建和窗口关联的渲染器:
SDL_Renderer* renderer = SDL_CreateRenderer(window, nullptr);
if (!renderer) {
SDL_Log("Create renderer failed: %s", SDL_GetError());
return -1;
}
所有图形图像都是通过渲染器绘制到窗口。以背景色绘制为例,代码如下:
SDL_SetRenderDrawColor(renderer, 16, 0, 16, 255);
SDL_RenderClear(renderer);
SDL_RenderPresent(renderer);
这里使用的背景色是RGB(16, 0, 16)。这种颜色值常见于曾经使用DirectDraw渲染视频的老手们。但这里只是为了和SDL窗口默认的黑色区分,便于观察渲染结果,没有其他特殊效果。
SDL_PollEvent
类似Win32 API中的PeekMessage,无论队列中有无事件都会立即返回;区别只是返回值不同。
SDL_WaitEvent
类似Win32 API中的WaitMessage,如果队列中没有事件会阻塞等待,直到收到第一个事件才返回。
我们使用第二种方式实现事件循环:
SDL_Event event{};
bool keep_going = true;
while (keep_going) {
SDL_WaitEvent(&event);
switch (event.type) {
case SDL_EVENT_QUIT: {
keep_going = false;
break;
}
case SDL_EVENT_KEY_DOWN: {
keep_going = keep_going && (event.key.keysym.sym != SDLK_ESCAPE);
break;
}
case SDL_EVENT_WINDOW_EXPOSED: {
SDL_SetRenderDrawColor(renderer, 16, 0, 16, 255);
SDL_RenderClear(renderer);
SDL_RenderPresent(renderer);
break;
}
}
}
SDL_EVENT_QUIT
表示点击了关闭窗口按钮,所以收到该事件后跳出循环;
SDL_EVENT_KEY_DOWN
表示键盘按下事件。这里我们实现了按下'Esc'键退出的功能;
SDL_EVENT_WINDOW_EXPOSED
表示需要对窗口进行重绘,所以我们将绘制背景色的代码放在这个事件中执行。
可以在while循环中增加一行日志来观察收到了哪些事件:
SDL_Log("Event: %d", event.type);
注意上面这个事件循环的写法和大多数SDL的示例不同。实际上,在这里我们是把SDL当作一个正经的窗口系统在使用,而不是当作一个游戏引擎。两者一个重要的区别是使用游戏引擎时一般是按照固定帧率持续进行窗口重绘,而一般的GUI软件使用窗口系统时只进行必要的重绘,以最大程度节省CPU和GPU的使用。视频虽然也有帧率的概念,但采用的是第二种方式,只在视频帧刷新时执行重绘。
#include
#include
int main(int argc, char* argv[])
{
SDL_SetLogPriorities(SDL_LOG_PRIORITY_VERBOSE);
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
SDL_Log("SDL_Init failed: %s", SDL_GetError());
return -1;
}
SDL_Window* window =
SDL_CreateWindow("Hello, SDL3!", 800, 600, SDL_WINDOW_RESIZABLE);
if (!window) {
SDL_Log("Could not create a window: %s", SDL_GetError());
return -1;
}
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
SDL_Renderer* renderer = SDL_CreateRenderer(window, nullptr);
if (!renderer) {
SDL_Log("Create renderer failed: %s", SDL_GetError());
return -1;
}
SDL_Event event{};
bool keep_going = true;
while (keep_going) {
SDL_WaitEvent(&event);
switch (event.type) {
case SDL_EVENT_QUIT: {
keep_going = false;
break;
}
case SDL_EVENT_KEY_DOWN: {
keep_going = keep_going && (event.key.keysym.sym != SDLK_ESCAPE);
break;
}
case SDL_EVENT_WINDOW_EXPOSED: {
SDL_SetRenderDrawColor(renderer, 16, 0, 16, 255);
SDL_RenderClear(renderer);
SDL_RenderPresent(renderer);
break;
}
}
SDL_Log("Event: %d", event.type);
}
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
输出效果如图:
小编推荐阅读