如果我们要做一个游戏,会涉及到大量的图片,如果这些图片都用单个文件保存,那程序里会加载大量的图片,会有很多表面,处理起来很麻烦,一般会把游戏里使用的图片分类,一类图片保存到一个大图片里,使用的时候在按要求裁剪出需要的部分,这张大图就叫精灵图,裁剪出来的部分叫精灵。
下面我们做一个简单的例子,来演示一下如何裁剪精灵图,需要的图片有两张,第一张是背景图,随便用什么都可以,第二张是精灵图。我们会让精灵在地图上走动。
精灵图里是一个小人各种样子,小人大小都是一样的,图片大小为96*192,所以可以计算出每个小人(精灵)大小为32*48,这样我们就可以按照这个比例抠出精灵。
SDL_Surface *gpScreen;//显示表面
SDL_Surface *gpBackGround;//背景表面
SDL_Surface *gpSpirit;//精灵表面//显示背景图片
gpBackGround = loadImage("background.jpg");
SDL_BlitSurface(gpBackGround,NULL,gpScreen,NULL);
SDL_Flip(gpScreen);//抠出第1副精灵的图
SDL_Rect src[4][3];src[0][0].x = src[0][0].y = 0;//精灵图左上角坐标
src[0][0].w = SPIRITWIDTH; //精灵的大小
src[0][0].h = SPIRITHEIGH;
SDL_BlitSurface(gpSpirit,&src[0],gpScreen,NULL);//在屏幕左上角显示第一个精灵
SDL_Flip(gpScreen);
我们定义了一个SDL_Rect类型的数组src[4][3],每一个数组元素代表了一个精灵在大图上的位置,数组的每一行代表了图片上的每一行图片,然后设置src[0][0]各个成员的值,然后把gpSpirit传输到显示表面上,不过这次我们不是将整个表面传输,而是传输src[0][0]指示的大小,所以在屏幕上显示的不是整个精灵大图,而是由src[0][0]指示的第一个精灵的图。精灵图的裁剪就是这么简单。运行效果:
但精灵图的黄色背景也会显示,这会很难看,如何去除这个背景色呢?SDL中有一个函数可以设定图像的指定颜色透明。
int SDL_SetColorKey(SDL_Surface *surface, Uint32 flag, Uint32 key);
参数:surface是指设定的是那个表面;
flag的值如下表所示
flag值 | 含义 |
0 | 取消colorkey 功能 |
SDL_SRCCOLORKEY | 以 key 为透明色。我们可以用 SDL_MapRGB() 来求得与 surface 的像素格式相符的颜色值。 |
SDL_RLEACCEL | 用RLE (Run Length Encoding) 的方式来提高 Blit 的效率。 |
其中key是要设定为透明的颜色,使用SDL_MapRGB()来取,为什么不能直接指定RGB颜色值呢,因为表面的颜色格式和RGB颜色格式不同,所以必须使用这个函数来返回将RGB颜色值转换成表面格式的颜色值。
Uint32 SDL_MapRGB(SDL_PixelFormat *fmt, Uint8 r, Uint8 g, Uint8 b);
参数:fmt指定表面;r、g、b是颜色值,你可以通过画图软件取得指定图像的颜色值。
有了这两个函数,我们就可以将精灵大图gpSpirit的背景颜色设成透明,显示的时候就没有背景色了。修改前面的代码:
gpBackGround = loadImage("background.jpg");
SDL_BlitSurface(gpBackGround,NULL,gpScreen,NULL);
SDL_Flip(gpScreen);
gpSpirit = loadImage("spirit.bmp");//载入精灵大图
colorkey= SDL_MapRGB(gpSpirit->format, 255, 238, 187);
SDL_SetColorKey(gpSpirit, SDL_SRCCOLORKEY , colorkey );
//抠出第1副精灵的图
SDL_Rect src[4][3];
src[0][0].x = src[0][0].y = 0;//精灵图左上角坐标
src[0][0].w = SPIRITWIDTH; //精灵的大小
src[0][0].h = SPIRITHEIGH;
SDL_BlitSurface(gpSpirit,&src[0][0],gpScreen,NULL);//在屏幕左上角显示第一个精灵
SDL_Flip(gpScreen);
运行后显示效果:
那么你是否还记得我们讲过的键盘事件检测?如果你会了键盘事件检测,我们可以做些有意思的尝试了,我们看一下精灵大图,会发现第一排小人是小人从上往下走的不同姿势,第二排从右往左走,第三排是从左往右走,第四排是从下往上走;那么我们是否可以通过键盘按键来让小人走路,转向呢?
我们首先实现小人直行,要一直往前走,要不停的切换同一排小人图片,比如让小人从上往下走,第一幅图示src[0][0],下一步图示src[0][1],在走一步是src[0][2],那么我们可以设置一个变量step来标示下一步要出现的图片,其初值为0。
1 /*
2 功能:演示精灵图裁剪
3 作者:csl
4 日期:2012-5-11
5 */
6 #include
7 #include
8 #include "SDL.h"
9
10 #include
11
12
13 //屏幕尺寸
14 #define SCREENWIDTH 640
15 #define SCREENHEIGH 480
16 #define BPP 32
17
18 //精灵尺寸
19 #define SPIRITWIDTH 32
20 #define SPIRITHEIGH 48
21
22 SDL_Surface *gpScreen;//显示表面
23 SDL_Surface *gpBackGround;//背景表面
24 SDL_Surface *gpSpirit;//精灵表面
25
26 SDL_Event myEvent;//事件
27
28 SDL_Surface *loadImage(char *aFilename);
29 char *localeToUTF8(char *src);
30
31 int main(int argc,char *argv[])
32 {
33 int quit = 0;
34 char caption[20]={"兵"};
35 SDL_Rect src[4][3];
36 SDL_Rect dst;
37 Uint32 colorkey;
38 int spiritX = 0;//小球的初始坐标
39 int spiritY = 0;
40 int speed = 5;
41 int step = 0;//步伐
42 int i,j;
43
44
45 if((SDL_Init(SDL_INIT_VIDEO)==-1)) //初始化视频子系统
46 {
47 printf("Unable to init SDL: %s\n", SDL_GetError());
48 exit(-1);
49 }
50 atexit(SDL_Quit);// 注册SDL_Quit,当退出时调用,使得退出时程序自动清理
51
52 //创建32位600*480窗口
53 gpScreen = SDL_SetVideoMode(SCREENWIDTH,SCREENHEIGH, BPP, SDL_HWSURFACE | SDL_HWPALETTE | SDL_DOUBLEBUF );
54 if(!gpScreen)
55 {
56 exit(1);
57 }
58 printf("%s\n",caption);
59 SDL_WM_SetCaption(localeToUTF8("兵棋"),NULL);
60
61 //SDL_WM_SetCaption(caption,NULL);
62 SDL_EnableKeyRepeat(500,30);//起动粘连键
63
64 gpBackGround = loadImage("background.jpg");
65 SDL_BlitSurface(gpBackGround,NULL,gpScreen,NULL);
66 SDL_Flip(gpScreen);
67 gpSpirit = loadImage("spirit.bmp");//载入精灵大图
68
69 colorkey= SDL_MapRGB(gpSpirit->format, 255, 238, 187);
70 SDL_SetColorKey(gpSpirit, SDL_SRCCOLORKEY , colorkey );
71
72 //设定每一个精灵在精灵大图上的位置
73 for (i &#61; 0;i<4;i&#43;&#43;)
74 {
75 for (j &#61; 0;j<3;j&#43;&#43;)
76 {
77 src[i][j].x &#61; j*SPIRITWIDTH;
78 src[i][j].y &#61; i*SPIRITHEIGH;
79 src[i][j].w &#61; SPIRITWIDTH;
80 src[i][j].h &#61; SPIRITHEIGH;
81 }
82 }
83
84 //抠出第1副精灵的图
85 dst.x&#61;dst.y &#61; 0;
86 SDL_BlitSurface(gpSpirit,&src[0][0],gpScreen,&dst);//在屏幕左上角显示第一个精灵
87 SDL_Flip(gpScreen);
88
89 while (!quit)
90 {
91 while (SDL_PollEvent(&myEvent))
92 {
93 switch (myEvent.type)
94 {
95 case SDL_QUIT:
96 quit &#61; 1;
97 break;
98 case SDL_KEYDOWN:
99 switch(myEvent.key.keysym.sym)
100 {
101 case SDLK_DOWN:
102 spiritY&#43;&#61;speed;
103 step&#43;&#43;;
104 spiritY &#61; (spiritY&#43;SPIRITHEIGH)>SCREENHEIGH?(SCREENHEIGH-SPIRITHEIGH):spiritY;
105 break;
106 case SDLK_MINUS:
107 speed--;
108 speed&#61;speed>0?speed:0;
109 case SDLK_EQUALS:
110 speed&#43;&#43;;
111 }
112
113 step %&#61;3;
114 SDL_BlitSurface(gpBackGround,NULL,gpScreen,NULL);
115 dst.x &#61; spiritX;
116 dst.y &#61; spiritY;
117 dst.w &#61; SPIRITWIDTH;
118 dst.h &#61; SPIRITHEIGH;
119 SDL_BlitSurface(gpSpirit,&src[0][step],gpScreen,&dst);
120 SDL_Flip(gpScreen);
121 break;
122 case SDL_KEYUP:
123 switch(myEvent.key.keysym.sym)
124 {
125 case SDLK_LEFT:
126 printf("小人的坐标&#xff1a;(%d,%d)\n",spiritX,spiritY);
127 break;
128 case SDLK_RIGHT:
129 printf("小人的坐标&#xff1a;(%d,%d)\n",spiritX,spiritY);
130 break;
131 case SDLK_UP:
132 printf("小人的坐标&#xff1a;(%d,%d)\n",spiritX,spiritY);
133 break;
134 case SDLK_DOWN:
135 printf("小人的坐标&#xff1a;(%d,%d)\n",spiritX,spiritY);
136 break;
137 }
138 break;
139 }
140 }
141 }
142
143 //system("pause");
144 return 0;
145 }
146
147
148 /*--------------------------------------------------------------------
149 函数名: loadImage
150 参 数: char *filename 图像文件的名字
151 返回值: SDL_Surface * 返回指向图像表面的指针
152 功 能: 载入图像
153 备 注:
154 ----------------------------------------------------------------------*/
155 SDL_Surface *loadImage(char *aFilename)
156 {
157 SDL_Surface* loadedImage &#61; NULL;
158 SDL_Surface* optimizedImage &#61; NULL;
159
160 //载入图像
161 loadedImage &#61; IMG_Load( aFilename);
162
163 if( NULL !&#61; loadedImage )//If the image loaded
164 {
165 //创建优化图像
166 optimizedImage &#61; SDL_DisplayFormat( loadedImage );
167
168 //释放loadImage
169 SDL_FreeSurface( loadedImage );
170 }
171 return optimizedImage;
172 }
173
174 /*--------------------------------------------------------------------
175 函数名: loadImage
176 参 数: char *src 中文字符串
177 返回值: char * UTF8字符串
178 功 能: 将汉字转换成UTF8字符串
179 备 注:
180 ----------------------------------------------------------------------*/
181 char *localeToUTF8(char *src)
182 {
183 static char *buf &#61; NULL;
184 wchar_t *unicode_buf;
185 int nRetLen;
186
187 if(buf){
188 free(buf);
189 buf &#61; NULL;
190 }
191 nRetLen &#61; MultiByteToWideChar(CP_ACP,0,src,-1,NULL,0);
192 unicode_buf &#61; (wchar_t*)malloc((nRetLen&#43;1)*sizeof(wchar_t));
193 MultiByteToWideChar(CP_ACP,0,src,-1,unicode_buf,nRetLen);
194 nRetLen &#61; WideCharToMultiByte(CP_UTF8,0,unicode_buf,-1,NULL,0,NULL,NULL);
195 buf &#61; (char*)malloc(nRetLen&#43;1);
196 WideCharToMultiByte(CP_UTF8,0,unicode_buf,-1,buf,nRetLen,NULL,NULL);
197 free(unicode_buf);
198 return buf;
199 }
第73-82行将每个小人的位置存储到src数组里&#xff0c;这样我们显示图像就不会错了&#xff0c;第101行我们检测了是否按下了↓键&#xff0c;如果按下了&#xff0c;我们把step的值加1&#xff0c;显示下一步的图片&#xff0c;但向下行走只有三幅图片&#xff0c;所以当step等于3时&#xff0c;应当将其置成0&#xff0c;这一步在第113行用了一个模运算完成&#xff0c;接下来就是定位将图片显示在屏幕上的位置&#xff0c;这个在键盘检测教程就已经说了&#xff0c;这不在累述&#xff0c;然后显示。运行结果可以看到很像一个人在行走。
现在&#xff0c;程序还有一点问题&#xff0c;就是不能转向&#xff0c;我们可以再设一个变量direct表示方向&#xff0c;其值为0表示向下&#xff0c;1向左&#xff0c;2向右&#xff0c;3向上&#xff0c;并且direct的值对应src行下标&#xff0c;然后再增加对其他三个方向的检测就可以了。另外我们在这个教程里设置了应用程序的名字并且名字是中文&#xff0c;所以需要加载windows.h头文件&#xff0c;这个会在后续章节讲述。
这个程序没有对小人进行碰撞检测&#xff0c;所以小人遇树撞树&#xff0c;以后会做碰撞检测的讲解&#xff0c;完整的代码请点击这儿下载。