【C语言探索之旅】 第三部分第二课:SDL开发游戏之创建窗口和画布
内容简介
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里:
注:小编会在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开发游戏之显示图像
程序员联盟社区
程序员联盟官网:
目前有一个微信群和一个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
《程序员联盟》的微社区,方便大家提问和互动。可以关注一下。
微社区地址和二维码如下:
谢谢!
程序员联盟 微信公众号
*您若觉得本文不错,请点击画面右上角《···》按钮“分享到朋友圈”或“发送给朋友”
*新朋友请关注「程序员联盟」微信搜公众号 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语言探索之旅】 第一部分第九课:函数
内容简介 1.课程大纲 2.第一部分第九课:函数 3.第一部分第十课预告: 练习题+习作 课程大纲 我们的课程分为四大部分,每一个部分结束后都会有练习题,并会公布答案.还会带大家用C语言编写三个游戏. ...
- 【C语言探索之旅】 开宗明义及第一课:什么是编程?
内容简介 1.课程大纲 2.第一部分第一课:什么是编程? 3.第一部分第二课预告:工欲善其事,必先利其器 课程大纲 不知道为什么,一直对C语言有一种很深厚的“情怀”(类似老罗对锤子手机的那种),说 ...
- 【C++探索之旅】第一部分第二课:C++编程的必要软件
内容简介 1.第一部分第二课:C++编程的必要软件 2.第一部分第三课预告:第一个C++程序 C++编程的必要软件 经过上一课之后,大家是不是摩拳擦掌,准备大干一场了呢. 这一课我们来做一些C++开发 ...
- 【Linux探索之旅】第一部分第二课:下载Linux,免费的噢
内容简介 1.第一部分第二课:下载Linux,免费的噢 2.第一部分第三课预告:测试并安装Ubuntu 下载Linux,免费的噢 大家好,上一课我们认识了非常“霸气侧漏”的Linux操作系统. 也知道 ...
- 【C语言探索之旅】 第三部分第一课:SDL开发游戏之安装SDL
内容简介 1.课程大纲 2.第三部分第一课: SDL开发游戏之安装SDL 3.第三部分第二课预告: SDL开发游戏之创建窗口和画布 课程大纲 我们的课程分为四大部分,每一个部分结束后都会有练习题,并会 ...
- 【C语言探索之旅】 第二部分第三课:数组
内容简介 1.课程大纲 2.第二部分第三课: 数组 3.第二部分第四课预告:字符串 课程大纲 我们的课程分为四大部分,每一个部分结束后都会有练习题,并会公布答案.还会带大家用C语言编写三个游戏. C语 ...
- C语言探索之旅】 第一部分第四课第三章:变量的世界之显示变量内容
内容简介 1.课程大纲 2.第一部分第四课第三章:变量的世界之显示变量内容 3.第一部分第五课预告:基本运算 课程大纲 我们的课程分为四大部分,每一个部分结束后都会有练习题,并会公布答案.还会带大家用 ...
- 【C语言探索之旅】 第三课:你的第一个程序
内容简介 1.课程大纲 2.第一部分第三课:你的第一个程序 3.第一部分第四课预告:变量的世界 课程大纲 我们的课程分为四大部分,每一个部分结束后都会有练习题,并会公布答案.还会带大家用C语言编写三个 ...
- 【C语言探索之旅】 第二部分第二课:进击的指针,C语言的王牌!
内容简介 1.课程大纲 2.第二部分第二课: 进击的指针,C语言的王牌 3.第二部分第三课预告: 数组 课程大纲 我们的课程分为四大部分,每一个部分结束后都会有练习题,并会公布答案.还会带大家用C语言 ...
随机推荐
- 【Gapps】安装GooglePlay引发一系列问题
再次感谢小海的支持,感谢大家的支持! 从安装CM至如今GooglePlay,小海为我提供了非常多方案,能够说是全面支持.仅仅是出于隐私不便公开他的个人信息,仅提供一个他的博客地址http://luha ...
- Ubuntu12.04下使用virtualbox4.3.12 amd64安装XP系统教程
首先第一步打开已安装好的Virtualbox4.3.12,效果图例如以下: 第二步:点击新建进入新建虚拟电脑界面,填写名称,选择类型和版本号(我这里使用的三XP 64bit): 第三步:选择内存大小, ...
- Python的Tkinter去除边框
from Tkinter import * class Application(Frame): def __init__(self,master=None, *args, **kwargs): Fra ...
- Java中的statickeyword具体解释
1.statickeyword主要有2个作用: ①为某特定的数据类型或者对象分配单一的存储空间.而与创建对象的个数无关. ②在不创建对象的情况下能够直接通过类名来直接调用方法或者使用类的属性. 2.s ...
- cocoa动态方法决议及消息转发
假设给一个对象发送不能响应的消息,同一时候又没有进行动态方法决议,又没实现消息转发,那么就会引发以下的crash信息 2014-07-30 15:47:54.434 MethodNotFind[171 ...
- 关于SVN配置文件的一个小例子
1 背景假设 厦门央瞬公司是一家电子元器件设备供应商,其中有个ARM部门,专门负责ARM芯片的方案设计.销售,并在北京.上海各设立了一个办事处.对于工作日志,原先采用邮件方式发给经理,但是这种方式 ...
- poj3176--Cow Bowling(dp:数塔问题)
Cow Bowling Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 14028 Accepted: 9302 Desc ...
- 使用Maven管理Spring
原文链接: Spring with Maven原文日期: 2013年04月17日翻译日期: 2014年06月29日翻译人员: 铁锚 1. 概述本教程向您展示怎样通过 Maven 管理 Spring 的 ...
- tarjan+缩点
B - Popular Cows Time Limit:2000MS Memory Limit:65536KB 64bit IO Format:%I64d & %I64u Su ...
- 探索Oracle数据库升级6 11.2.0.4.3 Upgrade12c(12.1.0.1)
探索Oracle数据库升级6 11.2.0.4.3 Upgrade12c(12.1.0.1) 一.前言: Oracle 12c公布距今已经一年有余了,其最大亮点是一个能够插拔的数据库(PD ...