裁剪精灵图片
如果我们要做一个游戏,会涉及到大量的图片,如果这些图片都用单个文件保存,那程序里会加载大量的图片,会有很多表面,处理起来很麻烦,一般会把游戏里使用的图片分类,一类图片保存到一个大图片里,使用的时候在按要求裁剪出需要的部分,这张大图就叫精灵图,裁剪出来的部分叫精灵。
下面我们做一个简单的例子,来演示一下如何裁剪精灵图,需要的图片有两张,第一张是背景图,随便用什么都可以,第二张是精灵图。我们会让精灵在地图上走动。
精灵图里是一个小人各种样子,小人大小都是一样的,图片大小为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 <stdio.h> 7 #include <stdlib.h> 8 #include "SDL.h" 9 10 #include <windows.h> 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 = 0;i<4;i++) 74 { 75 for (j = 0;j<3;j++) 76 { 77 src[i][j].x = j*SPIRITWIDTH; 78 src[i][j].y = i*SPIRITHEIGH; 79 src[i][j].w = SPIRITWIDTH; 80 src[i][j].h = SPIRITHEIGH; 81 } 82 } 83 84 //抠出第1副精灵的图 85 dst.x=dst.y = 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 = 1; 97 break; 98 case SDL_KEYDOWN: 99 switch(myEvent.key.keysym.sym) 100 { 101 case SDLK_DOWN: 102 spiritY+=speed; 103 step++; 104 spiritY = (spiritY+SPIRITHEIGH)>SCREENHEIGH?(SCREENHEIGH-SPIRITHEIGH):spiritY; 105 break; 106 case SDLK_MINUS: 107 speed--; 108 speed=speed>0?speed:0; 109 case SDLK_EQUALS: 110 speed++; 111 } 112 113 step %=3; 114 SDL_BlitSurface(gpBackGround,NULL,gpScreen,NULL); 115 dst.x = spiritX; 116 dst.y = spiritY; 117 dst.w = SPIRITWIDTH; 118 dst.h = 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("小人的坐标:(%d,%d)\n",spiritX,spiritY); 127 break; 128 case SDLK_RIGHT: 129 printf("小人的坐标:(%d,%d)\n",spiritX,spiritY); 130 break; 131 case SDLK_UP: 132 printf("小人的坐标:(%d,%d)\n",spiritX,spiritY); 133 break; 134 case SDLK_DOWN: 135 printf("小人的坐标:(%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 = NULL; 158 SDL_Surface* optimizedImage = NULL; 159 160 //载入图像 161 loadedImage = IMG_Load( aFilename); 162 163 if( NULL != loadedImage )//If the image loaded 164 { 165 //创建优化图像 166 optimizedImage = 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 = NULL; 184 wchar_t *unicode_buf; 185 int nRetLen; 186 187 if(buf){ 188 free(buf); 189 buf = NULL; 190 } 191 nRetLen = MultiByteToWideChar(CP_ACP,0,src,-1,NULL,0); 192 unicode_buf = (wchar_t*)malloc((nRetLen+1)*sizeof(wchar_t)); 193 MultiByteToWideChar(CP_ACP,0,src,-1,unicode_buf,nRetLen); 194 nRetLen = WideCharToMultiByte(CP_UTF8,0,unicode_buf,-1,NULL,0,NULL,NULL); 195 buf = (char*)malloc(nRetLen+1); 196 WideCharToMultiByte(CP_UTF8,0,unicode_buf,-1,buf,nRetLen,NULL,NULL); 197 free(unicode_buf); 198 return buf; 199 }
第73-82行将每个小人的位置存储到src数组里,这样我们显示图像就不会错了,第101行我们检测了是否按下了↓键,如果按下了,我们把step的值加1,显示下一步的图片,但向下行走只有三幅图片,所以当step等于3时,应当将其置成0,这一步在第113行用了一个模运算完成,接下来就是定位将图片显示在屏幕上的位置,这个在键盘检测教程就已经说了,这不在累述,然后显示。运行结果可以看到很像一个人在行走。
现在,程序还有一点问题,就是不能转向,我们可以再设一个变量direct表示方向,其值为0表示向下,1向左,2向右,3向上,并且direct的值对应src行下标,然后再增加对其他三个方向的检测就可以了。另外我们在这个教程里设置了应用程序的名字并且名字是中文,所以需要加载windows.h头文件,这个会在后续章节讲述。
这个程序没有对小人进行碰撞检测,所以小人遇树撞树,以后会做碰撞检测的讲解,完整的代码请点击这儿下载。