太空入侵者遊戲
轉載請注明出處
本文章的下載下傳位址,請單擊此連結
入侵者是一個簡單的射擊遊戲。遊戲運作的初始界面如圖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();
}
}
}__