6.1.4 示例程序GDIdemo6 这个程序中我们使用了定时器,来显示连续的静态图片,产生出动画效果。 素材方面,是在网络上收集的一张gif动图,然后在Photoshop中一帧一帧导出为bmp图片的,如下: 0.bmp 1.bmp 2.bmp 3.bmp 4.bmp 5.bmp 6.bmp 7.bmp 8.bmp 9.bmp 10.bmp 接下来我们看下代码。 程序代码片段一,全局变量声明: 1. //--------------------------【全局变量声明部分】------------------------------- 2. // 描述:全局变量的声明 3. //------------------------------------------------------------------------- 4. HDC g_hdc=NULL,g_mdc=NULL; //全局设备环境句柄与全局内存DC句柄 5. HBITMAP g_hSprite[12]; //声明位图数组用来储存各张人物位图 6. int g_iNum=0; //"g_iNum"变量用来记录目前显示的图号 程序代码片段二,窗口过程函数WndProc(): 1. //-----------------------【WndProc( )函数】---------------------------------- 2. // 描述:窗口过程函数WndProc,对窗口消息进行处理 3. //------------------------------------------------------------------------- 4. LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) 5. { 6. switch( message ) //switch语句开始 7. { 8. case WM_TIMER: //定时器消息 9. Game_Paint(hwnd); //调用Game_Paint()函数进行窗口绘图 10. break; //跳出该switch语句 11. 12. case WM_KEYDOWN: // 若是键盘按下消息 13. if (wParam == VK_ESCAPE) // 如果被按下的键是ESC 14. DestroyWindow(hwnd); // 销毁窗口, 并发送一条WM_DESTROY消息 15. break; //跳出该switch语句 16. 17. case WM_DESTROY: //若是窗口销毁消息 18. Game_CleanUp(hwnd); //调用自定义的资源清理函数Game_CleanUp()进行退出前的资源清理 19. PostQuitMessage( 0 ); //向系统表明有个线程有终止请求。用来响应WM_DESTROY消息 20. break; //跳出该switch语句 21. 22. default: //若上述case条件都不符合,则执行该default语句 23. return DefWindowProc( hwnd, message, wParam, lParam ); //调用默认的窗口过程 24. } 25. 26. return 0; //正常退出 27. } 需要注意的是,这里我们由于有了WM_TIMER来驱动绘制函数Game_Paint()的调用,所以就可以不用在窗口过程函数中对WM_PAINT消息进行响应了。 程序代码片段三,Game_Init()函数: 1. //---------------------【Game_Init( )函数】---------------------------------- 2. // 描述:初始化函数,进行一些简单的初始化 3. //------------------------------------------------------------------------- 4. BOOL Game_Init( HWND hwnd ) 5. { 6. g_hdc = GetDC(hwnd); //获取设备环境句柄 7. 8. wchar_t filename[20]; 9. //载入各个萝莉位图 10. for(int i=0;i<12;i++) 11. { 12. memset(filename, 0, sizeof(filename)); //filename的初始化 13. swprintf_s(filename,L"%d.bmp",i); //调用swprintf_s函数,“组装”出对应的图片文件名称 14. g_hSprite[i] = (HBITMAP)LoadImage(NULL,filename,IMAGE_BITMAP, WINDOW_WIDTH,WINDOW_HEIGHT,LR_LOADFROMFILE); 15. } 16. //-----【位图绘制四步曲之二:建立兼容DC】----- 17. g_mdc = CreateCompatibleDC(g_hdc); //建立兼容设备环境的内存DC 18. 19. g_iNum = 0; //设置初始的显示图号为"0" 20. SetTimer(hwnd,1,90,NULL); //建立定时器,间隔0.09秒发出消息 21. return TRUE; 22. } 这里出现了两个新的函数memset和swprintf_s,我们不妨来讲一下。首先是memset函数,我们先在MSDN中查一下它的原型,可以发现在MSDN中同时把它的师兄wmemset也列举出来,作为一类函数了: 1. void *memset( 2. void *dest, 3. int c, 4. size_t count 5. ); 6. wchar_t *wmemset( 7. wchar_t *dest, 8. wchar_t c, 9. size_t count 10. ); wmemset和memset就是参数类型和返回值不一样罢了,大体用法还是一致的,我们就看memset怎么用就可以了。 memset函数就是将第一个参数dest的前count(即第三个参数)个字节用第二个参数c替换,并返回第一个参数被替换后的值。 看一下我们在这里用到memset函数的这句代码: 1. memset(filename, 0, sizeof(filename)); //filename的初始化 它就是把filename数组的全部字节用0替换掉,也就是对filename进行初始化。 然后swprintf_s函数,这个我们后面经常会用到,需要重点了解一下。首先swprintf_s是swprintf函数的安全增强版本,其实他们用法什么的完全一致,只是加了_s后缀的那个为安全版本的函数,这样 VS2010不会报警告。因为VS2010编译器有新的标准,用swprintf的话经常会报警告出来,所以我们用的时候习惯加上_s后缀,成为swprintf的安全增强版本swprintf_s。 然后swprintf又是sprintf的宽字节版本。Sprintf大家看起来应该都很亲切,我们在C语言中都用烂了,它也就是把格式化的数据写到一个流中。不过有可能很多朋友都是直接学的C++,那样也许sprintf就接触的少了。好了,不废话,我们来看一下swprintf_s函数在MSDN中的定义: 1. int swprintf_s( 2. wchar_t *buffer, 3. size_t sizeOfBuffer, 4. const wchar_t *format [, 5. argument]... 6. ); ■ 第一个参数,wchar_t类型的*buffer,一个宽字符型指针,指向要写入的字符串的缓冲区,即输出的存储位置。 ■ 第二个参数,size_t类型的sizeOfBuffer,即存储的最大字符数,填字符串的大小,一般在这个参数里面我们会调用sizeof函数动态获取指定字符串的大小,这个值不填,省略掉也可以。 ■ 第三个参数,const类型的wchar_t *format,填需要处理的字符就好了。 而[argument]表示是个可选参数,可以是任何类型的数据,这就可以把任何类型的数据格式化到第三个参数format中。 也看一下我们这里用到这几句代码: 1. for(int i=0;i<12;i++) 2. { 3. memset(filename, 0, sizeof(filename)); //filename的初始化 4. swprintf_s(filename,L"%d.bmp",i); //调用swprintf_s函数,“组装”出对应的图片文件名称 5. g_hSprite[i] = (HBITMAP)LoadImage(NULL,filename,IMAGE_BITMAP, WINDOW_WIDTH,WINDOW_HEIGHT,LR_LOADFROMFILE); 6. } 就是在一个for循环中,把0~11的数字按10进制格式化到“%d.bmp”字符串中,得到1.bmp、2.bmp、3.bmp之类的文件名,然后存到filename中,接着就调用LoadImage函数把文件加载到位图句柄中。 其中的%d就是代表10进制转换的转换字符,更多转换字符也给大家列举出来: %% 印出百分比符号,不转换。 %c 整数转成对应的 ASCII 字元。 %d 整数转成十进位。 %f 倍精确度数字转成浮点数。 %o 整数转成八进位。 %s 整数转成字符串。 %x 整数转成小写十六进位。 %X 整数转成大写十六进位。 最后需要注意的一个点是使用swprintf系列字符格式化函数要包含tchar.h头文件。 另外需要注意的是,Game_Init函数中需要设置一个定时器,我们设置的是每隔0.09秒发出消息。 我们接下来看看如何绘制,即Game_ Paint()函数的实现代码。 程序代码片段四,Game_Paint()函数: 1. //---------------------【Game_Paint( )函数】--------------------------------- 2. // 描述:绘制函数,在此函数中进行绘制操作 3. //------------------------------------------------------------------------- 4. VOID Game_Paint( HWND hwnd ) 5. { 6. //处理图号 7. if(g_iNum == 11) //判断是否超过最大图号,若超过最大图号“10”,则将显示图号重设为"0"。 8. g_iNum = 0; 9. 10. //依据图号来贴图 11. SelectObject(g_mdc,g_hSprite[g_iNum]); //根据图号选入对应的位图 12. BitBlt(g_hdc,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,g_mdc,0,0,SRCCOPY); //以目前图号进行窗口贴图 13. 14. //图号自增 15. g_iNum++; //将“g_iNum”值加1,为下一次要显示的图号 16. } 第7-8行代码精析:对图号的处理,用一个if语句判断一下图号是否超过了10(即第一次等于11的时候),如果超过,就置0。 第11-12行代码精析:根据图号选择对应的位图然后从g_mdc贴到g_hdc中。 第15行代码精析:让图号自增,这样每次调用Game_Paint函数都会显示下一副图片,达到动画显示的效果。 由于Game_ CleanUp()函数这回需要调用一下KillTimer删除掉定时器,所以这回粉墨登场了。 程序代码片段四,Game_ CleanUp()函数: 1. //---------------------【Game_CleanUp( )函数】------------------------------- 2. // 描述:资源清理函数,在此函数中进行程序退出前资源的清理工作 3. //------------------------------------------------------------------------- 4. BOOL Game_CleanUp( HWND hwnd ) 5. { 6. KillTimer(hwnd,1); //删除所建立的定时器 7. //释放资源对象 8. for(int i=0;i<12;i++) 9. DeleteObject(g_hSprite[i]); 10. DeleteDC(g_mdc); 11. ReleaseDC(hwnd,g_hdc); //释放设备环境 12. return TRUE; 13. } KillTimer(hwnd,1)表示删除了hwnd窗口中编号为1的定时器。 下面我们看一下运行截图: 运行起来,这个小萝莉不停地左右甩动头发,挺可爱的,大家不妨自己去配套代码包里面找到源代码运行一下,看看我们是怎么用定时器来让这个萌系的小萝莉动起来的。
逐梦旅程:Windows游戏编程之从零开始——6.1.4 示例程序GDIdemo6
书名: 逐梦旅程:Windows游戏编程之从零开始
作者: 毛星云
出版社: 清华大学出版社
出版年: 2013-9-16
页数: 679
定价: 98.00元
装帧: 平装
ISBN: 9787302337508