天天看點

太空入侵者遊戲(VC++)

           太空入侵者遊戲

轉載請注明出處

本文章的下載下傳位址,請單擊此連結

入侵者是一個簡單的射擊遊戲。遊戲運作的初始界面如圖3.14所示,遊戲的戰鬥場面如圖3.15所示。

太空入侵者遊戲(VC++)

                                          圖3.14入侵者程式初始運作圖

太空入侵者遊戲(VC++)

                                                     圖3.15入侵者作戰運作圖

        在遊戲中,玩家控制飛船,一方面要向敵機射擊,另外還需要躲避敵人的子彈。直到目前關卡中

所有敵機被消滅,玩家才能進入下一關卡。

要編譯這個遊戲,需要安裝DirectX 8.0 SDK或以上版本。

在這個例子中要着重強調子彈、獎子(彈藥、獎金等)、卷屏等設計技巧,以及射擊過程是如何開

展的。相信通過對這個遊戲的了解,讀者對射擊遊戲的建構會有更深刻的認識。

3.7.1 獎子的設計

獎子是射擊類遊戲中必有的精靈,獎子實際就是玩家操作的飛船打掉敵機後,電腦獎賞的獎金、

彈藥、防彈服等。不過要想真正擁有這些獎子,玩家操縱的精靈必須在獎子消失(掉落到螢幕之外或

者在規定時間内消失)之前“吃”掉它。“吃”實際上是一種碰撞檢測行為。而在吃掉獎子之前,獎子

通常會自動下落。

如圖3.16 所示是入侵者遊戲中的獎子原始圖。Extras.bmp圖檔的分辨率是125×500。

太空入侵者遊戲(VC++)

                                           圖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();

}

}

}__

繼續閱讀