【C语言探索之旅】 第三部分第二课:SDL开发游戏之创建窗口和画布

2023-02-21,,


内容简介

1第三部分第二课: SDL开发游戏之创建窗口和画布

2、第三部分第三课预告: SDL开发游戏之显示图像


第三部分第二课:SDL开发游戏之创建窗口和画布

在上一课中,我们对SDL这个开源库做了介绍,也带大家配置了SDL的开发环境。请大家按照上一课的步骤创建一个SDL工程,能够初步运行。

如果遇到问题,可以百度,Google相关平台SDL的配置。或者联系小编。

当然了,有些朋友可能会说开发C语言游戏还可以用GTK+这个库,但是个人认为GTK+没有SDL那么适合开发游戏,其创建图形界面的能力倒是很强的。

SDL使得你可以用很少的资源占用,开发出很强大的游戏。而且我发现SDl很容易就能帮助我们开发出一些画风比较复古的游戏,如果你掌握好了SDL,那么开发怀旧的街机游戏是很简单的。

老的SDL版本一般是1.2版,但是目前已经不再怎么更新了吧,SDL官网最新的稳定版本(截止2015年6月23日)是SDL2.0.3,有人说2.0.4版也快发布了。

SDL1.2到SDL2.0,是一个很大的飞跃,增加了很多新的元素,性能也增强了很多。

不过可惜的是SDL2的API不向后兼容,不过一般来说SDL1.2编写的程序要迁移到SDL2.0,改动是不太大的。官网也有migration(迁移)的教程贴(全是英语,又一次“论学好英语对编程的帮助”,虽不是必须,但做编程不会英语是很可惜的):

http://wiki.libsdl.org/MigrationGuide

推荐一个不错的百度贴吧:SDL吧

http://tieba.baidu.com/f?kw=sdl&ie=utf-8

如果你英语还不错,那么SDL官网的WiKi毫无疑问是最好的老师了,所有你想知道的SDL的知识几乎都在WiKi里:

http://wiki.libsdl.org/

:小编会在Mac OS下的XCode上用SDL2来编写演示下面的课程。其他平台(Windows,Linux等)类似,就是环境配置略有不同,SDL具有可移植性。


SDL的加载和停止

可以说大部分的C语言第三方库在使用时,都需要初始化,使用完毕都要停止。SDL库的使用也不例外。

SDL库在使用之初,需要加载一些信息到内存中,以便正常运行。这些信息是被动态地加载到内存中的,用到了malloc函数,那么释放这些信息,就要用到与malloc对应的函数free了。

大家每次用malloc函数申请了一块内存,使用完毕不再需要时,一定要记得用free函数释放。不然内存会泄露太多,最终导致没有空间可以分配。

SDL库里面为我们提供了完成加载和释放信息的两个配对函数:

    SDL_Init:将SDL加载到内存中(调用一些malloc函数)

    SDL_Quit:将SDL从内存中释放(调用一些free函数)

所以,我们在构建SDL程序时,一开始需要调用SDL_Init,最后结束时需要调用SDL_Quit函数。

SDL_Init:将SDL加载到内存中

函数原型:

int SDL_Init(Uint32 flags)

SDL_Init函数必须在最开始调用,也就是使用任何其他SDL函数之前。它只有一个参数,这个参数是一个Uint32类型的(其实是系统定义的,就是int类型。不过是32位的无符号整型,也就是长度为4个字节的unsigned int型)。这个参数flags可以取下表中的一个值,代表我们下面程序中要用到SDL的哪一个子系统:

SDL_INIT_TIMER

计时器子系统

SDL_INIT_AUDIO

音频子系统

SDL_INIT_VIDEO

视频子系统

SDL_INIT_JOYSTICK

操纵杆子系统

SDL_INIT_HAPTIC

触屏反馈子系统

SDL_INIT_GAMECONTROLLER

控制器子系统

SDL_INIT_EVENTS

事件子系统

SDL_INIT_EVERYTHING

上面所有的子系统

以上表格中所列出的其实都是一些用#define定义的预处理常量。不太记得预处理常量的读者可以回去复习我们的《【C语言探索之旅】 第二部分第五课:预处理》。

给出在SDL.h头文件中,以上一些常量的定义:

#define SDL_INIT_TIMER   0x00000001#define SDL_INIT_AUDIO 0x00000010#define SDL_INIT_VIDEO  0x00000020

那你要问了:“SDL库还有子系统吗?”

是的。SDL有的子系统(部分的库)是负责屏幕显示的,有的是负责视频处理的,有的是负责操纵杆(小时候玩的那种游戏的操纵杆、摇杆)输入,等。这么多不同的子系统组成了SDL这个强大的库。

所以,如果我们这样调用:

SDL_Init(SDL_INIT_VIDEO);

就是告诉程序,我接下来要使用SDL库中视频处理那一部分的子系统。这样我们就可以创建一个窗口,在里面绘制各种图形,写文字,等等。

这样的方法在C语言编程中是很常用的,就是用#define来定义一些预处理常量(有时也叫“宏常量”),特别在嵌入式编程中非常有用。

这样做的好处是我们并不需要记住各种复杂的数值,而只需要记住几个英文的常量名,而且这些常量名是很容易见名知意的。例如:SDL_INIT_VIDEO中,SDL当然是指代SDL库,init是英语“初始化”的意思,video是“视频”的意思,那么这个常量就告诉SDL_Init函数,我们需要初始化SDL的视频子系统,这样视频子系统的各种信息就会被预先加载到内存中了。

因此,SDL_Init函数只要识别传给它的唯一参数的数值,就知道到底要加载哪些SDL子系统了。

如果我们调用:

SDL_Init(SDL_INIT_EVERYTHING);

那么就会加载所有SDL的子系统。一般不需要用到所有的(everything是英语“每样东西”,“所有”的意思)子系统,因为我们没有必要让电脑加载那些用不到的子系统,浪费内存。

你的问题又来了:“如果我同时需要加载两个或以上的子系统,该怎么做呢?”

这就要用到“按位或”运算符了。我们需要讲一下位运算的知识。

位运算

还记得我们以前的课程《【c语言探索之旅】第一部分第六课:条件表达式》里讲过的“逻辑运算符”吗?

那时候我们介绍了几个逻辑运算符:

逻辑运算符

意义

&&

逻辑与

||

逻辑或

!

逻辑非

逻辑与 &&

 

if (age > 18 && age < 25)

两个 & 号连在一起表示逻辑与,就是说当两边的表达式都为真时,括号中的整个表达式才为真,所以这里只有当age大于18并且小于25的情况下,括号里的表达式才为真。

逻辑或 ||

为了做逻辑或判断,我们则要用到两个 | 符号。逻辑或只要其两边的两个表达式有一个为真,整个表达式就为真。

if (age > 20 || money > 15000)

只有当age大于20或者money大于15000的情况下,括号里的表达式才为真。

逻辑非 !

逻辑非符号是感叹号,表示“取反”,加在表达式之前。如果表达式为真,那么加上感叹号则为假;如果表达式为假,那么加上感叹号则为真。

if (!(age < 18))

上面的表达式只有当age大于或等于18时才为真)。

今天来介绍一下与逻辑运算符有点“类似”但不同的“按位”运算符:

位运算,顾名思义是对“位”的运算。那么什么是“位”呢?

这里的“位”是比特位的意思,英文是“bit”。也就是我们以前说过的一个二进制位。

我们电脑最小的可读取单元就是一个bit位了,只能取0或1值(因为电脑里各样大大小小的半导体只有通和不通两种状态)。

我们平时所说的一个字节是由8个bit位组成的,例如:

01101110

C语言提供了6种位运算符:

按位运算符

意义

&

按位与

|

按位或

^

按位异或

~

取反

<<

左移

>>

右移

按位与运算符 &

只有参与&运算的两个位都为1时,结果才为1,否则为0。例如1&1为1,0&0为0,1&0为0。

数值在内存中以二进制的形式存在,9&5可写算式如下:
      00001001    (9的二进制)
   &00000101    (5的二进制)
      00000001    (1的二进制)
所以9&5=1。

按位与运算通常用来对某些位清0或保留某些位。例如把 a 的高16位清 0 ,保留低16位,可作a&65535运算(65536占用4个字节。65535的二进制表示为00000000000000001111111111111111)。

注:

严格来说,在计算机系统中,数值在内存中一律以补码形式存在。正数的补码与它的二进制形式相同(与其原码一致),负数则不一样。

负数的补码:符号位为 1,其余位为该数绝对值的原码按位取反,然后整个数加 1。

按照负数补码的规则,可以知道-1的补码为 0xff(对应的二进制为11111111),-2 的补码为 0xfe(对应的二进制是11111110)。

使用补码,可以将符号位和其它位统一处理;同时,减法也可按加法来处理。另外,两个用补码表示的数相加时,如果最高位(符号位)有进位,则进位被舍弃。

按位或运算符 |

参与或运算|的两个二进制位有一个为1时,结果就为1,两个都为0时结果才为0。例如1|1为1,0|0为0,1|0为1。

9|5可写算式如下:
    00001001    (9的二进制)
  |00000101    (5的二进制)
    00001101    (13的二进制)
所以9|5=13。

按位或运算可以用来将某些二进制位置1,而保留某些位。

按位异或运算符 ^

参与异或运算^的两个二进制位不同时,结果为1,相同时结果为0。也就是说,0^1为1,0^0为0,1^1为0。

9^5可写成算式如下:
     00001001    (9的二进制)
   ^00000101    (5的二进制) 
     00001100    (12的二进制)
所以9^5=12。

按位异或运算可以用来反转某些二进制位。

取反运算符 ~

取反运算符~为单目运算符,右结合性。作用是对参与运算的数的各二进位按位取反。例如 ~1为0,~0为1。

~9的运算为:
   ~0000000000001001
     1111111111110110
所以~9=65526。

左移运算符 <<

左移运算符<<用来把操作数的各二进位全部左移若干位,高位丢弃,低位补0。例如:

a=9;a<<3;

<<左边是要移位的操作数,右边是要移动的位数。

上面的代码表示把a的各二进位向左移动3位。a=00001001(9的二进制),左移3位后为01001000(十进制72)。

右移运算符 >>

 

右移运算符>>用来把操作数的各二进位全部右移若干位,低位丢弃,高位补0(或1)。例如:

a=9;a>>3;

表示把a的各二进位向右移动3位。a=00001001(9的二进制),右移3位后为00000001(十进制1)。

位运算是C语言的一个难点,在嵌入式编程中经常要用到。特别是这些运算符的结合混搭使用,是非常令人头痛的。

所以最好自己经常拿纸笔来做运算,使自己对二进制,十六进制很熟悉,特别是二进制和十六进制的转换。

介绍了位运算,我们回到SDL中。

我们在SDL的函数中要用到的位运算是 “按位或 |”,因为这可以把各个不同的选项“相加”,例如:

// 加载视频和音频子系统SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);

// 加载视频,音频和计时器子系统SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER);

用这样的方式,我们就可以做到“需要哪些子系统就加载哪些”,不必要用SDL_INIT_EVERYTHING。

这些我们传给SDL_Init函数的参数称为flag,也就是“标记,标志”,这在编程中是很常见的一种用法。为了同时拥有多个标记,我们用按位或符号“|”来连接各个标记,有点类似加法。

SDL_Quit:退出SDL

 

SDL_Quit函数的调用非常简单,因为它没有参数:

SDL_Quit();

一旦程序运行这句命令,那么之前加载入内存的所有SDL子系统都将被停止并从内存释放。

这是优雅地结束SDL程序的方式,记得在SDL程序结尾处调用这个函数。

了解了SDL_Init和SDL_Quit这两个函数,我们就可以给出SDL程序的大致框架了:

#include <stdlib.h>#include <stdio.h>#include <SDL2/SDL.h>

int main(int argc, char *argv[]){ SDL_Init(SDL_INIT_VIDEO); // 启动SDL系统 (这里加载了视频子系统) /* SDL已被加载入内存。 在这里可以放置你的SDL程序的主体内容了 */ SDL_Quit(); // 停止SDL (释放内存). return 0;}

暂时我们的main函数中还没填入实质性的内容,只是演示了一下如何初始化和结束SDL库。

错误处理

SDL_Init函数的返回值有两种情况:

0:如果一切顺利

-1:如果有错误

这个返回值很有用,我们可以根据这个值来做对应操作。比如,出错时打印一句话,然后退出程序,因为如果初始化错误,那就没有必要再继续;一切顺利,那么继续往下执行。

虽然这个操作并不是必须,但是这是好习惯,可以让我们的程序更易调试,出了错会容易找到原因。

“如果有错误,那么我们如何打印出错误呢?”

好问题。我们有两种选择:

    printf:用printf函数在终端上打印错误信息。

    fprintf:把错误信息写进文件里。用fprintf函数。

我们选择第二种方式,也就是写入文件的方式。当然了,这种方式比printf麻烦一些。

不过,我们却有一种更简便的写入文件的方式:使用标准错误输出。

什么是标准错误输出呢?

C语言中,有标准输入,标准输出和标准错误输出。

在C语言中,在程序开始运行时,系统自动打开3个标准文件:标准输入、 标准输出、标准错误输出。通常这3个文件都与终端相联系。因此,以前我们所用到的从终端输入或输出都不需要打开终端文件。系统自定义了3个文件指针(FILE *类型)stdin、stdout、stderr,分别指向终端输入、终端输出和标准错误输出(也从终端输出),其值通常是0,1和2。

这些变量都定义在stdio.h这个标准库的头文件里。

而我们的stderr变量就指向了一个地方,我们可以把错误信息写入到这个位置。这个地方在Windows中通常是一个叫做stderr.txt的文件;在Linux中,这个地方通常是在终端里。

这个stderr的变量在程序开始被自动创建,在程序结束会自动销毁。所以我们不需要用到文件操作相关的fopen和fclose函数了。

所以我们就可以用fprintf来写入到stderr上:

#include <stdlib.h>#include <stdio.h>#include <SDL2/SDL.h>

int main(int argc, char *argv[]){   if (SDL_Init(SDL_INIT_VIDEO) == -1)  // 加载SDL;如果出错   {       fprintf(stderr, "SDL 初始化错误 : %s\n", SDL_GetError());  // 打印错误信息       exit(EXIT_FAILURE);  // 退出程序   }   SDL_Quit();   return EXIT_SUCCESS;  // 如果顺利结束}

这下的程序比之前多了什么呢?

    我们把错误信息写入到了标准错误stderr中。%s使得SDL可以把最近的一次错误信息写入,这个错误信息就是SDL_GetError函数的返回值。

    我们在SDL_Init函数出错时调用exit函数来退出我们的程序。exit函数并没有什么新鲜的,之前的课程中我们已经学习过了。不过你可以发现,我们在exit函数的括号中用的是一个常量:EXIT_FAILURE。对应的,如果程序顺利运行,在最后我们用return EXIT_SUCCESS来结束程序。

    EXIT_SUCCESS:程序顺利结束的返回值。

    EXIT_FAILURE:程序发生错误的返回值。

还记得我们以前怎么做的吗?如果程序出错,我们用的返回值是-1;如果程序顺利结束,我们用的返回值是0。

那为什么我们现在如此“任性”,要用EXIT_SUCCESS和EXIT_FAILURE来分别代替正确和出错时的返回值呢?

那是因为不同的操作系统下,代表正确和错误的返回值的数值可能不尽相同,并不一定总是-1和0,stdio.h的内容在不同的操作系统上也是不尽相同的。使用这两个系统常量的优势就是在不同的操作系统下可以被替换为stdio.h定义的对应数值,我们的程序就具备很好的移植性了。


打开一个窗口

好了,我们现在已经学会如何开启和关闭SDL库了。那么我们要开始做一些有趣的事咯:打开一个窗口

从SDL1.2到SDL2,API发生了一些变化。特别是VIDEO(视频)子系统,几乎重写了全部API。因为SDL1.x版本是在20世纪90年代后期设计的,年代相距甚远,硬件设备和操作系统等发生了翻天覆地的进步,所以须要相应的改进。

英语比较好的朋友可以直接看官方给出的SDL1.2版本到SDL2版本的一些变化:

http://wiki.libsdl.org/MigrationGuide

以前创建一个窗口需要调用SDL_SetVideoMode函数。现在创建一个窗口的函数换成了SDL_CreateWindow。SDL1.2只支持单窗口模式,现在的SDL2已经支持多窗口了。

SDL_CreateWindow函数的原型如下:

SDL_Window* SDL_CreateWindow(const char* title, int x, int y, int w, int h, Uint32 flags);

SDL_CreateWindow函数的参数如下:

title

窗口的标题,UTF-8编码

x

窗口的x坐标位置(左上角), SDL_WINDOWPOS_CENTERED(在屏幕正中)或者SDL_WINDOWPOS_UNDEFINED(未定义)

y

窗口的y坐标位置(左上角), SDL_WINDOWPOS_CENTERED(在屏幕正中)或者SDL_WINDOWPOS_UNDEFINED(未定义)

w

窗口的宽

h

窗口的高

flags

0个, 1个或更多的 SDL_WindowFlags ,用按位或连接

以上表格中的flag可以是0,1或多个SDL_WindowFlags的组合,用按位或符号“|”连接。

而SDL_WindowFlags的取值可以是以下这些的组合:

SDL_WINDOW_FULLSCREEN

窗口全屏幕

SDL_WINDOW_FULLSCREEN_DESKTOP

窗口全屏幕,取当前桌面的分辨率

SDL_WINDOW_OPENGL 窗口可以和OpenGL配合使用

SDL_WINDOW_SHOWN

窗口被显示

SDL_WINDOW_HIDDEN

窗口被隐藏

SDL_WINDOW_BORDERLESS

窗口无边框

SDL_WINDOW_RESIZABLE

窗口可以调节大小

SDL_WINDOW_MINIMIZED

窗口最小化

SDL_WINDOW_MAXIMIZED

窗口最大化

SDL_WINDOW_INPUT_GRABBED

窗口得到键盘输入焦点,而且被束缚在窗口内

SDL_WINDOW_INPUT_FOCUS

窗口得到键盘输入焦点

SDL_WINDOW_MOUSE_FOCUS

窗口得到鼠标焦点

所以我们就来创建一个窗口,我们在之前的代码中增加一些内容:

#include <stdlib.h>#include <stdio.h>#include <SDL2/SDL.h>

const int SCREEN_WIDTH = 640;const int SCREEN_HEIGHT = 480;

int main(int argc, char *argv[]){   if (SDL_Init(SDL_INIT_VIDEO) == -1)  // 加载SDL;如果出错   {       fprintf(stderr, "SDL 初始化错误 : %s\n", SDL_GetError());  // 打印错误信息       exit(EXIT_FAILURE);  // 出错退出程序   }      // 创建一个窗口,宽640像素,高480像素   SDL_Window *screen = SDL_CreateWindow("游戏窗口",                                         SDL_WINDOWPOS_UNDEFINED,                                         SDL_WINDOWPOS_UNDEFINED,                                         SCREEN_WIDTH, SCREEN_HEIGHT,                                         SDL_WINDOW_SHOWN);      SDL_Quit();      return EXIT_SUCCESS;  // 顺利退出程序}

我们主要增加了这句命令:

SDL_Window *screen = SDL_CreateWindow("My Game Window",                                        SDL_WINDOWPOS_UNDEFINED,                                        SDL_WINDOWPOS_UNDEFINED,                                        640, 480,                                        SDL_WINDOW_SHOWN);

将程序编译运行,可以看到窗口几乎转瞬即逝。这也不稀奇,因为在创建了窗口之后,我们立马调用了SDL_Quit函数,所以“整个世界都清净了”。

那么,如何让我们创建的窗口保持住而不消失呢?我们可以用pause函数来完成,虽然不是太优雅。

===========================

pause函数是C语言的一个函数:

头文件:#include <unistd.h>

定义函数:int pause(void);

函数说明:pause()会令目前的进程暂停(进入睡眠状态), 直到被信号(signal)所中断.

返回值:只返回-1.

错误代码:EINTR 有信号到达中断了此函数.

===========================

因此,我们修改我们的代码,加入:#include <unistd.h>pause函数。

#include <stdlib.h>#include <stdio.h>#include <unistd.h>#include <SDL2/SDL.h>

const int SCREEN_WIDTH = 640;const int SCREEN_HEIGHT = 480;

int main(int argc, char *argv[]){   if (SDL_Init(SDL_INIT_VIDEO) == -1)  // 加载SDL;如果出错   {       fprintf(stderr, "SDL 初始化错误 : %s\n", SDL_GetError());  // 打印错误信息       exit(EXIT_FAILURE);  // 出错退出程序   }      // 创建一个窗口,宽640像素,高480像素   SDL_Window *screen = SDL_CreateWindow("游戏窗口",                                         SDL_WINDOWPOS_UNDEFINED,                                         SDL_WINDOWPOS_UNDEFINED,                                         SCREEN_WIDTH, SCREEN_HEIGHT,                                         SDL_WINDOW_SHOWN);      if(screen) // 如果创建窗口成功   {       pause(); // 暂停当前进程,使得窗口一直显示   }   else   {       fprintf(stderr, "创建窗口错误: %s\n", SDL_GetError()); // 打印错误信息   }          SDL_Quit();      return EXIT_SUCCESS;  // 顺利退出程序}

现在运行之后,可以看到我们的第一个窗口了吗?是不是很激动?

当然,比较优雅的方式也可以不使用pause函数,而让窗口显示几秒后消失,代码如下:

#include <stdlib.h>#include <stdio.h>#include <SDL2/SDL.h>

const int SCREEN_WIDTH = 640;const int SCREEN_HEIGHT = 480;

int main(int argc, char *argv[]){   if (SDL_Init(SDL_INIT_VIDEO) == -1)  // 加载SDL;如果出错   {       fprintf(stderr, "SDL 初始化错误 : %s\n", SDL_GetError());  // 打印错误信息       exit(EXIT_FAILURE);  // 出错退出程序   }      // 创建一个窗口,宽640像素,高480像素   SDL_Window *screen = SDL_CreateWindow("游戏窗口",                                         SDL_WINDOWPOS_UNDEFINED,                                         SDL_WINDOWPOS_UNDEFINED,                                         SCREEN_WIDTH, SCREEN_HEIGHT,                                         SDL_WINDOW_SHOWN);      if(screen) // 如果创建窗口成功   {       SDL_Delay(10000); // 使窗口保持10秒       SDL_DestroyWindow(screen); // 销毁窗口   }   else   {       fprintf(stderr, "创建窗口错误: %s\n", SDL_GetError()); // 打印错误信息   }      SDL_Quit();      return EXIT_SUCCESS;  // 顺利退出程序}

当然了,SDL2中,我们可以创建多个窗口。

事实上,每次调用SDL_CreateWindow函数都会返回一个指向窗口的结构体指针(SDL_Window*)。然后我们可以操纵每个指针来对每个窗口进行改动和操作。

暂时我们只需要一个窗口就够了。

关于游戏中一般的窗口坐标,我们需要讲一下:

一般游戏中的窗口的坐标原点是在左上角,也就是说,此处的x和y都是0。从左往右横坐标的值(x)逐渐增大;从上往下纵坐标的值(y)逐渐增大。如下图所示:


操纵Surface

既然我们已经学会了如何创建一个窗口。那么我们总要在窗口中显示些东西才有意思嘛对吧,不然黑不溜秋的窗口实在提不起劲。

我们先来看一下SDL_Window这个SDL2定义的结构体的内容是什么:

我们可以在SDL2的代码中的头文件 发现这样一句话:

typedef struct SDL_Window SDL_Window;

这只是一个typedef罢了,就是一个别名而已。所以真实的SDL_Window结构体的定义一定在其他地方。

我们继续寻找,发现原来它的定义在SDL2库的源代码中,在 src/video/SDL_sysvideo.h这个头文件里(藏得这么深。这样做的好处是可以防止使用SDL的程序员修改底层代码,实际上很多大型项目都是这么用的。在用户可见的地方只用一个typedef,而真实的定义在源代码里):

/* Define the SDL window structure, corresponding to toplevel windows */struct SDL_Window{   const void *magic;   Uint32 id;   char *title;   int x, y;   int w, h;   Uint32 flags;   SDL_DisplayMode fullscreen_mode;      SDL_Surface *surface;   SDL_bool surface_valid;   SDL_WindowShaper *shaper;   SDL_WindowUserData *data;   void *driverdata;   SDL_Window *prev;   SDL_Window *next;};

我们可以看到,在SDL_Window结构体中有一个成员:SDL_Surface *

这是一个SDL_Surface类型的指针。SDL_Surface又是什么呢?surface在英语中是“表面”的意思。所以说我们用SDL_CreateWindow函数创建了一个窗口(SDL_Window),它里面其实就有一个表面(SDL_Surface),可以把它看成一个画板,只不过暂时没有填充颜色,是黑色的而已。SDL2中,我们可以在表面上“作画”,也可以将一个表面“黏贴”到另一个表面上(下一课会学到)。

在SDL中,表面是很基础的元素。表面的形状都是矩形。当然,在SDL中我们也可以自己绘制其他图形,如圆形,三角形等,但是这些没有系统提供的函数,你需要自己来绘制或者使用别的开发者的插件。

当然了,SDL2在SDL1.x的基础上又增加了Texture(纹理)和Renderer(渲染器)这两个元素,我们之后的课会介绍。

我们如果要在这个表面上操作,需要首先取得这个表面才行。怎么获得这个窗口中的表面呢?可以使用SDL_GetWindowSurface函数:

SDL_Surface* SDL_GetWindowSurface(SDL_Window* window)

可以看到SDL_GetWindowSurface只有一个参数,就是窗口的结构体指针。返回值就是与此窗口绑定的表面的指针。

所以我们可以这样来写我们的代码,使得窗口的表面颜色变成一个我们指定的颜色:

#include <stdlib.h>#include <stdio.h>#include <SDL2/SDL.h>

const int SCREEN_WIDTH = 640;const int SCREEN_HEIGHT = 480;

int main(int argc, char *argv[]){   SDL_Surface* screenSurface = NULL;      if (SDL_Init(SDL_INIT_VIDEO) == -1)  // 加载SDL;如果出错   {       fprintf(stderr, "SDL 初始化错误 : %s\n", SDL_GetError());  // 打印错误信息       exit(EXIT_FAILURE);  // 出错退出程序   }      // 创建一个窗口,宽640像素,高480像素   SDL_Window *screen = SDL_CreateWindow("游戏窗口",                                         SDL_WINDOWPOS_UNDEFINED,                                         SDL_WINDOWPOS_UNDEFINED,                                         SCREEN_WIDTH, SCREEN_HEIGHT,                                         SDL_WINDOW_SHOWN);      if(screen) // 如果创建窗口成功   {       screenSurface = SDL_GetWindowSurface(screen); // 获得窗口的表面              SDL_FillRect(screenSurface, NULL, SDL_MapRGB(screenSurface->format, 17, 206, 112)); // 用指定颜色填充此表面              SDL_UpdateWindowSurface(screen);  // 刷新窗口表面              SDL_Delay(10000); // 使窗口保持10秒              SDL_DestroyWindow(screen); // 销毁窗口   }   else   {       fprintf(stderr, "创建窗口错误: %s\n", SDL_GetError()); // 打印错误信息   }      SDL_Quit();  // 卸载SDL      return EXIT_SUCCESS;  // 顺利退出程序}

其中的SDL_FillRect函数用于以指定颜色填充指定的表面,其原型如下:

int SDL_FillRect(SDL_Surface* dst, const SDL_Rect* rect, Uint32 color)

dst

目标SDL_Surface结构体

rect

SDL_Rect 结构体,是矩形。代表了填充的矩形区域,如果是NULL那么填充整个dst表面

color

填充的颜色(由红,绿,蓝三原色组成)

SDL_UpdateWindowSurface函数则用于刷新窗口的表面,其原型如下:

int SDL_UpdateWindowSurface(SDL_Window* window)

唯一的参数window就是要刷新的窗口。

颜色组成

颜色是由红,绿,蓝这三原色混合而成,每种颜色的取值范围都是0~255。如果三种原色的取值都是0,那么整体的颜色就是黑色;如果三种原色的取值都是255,那么整体的颜色就是白色。其他不同的取值会组合成很多种不同的颜色(一共有256 * 256 * 256 = 16777216 种之多),所以我们的大千世界才是如此色彩斑斓啊。

上述程序中,我们的红,绿,蓝的取值分别为17, 206, 112,所以整体的颜色就是这样一种蓝绿色。

运行以上的程序,我们的窗口就有很好看的颜色了。


总结

    SDL程序在开始处需要使用SDL_Init函数来加载,在结尾处要使用SDL_Quit函数来卸载。

    flag(标记)是一些常量,这些常量可以用按位或操作符“|”来连接,就好像相加一般,使多个特性可以同时具有。

    SDL的基础元素之一是“表面”(Surface),是SDL_Surface结构体类型,形状是矩形。我们可以在这些表面上“作画”。

    总是至少有一个“表面”,就是我们创建的窗口的那个表面。

    填充“表面”可以使用函数SDL_FillRect。

    颜色是由红,绿,蓝这三原色组成的。每一组分的取值范围都是0~255。


第三部分第三课预告:

今天的课就到这里,一起加油吧。

下一次我们学习:  SDL开发游戏之显示图像


程序员联盟社区

程序员联盟官网:

http://coderunity.com/

目前有一个微信群和一个QQ群,凡是对编程感兴趣的朋友都可以加,大家可以交流,学习,互动,讨论编写的程序的源代码,编程问答等。

微信群(程序员联盟),加群请私信我(微信群人数超过100之后,不能通过扫描二维码加入了,只能私信我,谢谢)

QQ群: 413981577 (1000人群)

QQ群文件里有很多编程书籍PDF和其他资料。扫描下面二维码加QQ:

我们还建立了一个公共的百度云盘,2TB容量,已有很多优秀编程资源,大家也可以上传。链接加群之后会发送。

百度贴吧 【程序员联盟】 欢迎您加入,交流编程,讨论代码,共享资源,已经有很多话题。吧主就是小编。

http://tieba.baidu.com/f?kw=%E7%A8%8B%E5%BA%8F%E5%91%98%E8%81%94%E7%9B%9F&ie=utf-8

《程序员联盟》的微社区,方便大家提问和互动。可以关注一下。

微社区地址和二维码如下:

http://m.wsq.qq.com/264152148

谢谢!


程序员联盟 微信公众号

*您若觉得本文不错,请点击画面右上角《···》按钮“分享到朋友圈”或“发送给朋友”

*新朋友请关注「程序员联盟」微信搜公众号  ProgrammerLeague

小编微信号: frogoscar

小编邮箱:    enmingx@gmail.com

程序员联盟官网:coderunity.com

小编QQ号:  379641629

程序员联盟QQ群:413981577

程序员联盟微信群:先加我微信

有朋友反映看手机端的文章太累,其实是可以用浏览器网页来看的:

方法1. 点击画面右上角的《···》按钮,然后选择“复制链接”,再把链接黏贴到你的浏览器里面或用邮件发送给自己,就可以在电脑的浏览器里打开了

方法2. 头条网www.toutiao.com,搜索我的自媒体“程序员联盟”,内有所有文章,也可以直接进这个链接:http://www.toutiao.com/m3750422747/

方法3. 我的51CTO博客,CSDN博客,博客园和开源中国博客链接(所有文章都在上面)
http://4526621.blog.51cto.com

http://blog.csdn.net/frogoscar

http://www.cnblogs.com/frogoscar

http://my.oschina.net/frogoscar/blog

如何查看所有文章

1. 点击“查看公众号”,再点击“查看历史消息

2. 在公众号回复任何信息,可以看到包含“查看历史消息”的链接。

【支持小编的劳动】

觉得文章对你有帮助,请纪念小编的辛勤劳动,扫描二维码捐赠给小编,谢谢!

支付宝

Paypal

【C语言探索之旅】 第三部分第二课:SDL开发游戏之创建窗口和画布的相关教程结束。

《【C语言探索之旅】 第三部分第二课:SDL开发游戏之创建窗口和画布.doc》

下载本文的Word格式文档,以方便收藏与打印。