金橋科普  
設為首頁 關于我們 郵箱登錄
科普聚焦
您現在的位置: 首頁 >> 科普頻道 >> 電腦遊戲 >> 文章詳情
DirectDraw編程基礎

pcant gameres 2006-09-04

作者:pcant     

 
 

DirectDraw編程基礎

 
 
  本文面向有幾個月學習編程經歷的初學者:看過C++的教程,看的懂基本的C++語法;有點點VC使用經驗,知道怎麼去組建一個工程;理解一些windows編程的基本概念,比如窗口、消息循環等;還有,不懂的地方會去查資料:)。
  看過幾本關于DirectDraw的書,這些書都不錯,在此感謝她們的作者。美中不足的是這些書的部分起點較高,雖然我們仍然能夠清晰的理解一些概念,但在組織這些文件上會有不少困惑。在此我重申一下書中的概念,也借此梳理一下自己的思路。廢話少說,言歸正傳。
首先說一些不可不說的東西。我認為它們不可不提,是因為這些東西也許太基礎,高手們往往忽略這些東西對新手的作用。作為一個新手,我覺得掌握程序的框架及組織方法,比多熟悉幾個APIs更迫切一些。Now lets begin:
  寫一個遊戲程序,要熟悉其流程,另外要鍛鍊組織程序文件的能力。對新手來說,我建議按部就班的來處理及分析要寫的程序,不主張這個時候你在搞思維跳躍。這是個良好的習慣,當然也有利于我們盡快掌握編程的思想方法。下面來看一個概括的流程及相應的程序框架

(框架顯示不出來。。)

  那麼,如何利用上面的流程來構建我們的大體程序框架呢?

  我們已經知道一些windows編程方面的東西了,也許你還比較了解MFC。我們這裡不提倡用MFC,盡管它封裝了好多有用的模式,但對我們編遊戲來說,倒是累贅了。好,接著說。既然採用windowsAPI,可以建立個文件WinMain.cpp來處理windows編程中有關窗口的一些問題。這樣,我們在該文件中應該完成創建窗口,處理基本消息(比如按“esc”退出等),控制程序退出等。遊戲過程中窗口的消息是不是也要在這處理呢?當然,不過遊戲當中的窗口就不僅是windows窗口了,顯示部分要靠DirectDraw來控制,那麼我們只好在WinMain.cpp中調用相關的模塊來處理。這麼看來,在WinMain.cpp中幾乎囊括了整個流程,不錯,它就控制了程序的整個框架,為你的程序內核提供了一個平台。平台有了,那麼下一步,GameMain.cpp要誕生了,這個主要用來控制整個遊戲的各個組件,協調各部分工作,完成遊戲設置初始化,遊戲中消息循環,控制遊戲退出。你的才華就在這兒來盡情的發揮了。一般,遊戲程序會有幾個固定的組件的:顯示,音樂,信息輸入。在DirectX中提供了很方便的組件DirectDraw,DirectSound和DirectMusic,DirectInput。相應的我們建立MyDirectDraw.cpp,MyDirectAudio.cpp,MyDirectInput.cpp來控制各部分組件的相應功能。
顯然,這3部分都是為GameMain.cpp服務的,被GameMain.cpp調用。那麼我們可以看出我們的程序應該包括的文件及其包含關系為:

(圖表顯示不出來了,555)

  程序文件怎麼去組織,應該由這個表可以看出來。這麼一看,我們發現,WinMain.cpp好像是一個投資者,提供開發平台,他只關注整個項目總的進程,不關注細節。GameMain.cpp好像個項目負責人,整個項目的細節過程由他來策劃,來控制,向上與WinMain.cpp交互,來完成項目,向下協調MyDirectDraw.cpp,MyDirectAudio.cpp,MyDirectInput.cpp之間的工作。MyDirectDraw.cpp,MyDirectAudio.cpp,MyDirectInput.cpp這三個家伙就是員工了,負責各自的工作,完成相應的功能給GameMain.cpp。

  組織程序應該就是這麼個思路,當然具體問題具體分析。那麼我們下面來開始看DirectDraw部分了。

  首先,做準備工作,安裝DirectX SDK,在VC中添加dxguid.lib和ddraw.lib(本來不想說這個,看到有個教程,它少加了dxguid.lib,鬱悶了我好一陣子,害人頗深感覺)這樣,directdraw程序才能通過編譯。提一下,dxguid.lib中定義了DirectX中會用到的所有全局句柄,ddraw.lib是DirectDraw使用的函數庫。

下面就可以寫代碼了,這裡我們當然主要看MyDirectDraw.cpp該怎麼寫了
為此,我選出了幾個源代碼,做參考研究,它們會與本文一起打包。
我還是習慣先從整體上鳥瞰一下:

  一般,在MyDirectDraw.cpp(注意不要忘記引用頭文件ddraw.h)中至少要有兩部分:初始化和結束。先看初始化,所謂初始化無非是個準備工作,需要的東西定義創建出來擺在手邊以備後用。來看看初始化函數intMyDirectDrawInit(void)該怎麼寫。首先定義一個指向DirectDraw對象的指針,創建DirectDraw對象,查詢以獲取最新的DirectDraw接口,設置協作等級,設置顯示模式。通過這些步驟可以創建一個黑色的屏幕了,也就是說已經開闢了我們需要的空間了,當然DirectDraw程序的初始化不會這麼簡單。要操作2d圖形,我們還要接著創建主頁面和緩衝頁面以及離屏頁面,總之根據需要,凡是需要在操作前需要準備好的東西都可以放在這裡。那麼結束 int MyDirectDrawShut(void)就應該釋放我們開闢的東西,一般要釋放主頁面指針,和DirectDraw接口等。

大體就是這麼個樣子,go on,該細一點了,呵呵

先定義指針:LPDIRECTDRAW lpDDraw_temp;代表整個顯示系統
創建對象: if (FAILED(DirectDrawCreate(NULL, &lpDDraw_temp, NULL)))
{
    MessageBox(NULL,TEXT("Direct Draw Create error!"),
    TEXT("Wrong!"),MB_OK);
    return(0);
}

這裡用了一個FAILED宏來檢測是否創建成功,這可以幫我們跟蹤錯誤。

函數DirectDrawCreate(NULL, &lpDDraw_temp, NULL)完成創建,第一個參數是顯示驅動的全局唯一標志符,這裡null表示目前的顯示設備;第二個參數用來接受創建出來的DirectDraw對象地址,這裡用&lpDDraw_temp接受;第三個參數?不要問,就給它null,不想惹麻煩的話。

查詢DirectDraw接口:if(FAILED(lpDDraw_temp->QueryInterface(IID_IDirectDraw7, (LPVOID *)&lpDDraw7)))
{
    MessageBox(NULL,TEXT("DirectDraw QueryInterface error!"),
    TEXT("Wrong!"),MB_OK);
    return(0);
}


通過QueryInterface()方法來獲取新接口,這裡是IDirectDraw7而不是IDirectDraw8,指向IDirectDraw7的指針放在lpDDraw7中,這是個全局變量,可以這樣定義LPDIRECTDRAW7 lpDDraw7=NULL;

順便說一下,一般情況下你是應該知道你使用的接口的,這和SDK有關,所以說這一步不是必須的。

設置協作等級: if (FAILED(lpDDraw7->SetCooperativeLevel(main_window_handle, DDSCL_FULLSCREEN | DDSCL_ALLOWMODEX | DDSCL_EXCLUSIVE | DDSCL_ALLOWREBOOT)))
{
    MessageBox(NULL,TEXT("DirectDraw SetCooperativeLevel error!"),
    TEXT("Wrong!"),MB_OK);
    return(0);
}


決定你這個程序和windows的關系,它向windows申請所用資源,比如它要全屏,獨佔等。第一個參數是主窗口句柄,就是你WinMain()中創建的那個了,第二個參數有幾個控制標志,常用的用法如下:

DDSCL_FULLSCREEN:全屏模式,必須和DDSCL_EXCLUSIVE同時使用
DDSCL_EXCLUSIVE:請求獨佔級別,須和DDSCL_FULLSCREEN同時使用
DDSCL_ALLOWREBOOT:允許系統檢測ctrl+alt+del按鍵消息(這很有用)


我想,這三個就夠用了,其他的就先不用管了
設置顯示模式:if(FAILED(lpDDraw7->SetDisplayMode(800, 600, 16,0,0)))
{
    MessageBox(NULL,TEXT("DirectDraw SetDisplayMode error!"),
    TEXT("Wrong!"),MB_OK);
    return(0);
}

遊戲中要使用的顯示模式可能和用戶當前顯示模式不一樣,要在此統一設置SetDisplayMode()強制使用它設置的模式,它的前三個參數很容易懂吧,第四個,用0表示使用默認的刷新率,第五個參數這裡是0,有書上說必須用DDSDM_STANDVGAMODE(可以理解,只是不知道這個0什麼意思,我想應該是default的意思吧。

到此為止,我想已經創建出來我們需要的空間了,以後,隨著我們要求的提高,再逐步完善初始化函數,now看看結束函數:
釋放接口: if (lpDDraw7)
{
    lpDDraw7->Release();
    lpDDraw7 = NULL;
}

以後還要釋放主頁面,緩衝頁面等,需要注意一點的是一定要釋放你申請的資源,這是個好習慣,更應該注意的一點是先創建的一定要後釋放,因為後創建的可能是在先創建的環境下工作的。

到此為止,我們只是做好了最基礎的準備工作,什麼還都不能做呢
想做點什麼嗎?歇會吧,說點不得不說的題外話:

那麼我們來看看顏色吧。有關色彩,分這麼幾種,256色(8位的),16位增強色,24位真彩和32位真彩。256色估計很少用了,16位目前還是主流,所以我們著重看一下16位增強色,通常16位增強色有兩種格式:5.5.5和5.6.5,一般用RGB表示法表示。其中:
5.5.5格式,最高位為Alpha位,表示是不是透明,其餘15位表示顏色,紅綠藍各5位,這種格式可以表示32786種顏色。通過宏

#define _RGB16BIT555(r,g,b)((b%32)+((g%32)<<5)+((r%32)<<10))來轉變成5.5.5格式

對5.6.5格式,顯然,紅藍各5位,綠6位,這樣可以表示65536種顏色,同樣,宏

#define _RGB16BIT565(r,g,b) ((b%32)+((g%64)<<6)+((r%32)<<11))來轉變成5.6.5格式

中間的移位我也搞不清楚是怎麼回事,姑且先不看了,看的越多可能越胡塗哦
那麼到底該用哪種格式?看機器了,大部分可以用5.6.5,當然你可以檢測一下,至于怎麼檢測嘛,我就不說了,查查相關資料就可以了。24位呢?紅綠藍各8位唄,32位?添個Alpha位,其餘同24位。好了顏色就說到這裡。

下面想幹嘛?想在屏幕上搞點顏色出來,參看附的源代碼code1
  你會不會發現我們還應該在上面的基礎上添點什麼?對,應該在初始化函數裡創建頁面,也就是DirectDrawSurface對象,那它和DirectDraw對象什麼區別?DirectDraw對象,我們知道是表示整個顯示系統,也就是你的顯卡和顯屏構成的那個系統,你能在顯示器屏幕上直接畫點東西嗎?不行,顯屏上的東西是通過顯存和內存操作把裡面的東西顯示出來,那麼相對應于顯屏,內存中就應該有一張矩形白紙供你作畫,然後才能把它在顯屏上顯示。那張白紙就是DirectDrawSurface對象,代表了顯存或內存裡的一個連續的線性的數據區。這個數據區可以被代表顯示硬件的DirectDraw對象所識別和確認。一般,可以創建的頁面有4種,我們常用的有主頁面(primary surface)和離屏頁面(offscreen plain)先說主頁面,就是一塊顯存,在主頁面中的圖形會顯示到屏幕中,直接在主頁面上操作會有個問題,數據一多,圖象就會不連續,為此可以採用緩衝技術,即建立一個Back buffer(後台緩衝),說白了,就是在內存中再開闢一塊區域,和主頁面的區域對應,這樣就可以不直接操作主頁面,先把數據寫入到這裡,然後通過換頁成為可見。離屏頁面不同了,它是和主頁面一模一樣的畫面,但是它永遠不在屏幕上表現出來,通常被用來存儲位圖,用于將後來的位圖圖象Blit到主頁面或後台緩衝上。那麼,我們來看一下這幾個頁面在工作當中的位置及作用:

(此處有一圖表,顯示不出來)

這樣,我們大體了解了頁面的作用,那麼初始化時就應該創建好,以等待到時對頁面的操作。于是我們的初始化函數中就應該再添加:

memset(&ddsd,0,sizeof(ddsd));
ddsd.dwSize=sizeof(ddsd);
//設置dwFlags,告訴DirectDraw哪些成員可用
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
//定義ddsCaps.dwCaps,請求一個帶後台緩衝的主頁面
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_COMPLEX | DDSCAPS_FLIP;
//定義設置後台緩衝的數量為1
ddsd.dwBackBufferCount = 1;
//創建主頁面
if (FAILED(lpDDraw7->CreateSurface(&ddsd, &lpDDprimary, NULL)))
{
    MessageBox(NULL,TEXT("DirectDraw Create primary Surface error!"),
    TEXT("Wrong!"),MB_OK);
    return(0);
}
//設置ddsCaps.dwCaps
ddsd.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER;
//連接主頁面及後台緩衝
if (FAILED(lpDDprimary->GetAttachedSurface(&ddsd.ddsCaps, &lpDDback)))
{
    MessageBox(NULL,TEXT("DirectDraw Create back Surface error!"),
    TEXT("Wrong!"),MB_OK);
    return(0);
}


在這裡,我們要定義幾個全局變量:
extern LPDIRECTSURFACE7 lpDDprimary;
extern LPDIRECTSURFACE7 lpDDback;
extern DDSURFACEDESC2 ddsd;


這是一個指向主頁面的指針,一個指向後台緩衝的指針,和一個頁面描述結構。不用說,這些定義你可以放在MyDirectDraw.h中。通過填充ddsd結構的成員來申明你所想創建的頁面的類型。這裡我們沒創建離屏頁面。用主頁面及後台緩衝可以完成一些相對簡單,數據不是很多的圖形顯示,數據過于復雜,就應該創建離屏頁面了。

相應的,在結束時,除了釋放DirectDraw7接口外,還要依次釋放後台緩衝指針和主頁面指針。還是提醒一下,先創建的一定要後釋放,不然你會死的很難堪的。怎麼去Release這些東西,看看code1中的代碼,很容易明白的。

順便我們看一下如何創建離屏頁面,看下面代碼:
DDSURFACEDESC2 ddsd;
LPDIRECTSURFACE7 lpDDopl; //這兩個定義不用說了吧
memset(&ddsd,0,sizeof(ddsd)); //清空結構內容
ddsd.dwSize=sizeof(ddsd); //設置大小
ddsd.dwFlags = DDSD_CAPS |DDSD_HEIGHT|DDSD_WIDTH;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;//指定頁面類型
ddsd.dwWidth=600;
ddsd.dwHeight=800; //設置離屏頁面大小
if (FAILED(lpDDraw7->CreateSurface(&ddsd, &lpDDopl, NULL)))
{
    MessageBox(NULL,TEXT("DirectDraw Create offscreen plain error!"),
    TEXT("Wrong!"),MB_OK);
    return(0);
} //創建離屏頁面


Okay!離屏頁面就創建好了,說一下,因為離屏頁面是個獨立的頁面,不隸屬于任何其他頁面,所以你必須指定它的大小。

關于頁面的創建我們就說到這,到這兒,是不是有一種萬事具備,只欠東風的感覺啊?

抬頭一看,天亮了,該睡覺了,睡醒咱們再接著說,先去呼呼了。

……n小時後……

好了,既然只欠東風,我們就來說東風。

簡單的畫圖,我們可以參看code1(在屏幕上打點)

  有關頁面的運用的位圖的操作(作圖也就這兩個東西)我還組織不起來,無法把理解到的東西組織到程序中(汗!還沒真正理解,就好意思在這說)我也在學嘛,多理解幾遍,說不定就能夠組織了,那麼,那麼,我們只能像我看過的幾本資料一樣,來拆開來說了,開始照單全收的抄書。希望抄完後,能有點組織的眉目。

  從以前那個頁面表,可以看出,這裡的操作無非是載入位圖,貼圖,翻頁顯示,以及對畫面進行剪貼。那我們一步步來說吧。這裡就事論事,就模塊論模塊,代碼段和以前的文件沒多大關聯了。大家看不明白了不要罵我,理解萬歲。

先看載入位圖,即將位圖load到離屏頁面中,要通過windows的HDC來進行存取,用windowsAPI配合DirectX來完成。我們看代碼段:

HDC hdc,hdc1; //聲明HDC對象,hdc用來存儲位圖,hdc1代表離屏頁面的DC
HBITMAP bitmap; //聲明HBITMAP對象
hdc=::CreateCompatatibleDC(NULL);//建立與目前顯示模式兼容的DC(參數為null)
bitmap=(HBITMAP)::LoadImage(NULL,”bgroud.bmp”,IMAGE_BITMAP,640,480,LR_LOADFROMFILE);
//加載640*480的位圖
::SelectObject(hdc,bitmap); //使用windows函數設置hdc中的內容為bitmap


現在把位圖加載到了DC中,下面就要把DC中的位圖貼到離屏頁面中了

LPDIRECTSURFACE7 lpDDopl; //這個定義不用說了吧
HRESULT result;//幹嘛用的?往下看
lpDDopl->GetSurfaceDesc(&ddsd);//ddsd和我們前面定義過的一樣
result= lpDDopl->GetDC(&hdc1);//用GetDC()來取得離屏頁面的DC
if(result!=DD_OK)
    MessageBox(“取得暫存區DC失敗”);//是否取得成功,了解result做這個用
::Bitblt(hdc1,0,0,ddsd.dwWidth,ddsd.dwHeight,hdc,0,0,SRCCOPY);
//這個就是貼圖用的windows函數
lpDDopl->releaseDC(hdc1);//釋放離屏頁面的DC,一定要釋放


到此我們已經把位圖貼到離屏頁面中了,下面應該把離屏頁面DC中的位圖填充到back buffer中,然後通過換頁顯示出來。先來了解兩個DirectDraw的貼圖函數Blt和BltFast。這兩個函數的原型在老王翻譯的directx開發手冊中有詳細說明,在我主頁上可以down到,你可以查閱一下。這裡我簡單說一下:

HRESULT Blt( LPRECT lpDestRect, //目標頁面的區域,lpDestRect定義其左上右下點坐標
LPDIRECTDRAWSURFACE7 lpDDSrcSurface,//源頁面指針
LPRECT lpSrcRect, //源頁面的區域
DWORD dwFlags,//控制標志,詳見老王的手冊
LPDDBLTFX lpDDBltFx)//圖形變換的信息結構,詳情請自己查閱
HRESULT BltFast( DWORD dwX, //目的區域左上x坐標
DWORD dwY, //目的區域左上y坐標
LPDIRECTDRAWSURFACE7 lpDDSrcSurface,//源頁面指針
LPRECT lpSrcRect, //源頁面的區域
DWORD dwTrans,//轉換參數,見老王手冊


這兩者的差別就是Blt多了圖形放縮功能,但是BltFast效率較高,如何選用已經很清楚了。調用這兩個函數中的一個就能夠實現從離屏頁面到back buffer的貼圖,代碼如下:

lpDDback->BltFast(0,0,lpDDopl,CRect(0,0,640,480),DDBLTFAST_WAIT);

//lpDDback是我們以前聲明過的後台緩衝,CRect(…)是個CRect類的對象,如果我們已聲明了一個CRect rect;這裡就可用&rect來代替
單看貼圖這步操作,還是很easy的。
看起來好像離顯示只有一步之遙了啊,right,只要翻頁(flip)一下就okay了

先看翻頁函數:
HRESULT Flip( LPDIRECTDRAWSURFACE7 lpDDDestSurface,//你想翻到的目標頁
DWORD dwFlags) //通常設為DDFLIP_WAIT


說明一下:第一個參數為null時,表示翻到目前頁面的所連接的下一個頁面。當換頁對象是可見的頁面,比如主頁面換頁鏈,進行換頁的Flip函數與系統CPU是異步執行的。這就是說,在這些可見的頁面上,調用Flip函數,它只是簡單的告訴顯示硬件該進行換頁了,並不需要等待換頁操作在硬件設備中實際完成後才返回。這是因為顯示硬件(顯示器)只有在完成一次垂直刷新後才能進行一次換頁。所以,Flip函數調用成功,並不意味著換頁已經完成,在實際的換頁操作進行之前,對即將成為主頁面的後台緩存是不能鎖定和進行Blit操作的。要讓Flip函數成為與系統CPU同步的操作,在調用時指定DDFLIP_WAIT標志即可

代碼同樣簡單:
lpDDprimary->Flip(NULL,DDFLIP_WAIT);

  小功告成,到這兒我們已經把一個指定的位圖bgroud.bmp在屏幕上顯示出來了,這個就可以作為你的遊戲的背景圖,比如潛水艇遊戲的那張大海圖。

需要說明一下的是,如果我們要在哪個頁面上操作(一般是back buffer),最好操作前先鎖定,用完再解鎖,防止其他GDI程序的幹擾。舉個例子:
lpDDback->Lock(NULL,&ddsd,DDLOCK-WAIT|DDLOCK_SURFACEMEMORYPTR, NULL); //鎖定後台緩衝
lpDDback->Unlock(NULL);//解鎖後台緩衝


把這兩句代碼分別添加到相應位置即可。

  再往下我們該做什麼了?背景有了,應該引進我們的精靈了(精靈這個術語真是可愛),然後想想看如何能讓我們的精靈動起來。老實說,到這,我快崩潰了。下面的內容應該屬于陌生的部分吧(如果前面的內容我還有點熟悉的話)。好在我這個人是很執著的,所以只有繼續硬著頭皮往下寫了,理解不到位的地方還請大家包涵,同時希望大家指教。

先做做準備工作,去吃飯先,休息一會再來…………

……又是n個小時……

有飯吃的日子真爽啊,珍惜吧,朋友們:)深吸一口氣,let’s go on

運用DirectDraw來做動畫,我們把不同的圖片載入到離屏頁面中,然後定時貼入到back buffer中,翻頁顯示就出現動畫效果了。來看個例程:

(待續) From:活著為了遊戲 ─ GameRes Blog

  發表評論 寫信給編輯 關閉窗口
同欄目內容
· 瑞典在網遊中開設虛擬大使館 2007-06-06
· 魔獸爭霸3秘籍(魔獸無敵秘籍) 2007-03-28
· 全屏模式 2006-09-04
· SDL系列教程15:精靈引擎2 2006-08-22
· 2D網絡遊戲:休閒遊戲的架構 2006-08-22
· 遊戲是怎樣鍊成的? 2006-08-22
相關內容
近期主推
西藏發現青藏高原最大冰川群
·西藏發現青藏高原最大冰川群
·中日: "月亮女神"攜手"嫦娥"
·行星大碰撞砸死恐龍催生人類
·美研制會飛汽車定價9萬美元
·美國教授設計出自適應機器人
·銀河系可能有20多個黑洞
近期熱門
英海域驚現大龍蝦為普通龍蝦5倍
·英海域驚現大龍蝦為普通龍蝦5倍
·新物種金蛙體表有劇毒
·我國嫦娥工程計劃測量月球
·精神病藥物可治多種癌症
·視覺震撼:來自海底的精靈
·能刪除不良記憶的藥物誕生
近期焦點
廣西發現罕見溶洞鐘乳石有30萬年
·廣西發現罕見溶洞鐘乳石有30萬年
·澳大利亞驚現“卡布基諾”海灘
·美國"彩色"瀑布夜幕下震撼遊人
·地球上究竟有多少個物種
·英國探險家北極遊泳破紀錄
·世界之最 世上最小的動物
金橋科普是一個公益性欄目,內容由作者提供或摘自互聯網,其目的是向廣大網民普及科學技術知識。如果您發現本網站轉載或摘編了您擁有著作權的作品,請通知我們(電話:023-63659911),我們立即刪除。
關于我們 | 網站地圖 | 用戶注冊 | 廣告客戶 | 招聘信息 | 業務信息
Copyright@2006-2009 JQInfo.com,WONDER. All Rights Reserved
重慶科技咨詢中心.重慶網得信息技術有限公司.版權所有
Email:webmaster@jqinfo.com
重慶數據通信局提供網絡帶寬. 渝ICP備05002327號