太空入侵者游戏(VC++)

           太空入侵者游戏

转载请注明出处

本文章的下载地址,请单击此链接

入侵者是一个简单的射击游戏。游戏运行的初始界面如图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 中私有成员变量xy表示奖子方块左上角在屏幕上的像素坐标(事实上这两个变

量并不是必需的);而矩形结构rcLastPos表示奖子上次出现在屏幕时的坐标;整形的iType表示奖子

类型,这里共有5 种类型的奖子。公有变量frame表示帧编号,每种奖子都有20帧。在Extra__________读者

可能有疑惑的地方就是成员变量pNextpPrev,因为从名称上读者就能猜出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+25455,表示当前奖子已经掉落到

屏幕的下方(但不一定完全掉落),所以需要裁剪掉奖子的下半部分,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值等于455480中包括了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();

}

}

}__

你可能感兴趣的:(Visual,C++游戏开发实例)