
太空入侵者游戏.doc
30页太空入侵者游戏入侵者是一个简单的射击游戏游戏运行的初始界面如图3.14 所示,游戏的战斗场面如图3.15 所示图3.14 入侵者程序初始运行图图3.15 入侵者作战运行图 在游戏中,玩家控制飞船,一方面要向敌机射击,另外还需要躲避 敌人的子弹直到当前关卡中所有敌机被消灭,玩家才能进入下一关卡 要编译这个游戏,需要安装DirectX 8.0 SDK 或以上版本 在这个例子中要着重强调子弹、奖子(弹药、奖金等)、卷屏等设 计技巧,以及射击过程是如何开 展的相信通过对这个游戏的理解,读者对射击游戏的构建会有更 深刻的认识 3.7.1 奖子的设计 奖子是射击类游戏中必有的精灵,奖子实际就是玩家操作的飞船打 掉敌机后,电脑奖赏的奖金、 弹药、防弹服等不过要想真正拥有这些奖子,玩家操纵的精灵必 须在奖子消失(掉落到屏幕之外或 者在规定时间内消失)之前“吃”掉它吃”实际上是一种碰撞 检测行为而在吃掉奖子之前,奖子 通常会自动下落 如图3.16 所示是入侵者游戏中的奖子原始图Extras.bmp 图片的分 辨率是125500图3.16 入侵者中的奖子源视图extras.bmp 从图中不难看出入侵者中有5 种奖子,每种奖子有20 帧动画。
由于 extras 的分辨率是125500, 所以平均每个奖子的每一帧是一幅2525 分辨率的图像 在入侵者中,单独设计了一个奖子类Extra: class Extra { private: int x; //奖子在屏幕中的位置 int y;RECT rcLastPos; //奖子上次出现在屏幕中的包围盒坐标 int iType; //奖子类型,从extras.bmp 中可以看出共有5 种 类型 Extra* pNext; //奖子链表 Extra* pPrev; public: int frame; //帧编号,从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; }; int GetType() { return iType; } void SetType(int iNewType) { iType = iNewType; } int GetX() { return x; } int GetY(){ return y; } void Move(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; } void SetXY(int nx, int ny) { rcLastPos.left = nx; rcLastPos.top = ny; rcLastPos.right = nx + 25; rcLastPos.bottom = ny + 25; x = nx; y = ny; } Extra* GetNext() { return pNext; } 122 Visual C++游戏开发技术与实例 Extra* GetPrev() { return pPrev; } void SetNext(Extra* nNext){ pNext = nNext; } void SetPrev(Extra* nPrev) { pPrev = nPrev; } BOOL Draw(LPDIRECTDRAWSURFACE7 lpOrigin, LPDIRECTDRAWSURFACE7 lpSource) { RECT rcRect; HRESULT hRet; int iClipTop; int iClipBottom; //子弹在超出屏幕上方(y 值小于0)和跨过屏幕下方 (y+255>455)的时候需要进行裁剪 if (y 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, if (hRet == DD_OK) { break; } if (hRet == DDERR_SURFACELOST) 第3 章2D 游戏开发123 { return FALSE; } if (hRet != DDERR_WASSTILLDRAWING) break; } return TRUE; } }; 很显然,Extra 中私有成员变量x、y 表示奖子方块左上角在屏幕上 的像素坐标(事实上这两个变 量并不是必需的);而矩形结构rcLastPos 表示奖子上次出现在屏幕 时的坐标;整形的iType 表示奖子 类型,这里共有5 种类型的奖子。
公有变量frame 表示帧编号,每 种奖子都有20 帧在Extra 中__________读者 可能有疑惑的地方就是成员变量pNext 和pPrev,因为从名称上读者 就能猜出Extra 变成了一种链表结 构这么做的理由很简单,因为屏幕中可能同时出现多个奖子,所 以为了奖子管理的需要,不妨将其 设置成链表结构 Extra 中成员函数都比较简单,需要关注的只有Move 函数和Draw 函数 Move 函数中出现的常数25 是由奖子图像的长度、宽度(2525)决 定的;而语句rcLastPos.bottom>455 是用来判断奖子是否已经掉出屏幕之外(确切的说是屏幕下限) ;语句y-=Qtde 表示奖子是垂直下 落的,参数Qtde 实际是一个负值,后面会看到这个值被设置成–3 Draw 函数中,首先碰到的问题就是对奖子的裁剪裁剪解决的方 法是当精灵在屏幕之外时,计算 实际需要绘制精灵的部分在Draw 函数里,裁剪操作如下: if (y 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 指定了在2525 图像中,实际要绘制的顶部y 值。
当 y+25>455,表示当前奖子已经掉落到 屏幕的下方(但不一定完全掉落),所以需要裁剪掉奖子的下半部 分,iClipBottom 指定了在2525 图像 中,实际要绘制的底部y 值如果这两个条件都不满足,则说明奖 子完全落在屏幕内,需要完整绘制 裁剪计算完成后,需要调整帧数,每种奖子都有20 帧 frame++; if (frame == 20) frame = 0; 当然,这段代码可以简化成“(frame++)%20” 帧调整完成后,绘制奖子 124 Visual C++游戏开发技术与实例lpOrigin->BltFast(x, y, lpSource, 在入侵者的主框架中定义了一个全局奖子对象Extra*pExtra因为 Extra 是一种链表结构,所以在 pExtra 中记录了当前屏幕中所有奖子而主框架中绘制奖子的函数 DrawExtra 定义如下 void DrawExtra() { // 绘制屏幕中所有奖子(奖金、弹药等) 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 pNextExtra = pExtra->GetNext(); if (p。












