很久没写博客了。这几天在做嵌入式的课程设计,忙活了将近两周。实验平台的开发板子用的是OK6410,操作系统是WindowsCE,微软旗下一个嵌入式方向的产品。本打算做一个三维的游戏,结果学了一段时间DirectX的东西。悲哀的是WinCE目前还不支持DirectX的API。囧,只好做一个二维的游戏。上周末一兄弟去北京工作,在寝室为他饯行,喝了几杯白兰地,结果周一难受得在床上躺了一天,呵呵,其实我酒量很好的。周二开始动工,周四写得差不多了,周五又调了一天。时间不多,所以游戏做得也比较简单。不扯了,下面开始总结一下。
其实无论是二维的,还是三维的游戏开发,思路都是一样的。实现游戏,尤其是格斗这一类游戏,很重要的一个东西,就是游戏动画技术,怎么让游戏中的人物动起来,这个需要想办法。其实主流的方式有两种,一种是类似“定时器”的东西,每隔多少毫秒定时刷新屏幕,不幸的是,这种方式不太准确,尤其是在Windows下利用响应WM_TIMER消息来实现的这种方式更加不准确,因为这种消息机制不是异步的。换句话说,如果指定定时器的时间间隔是1s,在消息队列的这种机制下,你根本就不能保证程序每隔1s就会收到一个WM_TIMER消息;另外的一种技术就是“游戏循环”,这个比较准确,其实就是将Windows程序中的消息循环机制稍微地加以修改,具体的实现方法就是,判断队列中目前是否有要处理的消息,如果有则进行处理,否则的话,就按照设定的时间间隔来重绘画面。
开发游戏,不可或缺的一个步骤就是游戏模型的设计。比如说,格斗中的人物,怎么在电脑屏幕上画出来,这是个问题。两种方式,自己用写代码的方式把游戏人物画出来。在大型的游戏中,这种方式不太常见,因为这样做太耗时了,GPU的处理速度也跟不上。另外一种方式,就是自己用专门的绘图软件,比如说二维中的Photoshop,三维中的3ds Max,把游戏人物设计好以后,存成特定格式的文件,直接加载进去就可以了。这种方式比较通用,对于一些大型的三维游戏,一般都有专门的设计人员负责游戏模型的创建。此次的游戏模型设计也是采用这种方式,只不过,方式比较低级,用的是bmp位图的方式存储。把人物动作的每一帧存下来,为了编程方便,自然需要规定好每一帧的大小。比如说,对于下面这个已经设计好的模型(Ryu很经典的发大招时的动作):
怎么让设计的模型显示在屏幕合适的位置上,还是有点讲究的。这里用到一个映射技术,即如何将游戏模型中的每一个像素点映射到显示器上合适的位置。所以,这里需要API来获取屏幕的位置和游戏模型像素点的位置。在Windows中,这种API已经很成熟了,可以直接调用,比如说GetObject、GetClientRect等。绘制人物直接调用一些GDI函数就可以了,比如说BitBlt、TransparentBlt、StretchBlt等。
其实,游戏的绘制,三维中叫做渲染,远远没有想象中的那么简单,尤其是闪屏的问题,比较令人恼火,上面的思路多么清晰啊,怎么绘制出来的人物一直在闪烁?!仔细分析一下,也很容易知道问题的原因所在,因为显示器是按照行来刷新的,一行一行地进行绘制,你在写代码的时候,直接写到显示器中(实际的开发中是一个DC,Device Context,一般中文翻译成设备上下文),即直接把绘制的代码写到一个DC中,就可以直接显示屏幕上。要知道,程序的执行还是按照过程的,一行一行地执行,必定存在一个先后绘制的问题,所以屏幕的闪烁也是正常的。那么怎么解决这个问题呢?可以利用一种缓冲显示技术,比如说双缓冲(double buffer)、三缓冲技术(triple buffering)。这种技术,有一个更能描述其功能的名字,页面切换技术(page flipping)。就相当于,实现把所有的模型渲染完毕,存在某一个地方,要现实的时候直接拷贝过来就行了,这就跟切换的效果是一样的,切换肯定不存在先后绘制的闪屏问题。这次的游戏设计,用了三缓冲技术。也就是说新建立一个与g_hdc(g_hdc负责最终在显示器上的显示)兼容的两个DC,一个g_bufdc,一个是g_hdcMem。首先把所有的资源统一加载到g_bufdc,再进行透明化处理等渲染操作,将渲染处理完的结果从g_bufdc贴图到g_hdcMem(注:g_hdcMem可以在初始化的时候贴上一张与窗口大小相同的空位图),最后再将所有渲染处理完的画面整体拷贝显示到屏幕g_hdc中。这样就有效地避免了屏幕的闪烁问题。
到目前为止,实现游戏人物的绘制已经没有问题。然后,游戏开发所带来的细节问题还有很多,比如说游戏的物理建模、重力系统、粒子系统、碰撞检测、游戏AI等,这些方面也需要考虑,因为它们也是游戏设计不可或缺的一部分。此次的游戏设计也有所涉及。不扯了,先贴几张图,再贴代码。
开机画面,卷轴效果:
Ryu发绝招打出的球不太好截图,囧。
代码写得比较烂,勿喷!
主要结构体设计:
//动作枚举 enum ActionType { ACTION_TYPE_BOXING=0,//拳击 ACTION_TYPE_KICK=1,//踢腿 ACTION_TYPE_WAVEBOXING=2,//绝招 ACTION_TYPE_NULL=3,//无攻击动作 }; //绝招时发出的子弹 struct WaveBullets { int x,y;//波的坐标 bool exist; }; //雪花粒子系统 struct Snow { int x,y;//雪花的位置坐标 BOOL exist; };
主要变量:
//窗口实例全局变量 HINSTANCE g_hInst=NULL; //三缓冲思想的三个DC HDC g_hdc=NULL,g_hdcMem=NULL,g_bufdc=NULL; /************************************************************************/ /* Hero的相关变量 */ /************************************************************************/ /* g_hHeroDirection表示Hero的移动方向,分为wait,go back,go forward三种; g_hHeroHit存储Hero的出招图,分为拳打、脚踢和绝招三种; g_hHeroWave存储Hero发出绝招时推出的波形子弹; g_hHeroBeAttactedHeavy存储Hero被重击的效果图; g_hHeroBeAttactedLight存储Hero被轻击的效果图; g_hHeroBlood存储Hero的血量图; g_hHeroDie存储Hero阵亡以后的效果图,g_hHeroWin存储Hero胜利以后的效果图 */ HBITMAP g_hHeroDirection[3]={NULL},g_hHeroHit[3]={NULL},g_hHeroWave=NULL,g_hHeroBeAttactedHeavy=NULL,g_hHeroBeAttactedLight=NULL,g_hHeroBlood=NULL,g_hHeroDie=NULL,g_hHeroWin=NULL; /* g_iHeroFileNum记录Hero画面帧号; g_iHeroX,g_iHeroY分别记录Hero的实时坐标位置; g_iHeroDirection记录方向,0,1,2分别表示前进、后退、wait三个方向 g_iHeroStrength记录Hero的血量(体力值) */ int g_iHeroFileNum=0,g_iHeroX=0,g_iHeroY=0,g_iHeroDirection=0; float g_iHeroStrength=0.0; //Hero的出招类型 ActionType g_HeroActionType; //heroWaves存储Hero打出的波形子弹的信息 WaveBullets heroWaves[10]; //g_iHeroWavesNum记录Hero的波形子弹的数目 int g_iHeroWavesNum=0; /************************************************************************/ /* Robot的相关变量 */ /************************************************************************/ /* g_hRobotDirection存储Robot的移动方向,也分为wait,go back,go forward三种; g_hRobotHit存储Robot的出招图,也分为拳打、脚踢和绝招三种; g_hRobotWave存储Robot发出绝招时推出的波形子弹; g_hRobotBeAttactedHeavy存储Robot被重击的效果图; g_hRobotBeAttactedLight存储Robot被轻击的效果图; g_hRobotBlood存储Robot的血量图; g_hRobotDie存储Robot阵亡以后的效果图 */ HBITMAP g_hRobotDirection[3]={NULL},g_hRobotWave=NULL,g_hRobotHit[3]={NULL},g_hRobotBeAttactedHeavy=NULL,g_hRobotBeAttactedLight=NULL,g_hRobotBlood=NULL,g_hRobotDie=NULL; /* g_iRobotFileNum存储Robot图片帧号,g_iRobotX、g_iRobotY记录Robot的位置; g_iRobotDirection记录Robot的移动方向,0/1/2分别表示前进、后退、wait; g_iRobotStrength记录Robot的体力值 */ int g_iRobotFileNum=0,g_iRobotX=0,g_iRobotY=0,g_iRobotDirection=0; float g_iRobotStrength=0.0; //Robot的出招类型 ActionType g_RobotActionType; //robotWaves存储Robot打出的波形子弹的信息 WaveBullets robotWaves[10]; //g_iRobotWaveNum记录Robot的子弹的数目 int g_iRobotWaveNum=0; /************************************************************************/ /* 游戏公共资源部分 */ /************************************************************************/ /* g_hLogoKO存储血量图中间的KO标志; g_hBackGround存储游戏背景图; g_hLoading存储游戏初始加载的效果图; g_hStart存储游戏的开始画面; g_hFire存储火焰燃烧画面; g_hTryAgain存储游戏结束画面; g_hSnow存储雪花粒子图 */ HBITMAP g_hLogoKO=NULL,g_hBackGround=NULL,g_hLoading=NULL,g_hStart=NULL,g_hFire=NULL,g_hTryAgain=NULL,g_hSnow=NULL;; //g_iFireFrameNum记录火焰图的帧号 int g_iFireFrameNum=0; //存储Hero出招图记录的宽度和高度信息 BITMAP g_bmpHeroHit; //存储Robot出招图记录的宽度和高度信息 BITMAP g_bmpRobotHit; //存储Hero血量位图记录的宽度和高度信息 BITMAP g_bmpHeroBlood; //存储Robot血量图记录的宽度和高度信息 BITMAP g_bmpRobotBlood; //血量图中间的KO logo位图信息 BITMAP g_bmpKO; /* 声明两个变量来记录时间,g_tPre记录上一次绘图的时间; g_tNow记录此次准备绘图的时间 */ DWORD g_tHeroPre=0,g_tHeroNow=0; //SnowFlowers存储雪花粒子信息 Snow SnowFlowers[100]; //g_SnowNum记录雪花的数目 int g_SnowNum=0; /************************************************************************/ /* Flag标志部分 */ /************************************************************************/ /* g_heroActionFlag、g_robotActionFlag 标识人物的是否出招,用于区别走动和出招两个不同的动作; g_heroBeAttactedLightFlag、g_heroBeAttactedHeavyFlag、 g_robotBeAttactedLightFlag、g_robotBeAttactedHeavyFlag 用于标识人物是否收到重击或者轻击; g_gameOverFlag标识游戏是否结束; first_start用于标识游戏是否第一次启动 */ bool g_heroActionFlag; bool g_robotActionFlag; bool g_heroBeAttactedLightFlag; bool g_heroBeAttactedHeavyFlag; bool g_robotBeAttactedLightFlag; bool g_robotBeAttactedHeavyFlag; bool g_gameOverFlag; bool first_start;
几个核心函数:
WinMain主程序的入口点:
int WINAPI WinMain( __in HINSTANCE hInstance, __in_opt HINSTANCE hPrevInstance, __in_opt LPWSTR lpCmdLine, __in int nShowCmd ) { //1.创建一个窗体类 WNDCLASS ws; g_hInst=hInstance; ws.cbClsExtra = 0; ws.cbWndExtra = 0; ws.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH); ws.hCursor = NULL; ws.hIcon = NULL; ws.hInstance = hInstance; ws.lpfnWndProc = WndProc; ws.lpszClassName = TEXT("Hello"); ws.lpszMenuName = NULL; ws.style = CS_VREDRAW | CS_HREDRAW; //2.注册窗体类 if (! RegisterClass(&ws)) return -1; //3.创建窗体 UINT sysWidth=GetSystemMetrics(SM_CXSCREEN); UINT sysHeight=GetSystemMetrics(SM_CYSCREEN); HWND hwnd = CreateWindow(TEXT("Hello"),TEXT("Street Fighter V1.0"),WS_VISIBLE | WS_BORDER | WS_SYSMENU /*| WS_MINIMIZEBOX*/ /*| WS_MAXIMIZEBOX*/ | WS_CAPTION, 0,0,sysWidth,sysHeight, NULL,NULL,hInstance,NULL); //4.更新窗体内容 UpdateWindow(hwnd); ShowWindow(hwnd,nShowCmd); if (!Game_Init (hwnd)) { MessageBox(hwnd, L"资源初始化失败", L"消息窗口", 0); return FALSE; } MSG msg={0}; //5.获取系统消息 while(msg.message!=WM_QUIT) { if (PeekMessage(&msg,0,0,0,PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } else { g_tHeroNow=GetTickCount();//获取当前系统时间 if ((g_tHeroNow-g_tHeroPre>=50)/*&&!first_start*/) { Game_Paint(hwnd); } } } return 1; }
窗口过程函数:
LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam) { RECT rect; GetClientRect(hwnd,&rect); switch (message) { // case WM_TIMER: // Game_Paint(hwnd); // break; case WM_KEYDOWN: switch (wParam) { case VK_P: //开始游戏 //first_start=false; break; case VK_Q: //退出游戏 DestroyWindow(hwnd); PostQuitMessage(0); break; case VK_ESCAPE: DestroyWindow(hwnd); PostQuitMessage(0); break; //方向控制(测试发现WINCE下不支持字母) case VK_LEFT://后退 if(!g_gameOverFlag)//游戏未结束 { g_heroActionFlag=false; g_HeroActionType=ACTION_TYPE_NULL; g_robotBeAttactedLightFlag=false; g_robotBeAttactedHeavyFlag=false; g_iHeroX-=10; g_iHeroDirection=0; if (g_iHeroX<=0) g_iHeroX=0; g_iRobotDirection=1;//Hero后退则Robot前进 if (g_iRobotX>(g_iHeroX+g_bmpHeroHit.bmWidth/6))//未靠近Hero { g_iRobotX-=(10+rand()%5);//以10-15的像素向Hero靠近 } else { g_iRobotX=(g_iHeroX+g_bmpHeroHit.bmWidth/6); } } else//游戏结束 { g_iHeroDirection=2; g_heroActionFlag=false; g_HeroActionType=ACTION_TYPE_NULL; } break; case VK_RIGHT://前进 if (!g_gameOverFlag)//游戏未结束 { g_heroActionFlag=false; g_robotBeAttactedLightFlag=false; g_robotBeAttactedHeavyFlag=false; g_iHeroX+=10; g_iHeroDirection=1; if(g_iHeroX>=(g_iRobotX-g_bmpRobotHit.bmWidth/6))//Hero靠近Robot则不能再靠近 g_iHeroX=g_iRobotX-g_bmpRobotHit.bmWidth/6; //g_RobotActionType=ACTION_TYPE_KICK; g_iRobotDirection=0;//Hero前进则Robot回退 g_iRobotX+=(10+rand()%5); if(g_iRobotX>=(rect.right-116)) g_iRobotX=rect.right-116; } else//游戏结束 { g_iHeroDirection=2; g_heroActionFlag=false; g_HeroActionType=ACTION_TYPE_NULL; } break; //招式控制 case VK_J: g_heroActionFlag=true; g_HeroActionType=ACTION_TYPE_BOXING; //g_RobotActionType=ACTION_TYPE_KICK; break; case VK_K: g_heroActionFlag=true; g_HeroActionType=ACTION_TYPE_KICK; //g_RobotActionType=ACTION_TYPE_BOXING; break; case VK_L: g_heroActionFlag=true; g_HeroActionType=ACTION_TYPE_WAVEBOXING; //g_robotBeAttactedHeavyFlag=true; for (int i=0;i<10;i++) { if (!heroWaves[i].exist) { heroWaves[i].x=g_iHeroX+50; heroWaves[i].y=g_iHeroY+20;//大概处于出手位置,更显真实 heroWaves[i].exist=true; g_iHeroWavesNum++;//波的数目累加 break; } } break; } break; case WM_DESTROY: Game_CleanUp(hwnd); PostQuitMessage( 0 ); break; default: //g_heroActionFlag=false; //g_robotActionFlag=true; g_iRobotDirection=2;//wait防守状态 g_iHeroDirection=2;//wait的动作,处于防守状态 return DefWindowProc(hwnd,message,wParam,lParam); } return 0; }
游戏资源初始化函数:
BOOL Game_Init( HWND hwnd ) { HBITMAP wndBmp; g_hdc=GetDC(hwnd); //先创建内存DC,再创建一个缓冲DC g_hdcMem=CreateCompatibleDC(g_hdc); g_bufdc=CreateCompatibleDC(g_hdc); RECT wndRect;//窗体矩形 GetClientRect(hwnd,&wndRect); //建一个和窗口大小相同的空的位图对象 wndBmp=CreateCompatibleBitmap(g_hdc,wndRect.right,wndRect.bottom); //将空的位图放到g_hdcMem中 SelectObject(g_hdcMem,wndBmp); //加载各种位图资源 g_hBackGround=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_BKGRD)); //g_hBlood=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_BLOOD)); g_hHeroDirection[0]=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_R1_GOBACK)); g_hHeroDirection[1]=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_R1_GOFORWARD)); g_hHeroDirection[2]=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_R1_WAIT)); g_hHeroHit[0]=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_R1_BOXING)); g_hHeroHit[1]=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_R1_KICK)); g_hHeroHit[2]=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_R1_WAVEBOXING)); g_hHeroWave=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_R1_WAVE)); g_hHeroBeAttactedLight=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_R1_BEATTACTED_LIGHT)); g_hHeroBeAttactedHeavy=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_R1_BEATTACTED_HEAVY)); g_hHeroBlood=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_R1_BLOOD)); g_hHeroDie=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_R1_DIE)); g_hHeroWin=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_R1_WIN)); g_hRobotDirection[0]=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_R2_GOBACK)); g_hRobotDirection[1]=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_R2_GOFORWARD)); g_hRobotDirection[2]=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_R2_WAIT)); g_hRobotHit[0]=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_R2_KICK)); g_hRobotHit[1]=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_R2_BOXING)); g_hRobotHit[2]=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_R2_WAVEBOXING)); g_hRobotWave=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_R2_WAVE)); g_hRobotBeAttactedHeavy=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_R2_BEATTACTED_HEAVY)); g_hRobotBeAttactedLight=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_R2_BEATTACTED_LIGHT)); g_hRobotBlood=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_R2_BLOOD)); g_hRobotDie=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_R2_DIE)); g_hLogoKO=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_KO)); g_hLoading=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_LOADING)); g_hStart=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_START)); g_hSnow=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_SNOW)); g_hFire=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_FIRE)); g_hTryAgain=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_TRY_AGAIN)); GetObject(g_hHeroHit[0],sizeof(g_bmpHeroHit),&g_bmpHeroHit); GetObject(g_hRobotHit[0],sizeof(g_bmpRobotHit),&g_bmpRobotHit); GetObject(g_hHeroBlood,sizeof(g_bmpHeroBlood),&g_bmpHeroBlood); GetObject(g_hRobotBlood,sizeof(g_bmpRobotBlood),&g_bmpRobotBlood); GetObject(g_hLogoKO,sizeof(g_bmpKO),&g_bmpKO); //Hero贴图的起始坐标 g_iHeroX=wndRect.right/4; g_iHeroY=wndRect.right/4; g_iHeroDirection=2;//wait状态 g_HeroActionType=ACTION_TYPE_NULL; g_heroActionFlag=false; g_heroBeAttactedHeavyFlag=false; g_heroBeAttactedLightFlag=false; g_iHeroStrength=100.0;//Hero体力值初始化为100 g_iHeroFileNum=0; g_iRobotFileNum=0; //Robot贴图的起始坐标 g_iRobotX=wndRect.right/4*3; g_iRobotY=wndRect.right/4; g_iRobotDirection=2; g_RobotActionType=ACTION_TYPE_NULL; g_robotActionFlag=false; g_robotBeAttactedHeavyFlag=false; g_robotBeAttactedLightFlag=false; g_iRobotStrength=100.0;//Robot体力值初始化为100 g_gameOverFlag=false; BITMAP bmpLoading; GetObject(g_hStart,sizeof(bmpLoading),&bmpLoading); //加载开始画面 first_start=true; HDC start_hdc=NULL; start_hdc=CreateCompatibleDC(g_hdc); if (first_start) { SelectObject(start_hdc,g_hStart); for (int i=0;i<bmpLoading.bmWidth;i++) { BitBlt(g_hdc, (wndRect.right-bmpLoading.bmWidth)/2,(wndRect.bottom-bmpLoading.bmHeight)/2+i, bmpLoading.bmWidth,1, start_hdc,0,i,SRCCOPY); Sleep(10); } } g_iFireFrameNum=0; Game_Paint(hwnd); return TRUE; }核心渲染函数:
VOID Game_Paint(HWND hwnd) { RECT wndRect; BITMAP bmpBkGrd; //获取背景图片的大小 GetObject(g_hBackGround,sizeof(bmpBkGrd),&bmpBkGrd); //获取窗口尺寸 GetClientRect(hwnd,&wndRect); BITMAP bmpHero;//hero位图信息63*93 GetObject(g_hHeroDirection[0],sizeof(bmpHero),&bmpHero); BITMAP bmpRobot;//robot位图信息,63*93 GetObject(g_hRobotDirection[0],sizeof(bmpRobot),&bmpRobot); //开始界面 BITMAP bmpStart; GetObject(g_hStart,sizeof(bmpStart),&bmpStart); BITMAP bmpSnow; //获取雪花位图的大小 GetObject(g_hSnow,sizeof(bmpSnow),&bmpSnow); BITMAP bmpFire; GetObject(g_hFire,sizeof(bmpFire),&bmpFire); BITMAP bmpHeroWin; GetObject(g_hHeroWin,sizeof(bmpHeroWin),&bmpHeroWin); BITMAP bmpTryAgain; GetObject(g_hTryAgain,sizeof(bmpTryAgain),&bmpTryAgain); //绘制开始界面 //MessageBox(hwnd, L"是否开始游戏?", L"消息窗口", 0); //先贴上背景图,将其存入内存DC g_hdcMem中 SelectObject(g_bufdc,g_hBackGround); //拉伸的方式全屏贴背景图 StretchBlt(g_hdcMem, 0,0, wndRect.right,wndRect.bottom, g_bufdc,0,0, bmpBkGrd.bmWidth,bmpBkGrd.bmHeight, SRCCOPY); //火焰燃烧画面 SelectObject(g_bufdc,g_hFire); TransparentBlt(g_hdcMem, 0,wndRect.right/3, bmpFire.bmWidth/6,bmpFire.bmHeight, g_bufdc, g_iHeroFileNum*bmpFire.bmWidth/6,0, bmpFire.bmWidth/6,bmpFire.bmHeight, RGB(0,0,0)); TransparentBlt(g_hdcMem, wndRect.right/20*19,wndRect.right/3, bmpFire.bmWidth/6,bmpFire.bmHeight, g_bufdc, (g_iHeroFileNum)*bmpFire.bmWidth/6,0, bmpFire.bmWidth/6,bmpFire.bmHeight, RGB(0,0,0)); //创建雪花粒子系统 if (g_SnowNum<100) { SnowFlowers[g_SnowNum].x=rand()%wndRect.right; SnowFlowers[g_SnowNum].y=0; SnowFlowers[g_SnowNum].exist=true; g_SnowNum++; } //判断粒子是否存在,存在则进行透明贴图操作 for (int i=0;i<100;i++) { if (SnowFlowers[i].exist) { //贴上粒子图 SelectObject(g_bufdc,g_hSnow); TransparentBlt(g_hdcMem, SnowFlowers[i].x,SnowFlowers[i].y, bmpSnow.bmWidth,bmpSnow.bmHeight, g_bufdc, 0,0, bmpSnow.bmWidth,bmpSnow.bmHeight, RGB(0,0,0)); //随机决定横向的移动方向和偏移量 if (rand()%2==0) SnowFlowers[i].x+=rand()%6; else SnowFlowers[i].x-=rand()%6; //纵方向上做匀速运动 SnowFlowers[i].y+=10; if (SnowFlowers[i].y>wndRect.bottom) { SnowFlowers[i].x=rand()%wndRect.right; SnowFlowers[i].y=0; } } } BITMAP bmpHeroWave; GetObject(g_hHeroWave,sizeof(bmpHeroWave),&bmpHeroWave); //Hero攻击,招式控制,招式不为空,且处于攻击状态 if((g_HeroActionType!=ACTION_TYPE_NULL)&&g_heroActionFlag&&!g_gameOverFlag) { //获取攻击状态的位图信息 BITMAP bmpHeroAction; GetObject(g_hHeroHit[0],sizeof(bmpHeroAction),&bmpHeroAction); if(g_HeroActionType==ACTION_TYPE_WAVEBOXING) { //人物招式贴图 SelectObject(g_bufdc,g_hHeroHit[g_HeroActionType]); TransparentBlt(g_hdcMem, g_iHeroX,g_iHeroY, bmpHeroAction.bmWidth/6,bmpHeroAction.bmHeight, g_bufdc, g_iHeroFileNum*bmpHeroAction.bmWidth/6,0, bmpHeroAction.bmWidth/6,bmpHeroAction.bmHeight, RGB(255,255,255)); //绝招效果贴图 SelectObject(g_bufdc,g_hHeroWave); if (g_iHeroWavesNum!=0) { for (int i=0;i<10;i++) if (heroWaves[i].exist) { TransparentBlt(g_hdcMem, heroWaves[i].x+45,heroWaves[i].y, bmpHeroWave.bmWidth,bmpHeroWave.bmHeight, g_bufdc, 0,0, bmpHeroWave.bmWidth,bmpHeroWave.bmHeight, RGB(255,255,255)); heroWaves[i].x+=20; //Hero打出的波距离Robot小于20个像素就认为是打中 if((g_iRobotX-heroWaves[i].x)<20) { if(g_iRobotStrength>0) { g_robotBeAttactedHeavyFlag=true; //造成5-15之间的伤害 float damage=5+(float)(rand()%10); g_iRobotStrength-=damage; } else { g_iRobotStrength=0; g_gameOverFlag=true; } g_RobotActionType=ACTION_TYPE_NULL; g_iHeroWavesNum--; heroWaves[i].exist=false; } //没打中到达窗口边缘自动消失 if (heroWaves[i].x>wndRect.right-bmpHeroWave.bmWidth) { g_iHeroWavesNum--; heroWaves[i].exist=false; } } } } else//普通招式贴图 { if(!g_gameOverFlag) { SelectObject(g_bufdc,g_hHeroHit[g_HeroActionType]); TransparentBlt(g_hdcMem, g_iHeroX,g_iHeroY, bmpHeroAction.bmWidth/6,bmpHeroAction.bmHeight, g_bufdc, g_iHeroFileNum*bmpHeroAction.bmWidth/6,0, bmpHeroAction.bmWidth/6,bmpHeroAction.bmHeight, RGB(255,255,255)); //位图差距为116 if((g_iRobotX-g_iHeroX)<=120) { g_robotBeAttactedLightFlag=true; if (g_iRobotStrength>0) { //造成0-5之间的伤害 float damage=(float)(rand()%5); g_iRobotStrength-=damage; Sleep(10); } else { g_iRobotStrength=0; g_gameOverFlag=true; } } } } //g_heroActionFlag=false; } //Hero受到轻击打 if (!g_gameOverFlag&&g_heroBeAttactedLightFlag&&g_RobotActionType!=ACTION_TYPE_NULL) { g_heroActionFlag=false; SelectObject(g_bufdc,g_hHeroBeAttactedLight); TransparentBlt( g_hdcMem, g_iHeroX,g_iHeroY, bmpHero.bmWidth/6,bmpHero.bmHeight, g_bufdc, g_iHeroFileNum*bmpHero.bmWidth/6,0, bmpHero.bmWidth/6,bmpHero.bmHeight, RGB(255,255,255)); g_heroBeAttactedLightFlag=false; } //Hero贴图,针对其各个方向进行贴图 if (!g_gameOverFlag&&!g_heroActionFlag&&!g_heroBeAttactedLightFlag&&!g_heroBeAttactedHeavyFlag) { SelectObject(g_bufdc,g_hHeroDirection[g_iHeroDirection]); TransparentBlt(g_hdcMem, g_iHeroX,g_iHeroY, bmpHero.bmWidth/6,bmpHero.bmHeight, g_bufdc, g_iHeroFileNum*bmpHero.bmWidth/6,0, bmpHero.bmWidth/6,bmpHero.bmHeight, RGB(255,255,255)); } //Robot受到轻击打 if (!g_gameOverFlag&&g_robotBeAttactedLightFlag/*&&g_HeroActionType!=ACTION_TYPE_NULL*/) { g_robotActionFlag=false; SelectObject(g_bufdc,g_hRobotBeAttactedLight); TransparentBlt( g_hdcMem, g_iRobotX,g_iRobotY, bmpRobot.bmWidth/6,bmpRobot.bmHeight, g_bufdc, g_iRobotFileNum*bmpRobot.bmWidth/6,0, bmpRobot.bmWidth/6,bmpRobot.bmHeight, RGB(255,255,255)); //g_robotBeAttactedLightFlag=false; } //Robot受到波的重击 if(g_robotBeAttactedHeavyFlag) { g_robotActionFlag=false; SelectObject(g_bufdc,g_hRobotBeAttactedHeavy); TransparentBlt(g_hdcMem, g_iRobotX,g_iRobotY, g_bmpRobotHit.bmWidth/6,g_bmpRobotHit.bmHeight, g_bufdc, g_iRobotFileNum*g_bmpRobotHit.bmWidth/6,0, g_bmpRobotHit.bmWidth/6,g_bmpRobotHit.bmHeight, RGB(255,255,255)); g_robotBeAttactedHeavyFlag=false; } //Sleep(500); //g_robotActionFlag=true; if ((!g_gameOverFlag)&&(!g_heroActionFlag)&&(g_HeroActionType==ACTION_TYPE_NULL)) { g_robotActionFlag=true; //int probability=rand()%2; if(1==rand()%2) { g_RobotActionType=ACTION_TYPE_KICK; } else { g_RobotActionType=ACTION_TYPE_BOXING; } } //Robot攻击 if(!g_gameOverFlag&&g_robotActionFlag/*&&(g_heroActionFlag==false)*/) { SelectObject(g_bufdc,g_hRobotHit[g_RobotActionType]); TransparentBlt(g_hdcMem, g_iRobotX,g_iRobotY, g_bmpRobotHit.bmWidth/6,g_bmpRobotHit.bmHeight, g_bufdc, g_iRobotFileNum*g_bmpRobotHit.bmWidth/6,0, g_bmpRobotHit.bmWidth/6,g_bmpRobotHit.bmHeight, RGB(255,255,255)); if((g_iRobotX-g_iHeroX)<=110) { g_heroBeAttactedLightFlag=true; if (g_iHeroStrength>0) { //造成5-10之间的伤害 float damage=(float)(5+rand()%5); g_iHeroStrength-=damage; //Sleep(10); } else { g_iHeroStrength=0; g_gameOverFlag=true; } } } //Robot贴图 if (!g_gameOverFlag&&!g_robotActionFlag&&!g_robotBeAttactedLightFlag&&!g_robotBeAttactedHeavyFlag) { SelectObject(g_bufdc,g_hRobotDirection[g_iRobotDirection]); TransparentBlt(g_hdcMem, g_iRobotX,g_iRobotY, bmpRobot.bmWidth/6,bmpRobot.bmHeight, g_bufdc, g_iRobotFileNum*bmpRobot.bmWidth/6,0, bmpRobot.bmWidth/6,bmpRobot.bmHeight, RGB(255,255,255)); // g_robotBeAttactedLightFlag=false; // g_robotBeAttactedHeavyFlag=false; } //血量位图中间的KO logo贴图 SelectObject(g_bufdc,g_hLogoKO); StretchBlt(g_hdcMem, wndRect.right/2-g_bmpKO.bmWidth/2,0, g_bmpKO.bmWidth,g_bmpKO.bmHeight, g_bufdc, 0,0, g_bmpKO.bmWidth,g_bmpKO.bmHeight, SRCCOPY); //贴上Hero血量图,根据体力值实时计算贴图位置 SelectObject(g_bufdc,g_hHeroBlood); StretchBlt(g_hdcMem, (int)(wndRect.right/2*(float)(1.0-(float)(g_iHeroStrength/CHARACTER_MAX_STRENGTH))),0, (int)((float)(g_iHeroStrength/CHARACTER_MAX_STRENGTH)*wndRect.right/2)-g_bmpKO.bmWidth/2,g_bmpHeroBlood.bmHeight, g_bufdc, 0,0, (int)(g_bmpHeroBlood.bmWidth*(float)(g_iHeroStrength/CHARACTER_MAX_STRENGTH)),g_bmpHeroBlood.bmHeight, SRCCOPY); //贴上Robot血量图,根据体力值实时计算贴图起始位置 SelectObject(g_bufdc,g_hRobotBlood); StretchBlt(g_hdcMem, wndRect.right/2+g_bmpKO.bmWidth/2,0, (int)(wndRect.right/2*(float)(g_iRobotStrength/CHARACTER_MAX_STRENGTH)),g_bmpRobotBlood.bmHeight, g_bufdc, 0,0, g_bmpRobotBlood.bmWidth,g_bmpRobotBlood.bmHeight, SRCCOPY); if(g_gameOverFlag)//游戏结束 { if (g_iHeroStrength<=0)//Hero阵亡 { //Hero倒地 SelectObject(g_bufdc,g_hHeroDie); TransparentBlt(g_hdcMem, g_iHeroX,g_iHeroY+10, g_bmpHeroHit.bmWidth/6,g_bmpHeroHit.bmHeight, g_bufdc, g_iHeroFileNum*g_bmpHeroHit.bmWidth/6,0, g_bmpHeroHit.bmWidth/6,g_bmpHeroHit.bmHeight, RGB(255,255,255)); //Robot站着 SelectObject(g_bufdc,g_hRobotDirection[g_iRobotDirection]); TransparentBlt(g_hdcMem, g_iRobotX,g_iRobotY, bmpRobot.bmWidth/6,bmpRobot.bmHeight, g_bufdc, g_iRobotFileNum*bmpRobot.bmWidth/6,0, bmpRobot.bmWidth/6,bmpRobot.bmHeight, RGB(255,255,255)); } else if(g_iRobotStrength<=0)//Robot阵亡 { //robot倒地 SelectObject(g_bufdc,g_hRobotDie); TransparentBlt(g_hdcMem, g_iRobotX,g_iRobotY+10, g_bmpRobotHit.bmWidth/6,g_bmpRobotHit.bmHeight, g_bufdc, g_iRobotFileNum*g_bmpRobotHit.bmWidth/6,0, g_bmpRobotHit.bmWidth/6,g_bmpRobotHit.bmHeight, RGB(255,255,255)); //加载Hero Win的图片 SelectObject(g_bufdc,g_hHeroWin); TransparentBlt(g_hdcMem, g_iHeroX,g_iHeroY, bmpHeroWin.bmWidth/6,bmpHeroWin.bmHeight, g_bufdc, g_iHeroFileNum*bmpHeroWin.bmWidth/6,0, bmpHeroWin.bmWidth/6,bmpHeroWin.bmHeight, RGB(255,255,255)); } //游戏结束画面加载 SelectObject(g_bufdc,g_hTryAgain); BitBlt(g_hdcMem, wndRect.right/4,0, bmpTryAgain.bmWidth/2,bmpTryAgain.bmHeight, g_bufdc, bmpTryAgain.bmWidth/2,0,SRCAND); BitBlt(g_hdcMem, wndRect.right/4,0, bmpTryAgain.bmWidth/2,bmpTryAgain.bmHeight, g_bufdc, 0,0,SRCPAINT); //Hero的攻击状态标识全部置为false g_heroActionFlag=false; g_HeroActionType=ACTION_TYPE_NULL; } //最终的显示 BitBlt(g_hdc, 0,0, wndRect.right,wndRect.bottom, g_hdcMem, 0,0,SRCCOPY); //记录此次绘图时间,供下次游戏循环中判断是否已经达到画面更新操作设定的时间间隔 g_tHeroPre = GetTickCount(); g_iFireFrameNum++; g_iHeroFileNum++; g_iRobotFileNum++; if (g_iFireFrameNum==6) { g_iFireFrameNum=0; } if (g_iRobotFileNum==6) g_iRobotFileNum=0; if(g_iHeroFileNum==6) g_iHeroFileNum=0; }
游戏资源句柄释放函数:
BOOL Game_CleanUp(HWND hwnd ) { //释放各种资源句柄 for(int i=0;i<3;i++) { DeleteObject(g_hHeroDirection[i]); DeleteObject(g_hHeroHit[i]); DeleteObject(g_hRobotHit[i]); DeleteObject(g_hRobotDirection[i]); } DeleteObject(g_hTryAgain); DeleteObject(g_hHeroWin); DeleteObject(g_hHeroDie); DeleteObject(g_hRobotDie); DeleteObject(g_hFire); DeleteObject(g_hSnow); DeleteObject(g_hStart); DeleteObject(g_hLoading); DeleteObject(g_hLogoKO); DeleteObject(g_hHeroBlood); DeleteObject(g_hRobotBlood); DeleteObject(g_hHeroBeAttactedHeavy); DeleteObject(g_hHeroBeAttactedLight); DeleteObject(g_hRobotBeAttactedLight); DeleteObject(g_hRobotBeAttactedHeavy); DeleteObject(g_hHeroWave); DeleteObject(g_hRobotWave); DeleteObject(g_bufdc); DeleteDC(g_hdcMem); ReleaseDC(hwnd,g_hdc); return TRUE; }上面的代码写得比较烂,没有面向对象去写,全部是过程式的,渲染函数写得太长,编程习惯不太好。但是,沿着这个思路走下去,把一个完整的“街霸”开发出来还是没有问题的。