裁剪精灵图片

       如果我们要做一个游戏,会涉及到大量的图片,如果这些图片都用单个文件保存,那程序里会加载大量的图片,会有很多表面,处理起来很麻烦,一般会把游戏里使用的图片分类,一类图片保存到一个大图片里,使用的时候在按要求裁剪出需要的部分,这张大图就叫精灵图,裁剪出来的部分叫精灵。

      下面我们做一个简单的例子,来演示一下如何裁剪精灵图,需要的图片有两张,第一张是背景图,随便用什么都可以,第二张是精灵图。我们会让精灵在地图上走动。

spiritbackground spirit

        精灵图里是一个小人各种样子,小人大小都是一样的,图片大小为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]指示的第一个精灵的图。精灵图的裁剪就是这么简单。运行效果:

image

      但精灵图的黄色背景也会显示,这会很难看,如何去除这个背景色呢?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);

  运行后显示效果:

image

  那么你是否还记得我们讲过的键盘事件检测?如果你会了键盘事件检测,我们可以做些有意思的尝试了,我们看一下精灵大图,会发现第一排小人是小人从上往下走的不同姿势,第二排从右往左走,第三排是从左往右走,第四排是从下往上走;那么我们是否可以通过键盘按键来让小人走路,转向呢?

  我们首先实现小人直行,要一直往前走,要不停的切换同一排小人图片,比如让小人从上往下走,第一幅图示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头文件,这个会在后续章节讲述。

  这个程序没有对小人进行碰撞检测,所以小人遇树撞树,以后会做碰撞检测的讲解,完整的代码请点击这儿下载。

posted @ 2012-05-11 15:38  成少雷  阅读(3018)  评论(1编辑  收藏  举报