很久没写博客了。这几天在做嵌入式的课程设计,忙活了将近两周。实验平台的开发板子用的是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;
}
上面的代码写得比较烂,没有面向对象去写,全部是过程式的,渲染函数写得太长,编程习惯不太好。但是,沿着这个思路走下去,把一个完整的“街霸”开发出来还是没有问题的。