太空入侵者游戏
转载请注明出处
本文章的下载地址,请单击此链接
入侵者是一个简单的射击游戏。游戏运行的初始界面如图3.14所示,游戏的战斗场面如图3.15所示。
图3.14入侵者程序初始运行图
图3.15入侵者作战运行图
在游戏中,玩家控制飞船,一方面要向敌机射击,另外还需要躲避敌人的子弹。直到当前关卡中
所有敌机被消灭,玩家才能进入下一关卡。
要编译这个游戏,需要安装DirectX 8.0 SDK或以上版本。
在这个例子中要着重强调子弹、奖子(弹药、奖金等)、卷屏等设计技巧,以及射击过程是如何开
展的。相信通过对这个游戏的理解,读者对射击游戏的构建会有更深刻的认识。
3.7.1 奖子的设计
奖子是射击类游戏中必有的精灵,奖子实际就是玩家操作的飞船打掉敌机后,电脑奖赏的奖金、
弹药、防弹服等。不过要想真正拥有这些奖子,玩家操纵的精灵必须在奖子消失(掉落到屏幕之外或
者在规定时间内消失)之前“吃”掉它。“吃”实际上是一种碰撞检测行为。而在吃掉奖子之前,奖子
通常会自动下落。
如图3.16 所示是入侵者游戏中的奖子原始图。Extras.bmp图片的分辨率是125×500。
图3.16入侵者中的奖子源视图extras.bmp
从图中不难看出入侵者中有5 种奖子,每种奖子有20 帧动画。由于extras的分辨率是125×500,
所以平均每个奖子的每一帧是一幅25×25分辨率的图像。
在入侵者中,单独设计了一个奖子类Extra:
classExtra
{
private:
int x;//奖子在屏幕中的位置
int y;
RECTrcLastPos; //奖子上次出现在屏幕中的包围盒坐标
intiType; //奖子类型,从extras.bmp中可以看出共有5 种类型
Extra*pNext; //奖子链表
Extra*pPrev;
public:
intframe; //帧编号,从extras.bmp中可以看出共有20 帧
Extra()
{
x = 0;
y = 0;
pNext= NULL;
pPrev= NULL;
frame= 0;
iType= 0;
rcLastPos.left= 0;
rcLastPos.top= 0;
第3 章2D 游戏开发121
rcLastPos.right= 0;
rcLastPos.bottom= 0;
};
intGetType()
{
returniType;
}
voidSetType(int iNewType)
{
iType= iNewType;
}
intGetX()
{
returnx;
}
intGetY()
{
returny;
}
voidMove(int Qtde)
{
rcLastPos.left= x;
rcLastPos.top= y;
rcLastPos.right= x + 25;
rcLastPos.bottom= y + 25;
if(rcLastPos.bottom > 455)
rcLastPos.bottom= 455;
//Qtde是一个负值,因为这种奖子都是垂直下落的,即y 值需要不断增加
y -=Qtde;
}
voidSetXY(int nx, int ny)
{
rcLastPos.left= nx;
rcLastPos.top= ny;
rcLastPos.right= nx + 25;
rcLastPos.bottom= ny + 25;
x =nx;
y =ny;
}
Extra*GetNext()
{
returnpNext;
}
122 Visual C++游戏开发技术与实例
Extra*GetPrev()
{
returnpPrev;
}
voidSetNext(Extra* nNext)
{
pNext= nNext;
}
voidSetPrev(Extra* nPrev)
{
pPrev= nPrev;
}
BOOLDraw(LPDIRECTDRAWSURFACE7 lpOrigin, LPDIRECTDRAWSURFACE7 lpSource)
{
RECTrcRect;
HRESULThRet;
intiClipTop;
intiClipBottom;
//子弹在超出屏幕上方(y 值小于0)和跨过屏幕下方(y+255>455)的时候需要进行裁剪
if (y< 0)
iClipTop= y * -1;
else
iClipTop= 0;
if(y+25 > 455)
iClipBottom= y+25-455;
else
iClipBottom= 0;
rcRect.left= frame * 25;
rcRect.top= ((iType - 1) * 25) + iClipTop;
rcRect.right= frame * 25 + 25;
rcRect.bottom= ((iType - 1) * 25) + 25 - iClipBottom;
//帧数编号加1
frame++;
if(frame == 20)
frame= 0;
while(1)
{
hRet=lpOrigin->BltFast(x,y,lpSource,&rcRect,DDBLTFAST_SRCCOLORKEY);
if(hRet == DD_OK)
{
break;
}
if(hRet == DDERR_SURFACELOST)
第3 章2D 游戏开发123
{
returnFALSE;
}
if(hRet != DDERR_WASSTILLDRAWING)
break;
}
returnTRUE;
}
};
很显然,Extra 中私有成员变量x、y表示奖子方块左上角在屏幕上的像素坐标(事实上这两个变
量并不是必需的);而矩形结构rcLastPos表示奖子上次出现在屏幕时的坐标;整形的iType表示奖子
类型,这里共有5 种类型的奖子。公有变量frame表示帧编号,每种奖子都有20帧。在Extra中__________读者
可能有疑惑的地方就是成员变量pNext和pPrev,因为从名称上读者就能猜出Extra变成了一种链表结
构。这么做的理由很简单,因为屏幕中可能同时出现多个奖子,所以为了奖子管理的需要,不妨将其
设置成链表结构。
Extra 中成员函数都比较简单,需要关注的只有Move函数和Draw函数。
Move 函数中出现的常数25是由奖子图像的长度、宽度(25×25)决定的;而语句rcLastPos.bottom>
455 是用来判断奖子是否已经掉出屏幕之外(确切的说是屏幕下限);语句y-=Qtde表示奖子是垂直下
落的,参数Qtde 实际是一个负值,后面会看到这个值被设置成–3。
Draw 函数中,首先碰到的问题就是对奖子的裁剪。裁剪解决的方法是当精灵在屏幕之外时,计算
实际需要绘制精灵的部分。在Draw 函数里,裁剪操作如下:
if (y< 0)
iClipTop= y * -1;
else
iClipTop= 0;
if(y+25 > 455)
iClipBottom= y+25-455;
else
iClipBottom= 0;
rcRect.left= frame * 25;
rcRect.top= ((iType - 1) * 25) + iClipTop;
rcRect.right= frame * 25 + 25;
rcRect.bottom= ((iType - 1) * 25) + 25 - iClipBottom;
当y 小于0,表示当前奖子在屏幕上方(但不一定完全在上方),所以需要裁剪掉奖子的上半部分,
iClipTop 指定了在25×25图像中,实际要绘制的顶部y值。当y+25>455,表示当前奖子已经掉落到
屏幕的下方(但不一定完全掉落),所以需要裁剪掉奖子的下半部分,iClipBottom指定了在25×25图像
中,实际要绘制的底部y 值。如果这两个条件都不满足,则说明奖子完全落在屏幕内,需要完整绘制。
裁剪计算完成后,需要调整帧数,每种奖子都有20帧。
frame++;
if(frame == 20)
frame= 0;
当然,这段代码可以简化成“(frame++)%20”。
帧调整完成后,绘制奖子。
124 Visual C++游戏开发技术与实例
lpOrigin->BltFast(x,y, lpSource, &rcRect, DDBLTFAST_SRCCOLORKEY);
在入侵者的主框架中定义了一个全局奖子对象Extra*pExtra。因为Extra是一种链表结构,所以在
pExtra 中记录了当前屏幕中所有奖子。而主框架中绘制奖子的函数DrawExtra定义如下。
voidDrawExtra()
{
// 绘制屏幕中所有奖子(奖金、弹药等)
Extra*pFirstExtra;
Extra*pNextExtra = NULL;
Extra*pLastExtra = NULL;
Extra*pPrevExtra = NULL;
//保存这个全局指针。因为后面的操作会改变这个指针,而最后还要恢复它
pFirstExtra= pExtra;
//因为当前屏幕中可能有多个奖子,所以这里使用while 语句就是要将所有的奖子都绘制出来
//当前屏幕中的奖子都被保存在同一个奖子链表中,所以绘制所有奖子时要求遍历整个奖子队列
while(pExtra != NULL)
{
//传入的是-3,即每次掉落3 个像素点
pExtra->Move(-3);
//绘制当前节点奖子,lpExtra 是extras.bmp 的DDraw 的Surface
pExtra->Draw(lpBackBuffer,lpExtra);
//如果当前的奖子已经掉落到屏幕以外,则调整奖子列表,删除当前奖子节点
if(pExtra->GetY() > 455)
{
//调整奖子链表节点和相关指针
if(pPrevExtra != NULL)
pPrevExtra->SetNext(pExtra->GetNext() );
pNextExtra= pExtra->GetNext();
if(pNextExtra != NULL)
pNextExtra->SetPrev(pPrevExtra);
//如果当前奖子节点位于链表中的第一位置,则还需要再调整链表
if(pExtra == pFirstExtra)
pFirstExtra=pExtra->GetNext();
//删除当前奖子节点
delete(pExtra);
//重置当前奖子节点
pExtra = pNextExtra;
}
else
{
//遍历奖子链表:当前奖子节点指向链表中下一个节点
pPrevExtra = pExtra;
第3 章2D 游戏开发125
pExtra = pExtra->GetNext();
}
}
pExtra = pFirstExtra;
}
事实上,DrawExtra 中很大部分的工作都是对Extra链表的操作。
最后的问题就是如何“吃”奖子?在前面已经提过了,这是一个碰撞检测问题。在主框架中定义
了“吃”奖子函数CheckHitExtra。通常“吃”奖子的碰撞检测采用的是最简单的矩形包围盒技术,这
里正是用了这种方法。当然“吃”完了奖子后,必须把它从奖子链表中删除。
int CheckHitExtra(void)
{
Extra *pFirstExtra, *pPrevExtra, *pNextExtra;
int iReturn = 0;
pPrevExtra = NULL;
pNextExtra = NULL;
pFirstExtra = pExtra;
//检测所有奖子是否和飞船发生了碰撞
while (pExtra != NULL)
{
//如果奖子在飞船的矩形包围盒内,则说明发生了“吃”的操作
//一旦吃掉,则需要删掉该奖子,同时调整链表
if ((pExtra->GetX() >= iShipPos &&
pExtra->GetX() < iShipPos+54 &&
pExtra->GetY() > 385 &&
pExtra->GetY() < 425) &&
(pExtra->GetType() != 0))
{
if (pPrevExtra != NULL)
pPrevExtra->SetNext( pExtra->GetNext() );
pNextExtra = pExtra->GetNext();
if (pNextExtra != NULL)
pNextExtra->SetPrev(pPrevExtra);
if (pExtra == pFirstExtra)
pFirstExtra=pExtra->GetNext();
iReturn = pExtra->GetType();
delete(pExtra);
pExtra = pFirstExtra;
return iReturn;
}
pPrevExtra = pExtra;
pExtra = pExtra->GetNext();
}
pExtra = pFirstExtra;
return iReturn;
}
126 Visual C++游戏开发技术与实例
3.7.2 子弹(Bullet)的设计
在入侵者中子弹的设计几乎是和奖子的设计一样的,只是在图像的宽度和高度上需要进行调整。
读者不妨查看类Bullet,然后再和Extra类做个比较。
不过子弹的设计并不是一成不变的。在游戏设计中,子弹的设计方式和方法很多,这里只是采用
了静态的直线传递方法。子弹的弹道通常决定了子弹设计的方式。例如,很多游戏中的追踪弹、散花
弹和激光弹等。对于追踪弹来说,它会自动锁定目标,当然这并不意味着一定能够击落敌机。通常情
况下,敌机如果可以将追踪弹引出屏幕之外就算追踪弹无效,这是很公平的决策。在追踪弹设计中,
弹道的设计需要平滑,因为折线式的追踪轨迹并不现实。平滑的方法很多,样条曲线就是一个不错的
选择,在第4 章中会介绍到一种3次样条曲线的设计方法。对于散花弹而言,可以考虑成由多个单一
的子弹组成,不过这种方法需要考虑效率。而激光弹是一种特殊的子弹,它的有效攻击范围实际是会
变化的。这种子弹的设计需要考虑真实世界的激光。当打开激光源的时候,光会快速的形成一条射线,
在这条轨迹内的任何物体实际上都会被激光照射,而当关闭激光源的时候,激光总是消失在远处。根
据这种物理规律,才能够设计出比较合理的激光子弹。
3.7.3 卷屏(Scroll)的设计
在入侵者游戏中,为了体现游戏中的飞船处于飞行状态,采用了一种简单的卷屏技术。这种卷屏
技术是利用背景图片的变化实现的。如图3.17 所示是入侵者的背景图片。它是640×480分辨率的图片。
而该图片中底部的状态栏占据了25 个像素的高度。
图3.17 背景图片
卷屏函数DrawInvalidBackGround定义如下:
void DrawInvalidBackGround()
{
//重绘背景
RECT rcRect;
HRESULT hRet;
Static y = 0;
const int iFim = 455;
//新关卡
第3 章2D 游戏开发127
if (lastTickCount = 0)
{
y =0;
}
//向下卷屏一个像素
y += 1;
//如果已经卷完455 个像素(即一个屏幕),则y 值重新置0。
if (y>iFim)
y = 0;
//上半屏的矩形区域
rcRect.left = 0;
rcRect.top = 0;
rcRect.right = 640;
rcRect.bottom = iFim-y;
while (1)
{
hRet = lpBackBuffer->BltFast(0, y, lpBkGround,
&rcRect, DDBLTFAST_NOCOLORKEY);
if (hRet == DD_OK)
{
break;
}
if (hRet == DDERR_SURFACELOST)
{
hRet = RestoreSurfaces();
if (hRet != DD_OK)
break;
}
if (hRet != DDERR_WASSTILLDRAWING)
break;
}
//下半屏的矩形区域
rcRect.left = 0;
rcRect.top = iFim-y;
rcRect.right = 640;
rcRect.bottom = iFim;
while (1)
{
hRet = lpBackBuffer->BltFast(0, 0, lpBkGround,
&rcRect, DDBLTFAST_NOCOLORKEY);
if (hRet == DD_OK)
{
break;
}
if (hRet == DDERR_SURFACELOST)
{
hRet = RestoreSurfaces();
128 Visual C++游戏开发技术与实例
if (hRet != DD_OK)
break;
}
if (hRet != DDERR_WASSTILLDRAWING)
break;
}
}
DrawInvalidBackGround 函数使用的卷屏方法是将背景图片从水平方向分成两个部分,然后分别绘
制上下部分的图片。背景图片的分割线是从关卡开始后背景下降的y值累加之和。每次绘制背景的时
候y 值都要增加1个像素,直到y值等于455(480中包括了25个像素高度的状态栏)后重置为0。
通过这样的两次背景绘制,屏幕就动了起来。
需要指出的是,对于一些复杂的横板卷屏游戏而言,这种方法就完全行不通了。因为横板游戏的
背景中包含了复杂的地形,这种地形是影响游戏发展的,而不像在入侵者中背景对整个游戏进程毫无
影响。
上面的DrawInvalidBackGround函数是在UpdateFrame中被调用的,而UpdateFrame函数负责的
是整个游戏状态的刷新。其中屏幕刷新的时候,遵守一个刷新顺序:背景刷新、奖子刷新、敌机刷新、
子弹刷新、飞船刷新。
下面来看看UpdateFrame 函数是如何工作的。UpdateFrame 函数在开始部分定义了一些局部变量:
void UpdateFrame( void )
{
int ddrval;
DWORD thisTickCount;
RECT rcRect;
DWORD delay = 18;
static int frame = 0;
HBITMAP hbm;
DDBLTFX ddbltfx;
HRESULT hRet;
接着,它判断飞船是否已经爆炸了一段时间。如果是这样,则载入游戏结束的画面,重置一些全
局变量。
thisTickCount = GetTickCount();
if (thisTickCount - dwShipExplode > 2000&&iShipState == 3)
{
Ovni* auxpUFO;
Bullet* auxpBullet;
while(pUFO !=NULL)
{
auxpUFO = pUFO->GetNext();
delete(pUFO);
pUFO = auxpUFO;
}
while(pBullet !=NULL)
{
auxpBullet = pBullet->GetNext();
第3 章2D 游戏开发129
delete(pBullet);
pBullet = auxpBullet;
}
ShowGameOver();
lastTickCount = 0;
iAppState = 0;
iShipState = 0;
}
下面判断当前游戏处于何种状态。其中APP_MAINMENU标志表示当前游戏处于主菜单状态;
APP_GAMESCREEN 标志表示游戏已经开始;APP_CREDITS表示游戏处于显示积分的状态;
APP_HELPSCREEN 表示游戏处于帮助屏幕状态。
switch (iAppState)
{
case APP_MAINMENU:
//如果时间间隔过短,则返回
if ((thisTickCount - lastTickCount) <= delay)
return;
//如果是主菜单界面,则需要做一些初始化工作
if (lastTickCount == 0)
{
iOption = 0;
frame = 0; //设置飞船光标帧标号为0
// 载入初始界面的bmp 文件
hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL),MAKEINTRESOURCE
(IDB_INVASION), IMAGE_BITMAP, 0, 0,LR_CREATEDIBSECTION );
if( NULL == hbm )
return;
// 将初始界面拷贝到前台缓冲中
ddrval = DDCopyBitmap(lpFrontBuffer, hbm, 0, 0,640, 480 );
if( ddrval != DD_OK )
{
DeleteObject( hbm );
return;
}
// 将初始界面拷贝到后台缓冲中
ddrval = DDCopyBitmap(lpBackBuffer, hbm, 0, 0,640, 480 );
if( ddrval != DD_OK )
{
DeleteObject( hbm );
return;
}
// 画出4 个菜单,START GAME、CREDITS、HELP 和QUIT。
bltText("START GAME\0",190,280);
130 Visual C++游戏开发技术与实例
bltText("CREDITS\0",190,320);
bltText("HELP\0",190,360);
bltText("QUIT\0",190,400);
}
ddbltfx.dwSize = sizeof( ddbltfx );
ddbltfx.dwFillColor = dwFillColor;
rcRect.left = 130;
rcRect.top = 270;
rcRect.right = 190;
rcRect.bottom = 460;
while (1)
{
//填充后台缓冲的某个矩形区域为黑色
hRet=lpBackBuffer->Blt(&rcRect,NULL,NULL,DDBLT_COLORFILL,&ddbltfx);
if (hRet == DD_OK)
{
break;
}
if (hRet == DDERR_SURFACELOST)
{
hRet = RestoreSurfaces();
if (hRet != DD_OK)
break;
}
if (hRet != DDERR_WASSTILLDRAWING)
break;
}
//当前菜单选择光标帧的矩形区域
rcRect.left = frame * 32;
rcRect.right = (frame * 32) + 32;
rcRect.top = 0;
rcRect.bottom = 20;
while (1)
{
//绘制这个光标
hRet = lpBackBuffer->BltFast(150 , 275+ (40 *iOption), lpSelect, &rcRect,
TRUE);
if (hRet == DD_OK)
{
break;
}
if (hRet == DDERR_SURFACELOST)
{
hRet = RestoreSurfaces();
if (hRet != DD_OK)
break;
}
if (hRet != DDERR_WASSTILLDRAWING)
第3 章2D 游戏开发131
break;
}
//帧数自增
frame++;
if (frame > 19)
frame = 0;
while( 1 )
{
//前后台交换缓冲区
ddrval = lpFrontBuffer->Flip(NULL, 0 );
if( ddrval == DD_OK )
{
break;
}
if( ddrval == DDERR_SURFACELOST )
{
if( !RestoreSurfaces() )
{
return;
}
}
if( ddrval != DDERR_WASSTILLDRAWING )
{
break;
}
}
//重置上次时间
lastTickCount = thisTickCount;
return;
case APP_GAMESCREEN:
int iAmmo;
//游戏刚开始,初始化
if (lastTickCount == 0)
{
bShoot = FALSE;
//设置界面中的矩形区域
rcRect.left = 0;
rcRect.top = 0;
rcRect.right = 640;
rcRect.bottom = 480;
//设置前台和后台缓冲
DrawBackGround(lpBackBuffer,0,455,640,480);
DrawBackGround(lpFrontBuffer,0,455,640,480);
}
//在状态栏中绘制生命值
DrawShield();
132 Visual C++游戏开发技术与实例
//如果得分发生变化,则在状态栏中重绘得分
if (Lastscore != score)
{
DrawBackGround(lpBackBuffer,510,460,640,477);
DrawBackGround(lpFrontBuffer,510,460,640,477);
DrawScore(510,460);
Lastscore = score;
}
//如果武器不一样则重绘武器
if (iLastWeapon != iWeapon)
{
DrawBackGround(lpBackBuffer, 84,458, 172,477);
DrawBackGround(lpFrontBuffer,84,458, 172,477);
DrawWeapon();
iLastWeapon = iWeapon;
iLastAmmo = -1;
}
if (iWeapon < 3)
iAmmo = iLaserAmmo;
else
iAmmo = iPhotonAmmo;
if (iLastAmmo != iAmmo)
{
DrawBackGround(lpBackBuffer,241,459,300,477);
DrawBackGround(lpFrontBuffer,241,459,300,477);
DrawAmmo();
iLastAmmo = iAmmo;
}
if ((thisTickCount - lastTickCount) <= delay)
return;
//如果敌机和奖子都没有出现,则要初始化关卡
if (pUFO == NULL &&pExtra == NULL)
{
SndObjPlay(hsoEnter, NULL);
InitLevel(TRUE);
DrawBackGround(lpBackBuffer,0,0,640,480);
DrawBackGround(lpFrontBuffer,0,0,640,480);
DrawScore(510,459);
lastTickCount = 0;
return;
}
//卷屏
DrawInvalidBackGround();
//绘制奖子
DrawExtra();
//绘制敌机
DrawUfo();
//绘制子弹
第3 章2D 游戏开发133
DrawBullet();
//绘制飞船
DrawShip();
bShoot = FALSE;
while( 1 )
{
//交换前后台缓冲
ddrval = lpFrontBuffer->Flip(NULL, 0 );
if( ddrval == DD_OK )
{
break;
}
if( ddrval == DDERR_SURFACELOST )
{
if( !RestoreSurfaces() )
{
return;
}
}
if( ddrval != DDERR_WASSTILLDRAWING )
{
break;
}
}
lastTickCount = GetTickCount();
break;
case APP_CREDITS: //显示屏幕积分
ShowCredits();
iAppState = 0;
lastTickCount = 0;
break;
case APP_HELPSCREEN: //显示帮助屏幕
static BOOL bBlink;
static DWORD dwTime;
if ((thisTickCount - lastTickCount) <=delay+20)
return;
if (lastTickCount == 0)
{
ddbltfx.dwSize = sizeof( ddbltfx );
ddbltfx.dwFillColor = RGB(0,0,0);
rcRect.left = 0;
rcRect.top = 0;
rcRect.right = 640;
rcRect.bottom = 480;
while (1)
{
hRet=lpFrontBuffer->Blt(&rcRect,NULL,NULL,DDBLT_COLORFILL,&ddbltfx);
if (hRet == DD_OK)
{
break;
134 Visual C++游戏开发技术与实例
}
if (hRet == DDERR_SURFACELOST)
{
hRet = RestoreSurfaces();
if (hRet != DD_OK)
break;
}
if (hRet != DDERR_WASSTILLDRAWING)
break;
}
while (1)
{
hRet = lpBackBuffer->Blt(&rcRect, NULL,NULL, DDBLT_COLORFILL,
&ddbltfx);
if (hRet == DD_OK)
{
break;
}
if (hRet == DDERR_SURFACELOST)
{
hRet = RestoreSurfaces();
if (hRet != DD_OK)
break;
}
if (hRet != DDERR_WASSTILLDRAWING)
break;
}
bltText("PHOTON AMMO\0",90,45);
bltText("WEAPON ADVANCE\0",90,95);
bltText("100 POINTS BONUS\0",90,145);
bltText("LASER AMMO\0",90,195);
bltText("SHIELD CHARGE\0",90,245);
bltText("LEFT, RIGHT - MOVESHIP\0",50,290);
bltText("DOWN OR ENTER - STOPSHIP\0",50,325);
bltText("CTRL - CHANGEWEAPON\0",50,360);
bltText("PRESS ANY KEY TOCONTINUE\0",140,450);
bBlink = FALSE;
dwTime = 0;
}
ddbltfx.dwSize = sizeof( ddbltfx );
ddbltfx.dwFillColor = dwFillColor;
rcRect.left = 50;
rcRect.top = 40;
rcRect.right = 75;
rcRect.bottom = 270;
while (1)
{
hRet=lpBackBuffer->Blt(&rcRect,NULL,NULL,DDBLT_COLORFILL,&ddbltfx);
第3 章2D 游戏开发135
if (hRet == DD_OK)
{
break;
}
if (hRet == DDERR_SURFACELOST)
{
hRet = RestoreSurfaces();
if (hRet != DD_OK)
break;
}
if (hRet != DDERR_WASSTILLDRAWING)
break;
}
if (bBlink == TRUE)
{
ddbltfx.dwSize = sizeof( ddbltfx );
ddbltfx.dwFillColor = dwFillColor;
rcRect.left = 0;
rcRect.top = 450;
rcRect.right = 640;
rcRect.bottom = 475;
while (1)
{
hRet = lpBackBuffer->Blt(&rcRect, NULL,NULL, DDBLT_COLORFILL,
&ddbltfx);
if (hRet == DD_OK)
{
break;
}
if (hRet == DDERR_SURFACELOST)
{
hRet = RestoreSurfaces();
if (hRet != DD_OK)
break;
}
if (hRet != DDERR_WASSTILLDRAWING)
break;
}
dwTime = dwTime + 1;
if (dwTime == 20)
{
bltText("PRESS ANY KEY TOCONTINUE\0",140,450);
bBlink = FALSE;
dwTime = 0;
}
}
else
{
dwTime = dwTime + 1;
if (dwTime == 20)
136 Visual C++游戏开发技术与实例
{
//bltText("PRESS ANY KEY TOCONTINUE\0",140,450);
bBlink = TRUE;
dwTime = 0;
}
}
rcRect.left = frame * 25;
rcRect.right = (frame * 25) + 25;
rcRect.top = 0;
rcRect.bottom = 25;
while (1)
{
hRet = lpBackBuffer->BltFast(50, 40, lpExtra,&rcRect, TRUE);
if (hRet == DD_OK)
{
break;
}
if (hRet == DDERR_SURFACELOST)
{
hRet = RestoreSurfaces();
if (hRet != DD_OK)
break;
}
if (hRet != DDERR_WASSTILLDRAWING)
break;
}
rcRect.top = 25;
rcRect.bottom = 50;
while (1)
{
hRet =lpBackBuffer->BltFast(50,90,lpExtra,&rcRect,TRUE);
if (hRet == DD_OK)
{
break;
}
if (hRet == DDERR_SURFACELOST)
{
hRet = RestoreSurfaces();
if (hRet != DD_OK)
break;
}
if (hRet != DDERR_WASSTILLDRAWING)
break;
}
rcRect.top = 50;
rcRect.bottom = 75;
while (1)
{
hRet = lpBackBuffer->BltFast(50, 140, lpExtra,&rcRect, TRUE);
if (hRet == DD_OK)
第3 章2D 游戏开发137
{
break;
}
if (hRet == DDERR_SURFACELOST)
{
hRet = RestoreSurfaces();
if (hRet != DD_OK)
break;
}
if (hRet != DDERR_WASSTILLDRAWING)
break;
}
rcRect.top = 75;
rcRect.bottom = 100;
while (1)
{
hRet = lpBackBuffer->BltFast(50, 190, lpExtra,&rcRect, TRUE);
if (hRet == DD_OK)
{
break;
}
if (hRet == DDERR_SURFACELOST)
{
hRet = RestoreSurfaces();
if (hRet != DD_OK)
break;
}
if (hRet != DDERR_WASSTILLDRAWING)
break;
}
rcRect.top = 100;
rcRect.bottom = 125;
while (1)
{
hRet = lpBackBuffer->BltFast(50, 240, lpExtra,&rcRect, TRUE);
if (hRet == DD_OK)
{
break;
}
if (hRet == DDERR_SURFACELOST)
{
hRet = RestoreSurfaces();
if (hRet != DD_OK)
break;
}
if (hRet != DDERR_WASSTILLDRAWING)
break;
}
frame++;
138 Visual C++游戏开发技术与实例
if (frame == 19)
frame = 0;
while( 1 )
{
ddrval = lpFrontBuffer->Flip(NULL, 0 );
if( ddrval == DD_OK )
{
break;
}
if( ddrval == DDERR_SURFACELOST )
{
if( !RestoreSurfaces() )
{
return;
}
}
if( ddrval != DDERR_WASSTILLDRAWING )
{
break;
}
}
lastTickCount = thisTickCount;
return;
}
return;
}
至此,UpdateFrame 函数分析结束。UpdateFrame函数是在程序处于空闲的时候被调用的,WinMain
函数中体现了这一点:
int PASCAL WinMain( HINSTANCE hInstance,HINSTANCE hPrevInstance, LPSTR lpCmdLine,int
nCmdShow )
{
MSG msg;
hInst = hInstance;
//初始化win32 框架
if( !initApplication(hInstance, nCmdShow) )
{
return FALSE;
}
// 初始化游戏
if( !InitializeGame() )
{
OutputDebugString("ERROR STARTING THEGAME\n");
DestroyWindow( hWndMain );
return FALSE;
}
// 初始化声音缓冲
InitializeSound();
第3 章2D 游戏开发139
// 遍历消息队列,当消息队列为空时更新屏幕
while( 1 )
{
if( PeekMessage( &msg, NULL, 0, 0,PM_NOREMOVE ) )
{
if( !GetMessage( &msg, NULL, 0, 0 ) )
{
return msg.wParam;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else if ( bIsActive )
{
UpdateFrame();
}
else
{
WaitMessage();
}
}
}__