codecamp
案例一、你的第一个SDL2程序:创建一个窗口

引入头文件

要使用 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 存储,以保证不会乱码。 SDL2窗口标题

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)
);

此函数有三个参数:

  1. 目标 surface,指定你要填充的 surface,在这里是 screenSurface。
  2. 指定是完整填充还是部分填充若为空值,则填充整个 Surface;若要部分填充,则需要指定其他值(此处暂不讨论)。
  3. 用于指定填充的颜色,但不是直接传入 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万毫秒)后自动退出。

案例二、在 surface 上面贴一张图片
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }