6.2 游戏循环动画显示 在本节开头,首先给大家一个大体的认知。本节我们讲的“游戏循环”技术,是目前Windows游戏中普遍采用的动画显示技术。 上一节中我们讲解了用定时器来产生动画的效果。定时器的使用固然简单方便,但是事实上这样的方法仅适合用在显示简易的动画以及小型的游戏程序中。因为一般而言,游戏本身需要显示顺畅的游戏画面,使玩家感觉不到延迟的状态。基本游戏画面必须在一秒钟之内更新至少25次以上,这一秒钟内程序还必须进行消息的处理和大量数学运算甚至音效的输出等操作。而使用定时器的消息来驱动这些操作,往往是达不到所要求的标准,差强人意的。 这里我们要提出一种“游戏循环”的概念,”游戏循环“是将我们之前程序中的消息循环加以修改,方法是判断其中的内容目前是否有要处理的消息,如果有则进行处理,否则按照设定的时间间隔来重绘画面。采用“游戏循环”的游戏程序的执行效率足以秒杀采用定时器的游戏程序,所以目前市面上的大型游戏程序基本上都是利用游戏循环来驱动游戏画面的更新的。 下面就是一段对普通消息循环修改而成的“游戏循环”的核心代码,理解了这段代码,就理解了“游戏循环”的理念。 1. //【5】消息循环过程 2. MSG msg = { 0 }; //定义并初始化msg 3. while( msg.message != WM_QUIT ) //使用while循环,如果消息不是WM_QUIT消息,就继续循环 4. { 5. if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) ) //查看应用程序消息队列,有消息时将队列中的消息派发出去。 6. { 7. TranslateMessage( &msg ); //将虚拟键消息转换为字符消息 8. DispatchMessage( &msg ); //分发一个消息给窗口程序。 9. } 10. else 11. { 12. g_tNow = GetTickCount(); //获取当前系统时间 13. if(g_tNow-g_tPre >= 100) //当此次循环运行与上次绘图时间相差0.1秒时再进行重绘操作 14. Game_Paint(hwnd); 15. } 16. } 我们来讲解一下上面贴出的这段游戏循环核心代码中的几个重点。 第3行代码精析:当收到的msg.message不是窗口结束消息WM_QUIT,就继续运行循环,其中msg是一个MSG的消息结构,我们在第二行中对它进行了定义,而MSG结构体的成员message是一个消息类型的代号。 第5行代码精析:使用PeekMessage()函数来检测目前是否有需要处理的消息,若检测到消息(包含WM_QUIT消息)则会返回一个非0值,否则返回0。因此在游戏循环中,若检测到消息便进行消息的处理,否则运行else叙述之后的程序代码。这里我们要注意的是,PeekMessage()函数不能用原先消息循环的条件GetMessage()取代,因为GetMessage()函数只有在取得WM_QUIT消息时才会返回0,其他时候则是返回非0值或-1(发生错误时)。 第12行代码精析:我们调用的GetTickCount()函数会取得系统开始运行到目前所经过的时间,单位是毫秒(milliseconds),也就是千分之一秒。 1. DWORD WINAPI GetTickCount(void); //取得系统开始到目前经过的时间 这里取得时间的目的主要是可以搭配接下来的if语句,用来调整游戏运行的速度到一个适当的值,使得游戏不会因为运行计算机速度的不同而跑得太快或者太慢。 第13行代码精析:这是一个if条件句,在判断条件的括号里,tPre记录前次绘图的时间,而“tNow-tRre”就是计算上次绘图到这次循环运行之间相差多少时间。这里设置为若相差100个单位时间以上则再次进行绘图的操作,通过这个数值的控制可以调整游戏运行的速度。这里我们设定100个单位时间(微秒),那么每隔100个时间单位就调用一次第14行中写的Game_Paint(hwnd)函数,进行绘图的操作,那么1秒钟大约调用Game_Paint(hwnd)函数1000/100=10次,即重绘窗口10次。 由于循环的运行速度远比定时器发出时间信号来得快,因此使用游戏循环可以更精准地控制程序运行速度并提高每秒钟画面重绘的次数。目前市面上的游戏,基本上都是采用的游戏循环的显示方式。 了解了游戏循环使用的基本概念之后,接下来依旧是通过一个程序实例示例程序GDIdemo7,来将本节知识融会贯通,学以致用。 这个示例程序中,我们以游戏循环的方法驱动Game_Paint函数,进行窗口的连续贴图,更精确地制作游戏动画效果。 使用的素材如下,非常酷的动漫人物舞刀的连续动画: 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. DWORD g_tPre=0,g_tNow=0; //声明l两个变量来记录时间,g_tPre记录上一次绘图的时间,g_tNow记录此次准备绘图的时间 7. int g_iNum=0; //g_iNum变量用来记录目前显示的图号 程序代码片段二,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. //载入各个人物位图 11. for(int i=0;i<12;i++) 12. { 13. memset(filename, 0, sizeof(filename)); //filename的初始化 14. swprintf_s(filename,L"%d.bmp",i); //调用swprintf_s函数,“组装”出对应的图片文件名称 15. g_hSprite[i] = (HBITMAP)LoadImage(NULL,filename,IMAGE_BITMAP,800, 600,LR_LOADFROMFILE);//载入位图 16. } 17. 18. //-----【位图绘制四步曲之二:建立兼容DC】----- 19. g_mdc = CreateCompatibleDC(g_hdc); //建立兼容设备环境的内存DC 20. return TRUE; 21. } 以上的代码基本上和上一节我们讲定时器时的示例程序中的代码一致,在上一节中已经有详细讲解,这里就不多赘言。 程序代码片段三,Game_Paint()函数: 1. //------------------【Game_Paint( )函数】---------------------------------- 2. // 描述:绘制函数,在此函数中进行绘制操作 3. //------------------------------------------------------------------------ 4. VOID Game_Paint( HWND hwnd ) 5. { 6. if(g_iNum == 11) //判断是否超过最大图号,若超过最大图号“10”,则将显示图号重设为"0"。 7. g_iNum = 0; 8. 9. SelectObject(g_mdc,g_hSprite[g_iNum]); 10. BitBlt(g_hdc,0,0,800,600,g_mdc,0,0,SRCCOPY); //以目前图号进行窗口贴图 11. 12. g_tPre = GetTickCount(); //记录此次绘图时间,供下次游戏循环中判断是否已经达到画面更新操作设定的时间间隔。 13. g_iNum++; //将“g_iNum”值加1,为下一次要显示的图号 14. 15. } 以上的这段代码也基本和定时器一节中的配套代码一致,唯一的区别是12行中多了一句g_tPre = GetTickCount()来把当前调用BitBlt贴图后的系统时间赋给全局变量g_tPre,这样在消息循环中就可以通过g_tNow-g_tPre的大小来决定过多久进行下一次绘制。 我们可以发现,定时器与我们这节讲的游戏循环其实差不多,就是想办法来找到一个不断调用绘制函数Game_Paint()的方式而已。 来看一下酷酷的运行截图: 很酷吧~截图其实看不大出来实际的那种刀光剑舞的华丽效果,推荐大家也去亲手运行体会一下。 最后提醒大家一下,目前市面上的游戏程序,包括后面我们讲解的DirectX程序,都是利用这样的游戏循环的画面绘制方式。