DirectX學習手記(-) (轉)

worldblog發表於2007-12-14
DirectX學習手記(-) (轉)[@more@]

學習手記(-) HappyFire 2002/8/2 題記:玩了很多的遊戲,不禁萌發了自己做遊戲的念頭,於是7月份從網上收集了N多資料。7月20幾號在家開始了閉關式的學習,一直到昨天,我的第一個地圖類封裝完畢,並乘勝追擊到凌晨3點,做好了地圖編輯器的0.9版。早上起來覺得有點累(才睡了幾個小時,呵呵),於是把這些天的學習經歷回憶一下,權當是休息。這個過程是一個從對遊戲一無所知到略識其理得過程,我想對於像我這樣的初學者應該有所幫助吧,至少可以少走些彎路。 一. 初識DirectX 在放假之前,我拜讀了金點時空softboy的《聖劍英雄傳--英雄救美製作手札》一文。此文簡述了RPG遊戲的基本原理,通俗易懂,看完此文使人覺得遊戲製作並非遙不可及,強烈推薦!但此文並沒有提到有關DirectX程式設計的方法。所以做遊戲,還要先過DirectX關。於是我花了一天的時間,通讀了老王翻譯的《DirectX中文手冊》,並分析了幾個例程,在加上一個多星期的程式設計經歷,總算有了一點感覺。 1.基礎中的基礎 既然我們討論的是平臺下的遊戲程式設計,當然要對Windows程式設計有所瞭解。我們使用的是 SDK()程式設計。至於為什麼不用MFC? MFC是對API的封裝(其實何止是封裝),它適用於開發有統一架構和介面的大型商業,但其複雜的機制影響了速度,這對於遊戲程式設計是難以忍受的,因此基於C/C++的Win32API是我們必然的選擇。(也聽說有用VB開發的遊戲,但那畢竟不是主流)。那麼對於Win32 API要掌握到什麼程度呢?我想只要知道WinMain、視窗類、訊息迴圈、視窗回撥,能編個簡單的HelloWorld就行了,至於GDI 只要稍作了解,而Windows的基本上用不到。這方面的文章很多,找幾篇看看就行了。當然遊戲程式設計不同於一般的程式設計,所以這其中也有些變化,在下面我會提到的。 2.第一個DirectDraw例程 學DirectX當然要先學習它最基本也是最重要的部分DirectDraw。DirectDraw是微軟提供的Windows平臺下高效的與無關的圖形引擎,它提供對視訊記憶體的直接訪問...好了,閒話少說,來看看我的第一個例程(這是在手冊上的第一個例程的基礎上修改而成的): //----------------------------------- //工程:helloDX //:helloDX.cpp //內容:第一個DirectDraw應用程式 //----------------------------------- #include #include #include //全域性變數 LPDIRECTDRAW lpDD; //DirectDraw LPDIRECTDRAWSURFACE lpDDSPrimary ; //DirectDraw主表面 LPDIRECTDRAWSURFACE lpDDSBack ; //後臺緩衝表面 char szMsg1[]="我的第一個DirectDraw程式" ; char szMsg2[]="按ESC退出" ; BOOL bActive = TRUE ; HWND hwnd ; //函式宣告 LRESULT CALLBACK WinProc ( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) ; BOOL InitWindow ( HINSTANCE hInstance, int nCmdShow ) ; BOOL InitDDraw ( void ) ; //初始化DirectX void FreeDDraw ( void ) ; //釋放DirectX物件 void MainL ( void ) ; //遊戲主迴圈 //------------------------------------------------------- //函式:WinMain() //功能:Win32應用程式入口函式.進行初始化工作,處理訊息迴圈 //------------------------------------------------------- int WINAPI WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { MSG msg ; //初始化主視窗 if ( !InitWindow(hInstance,nCmdShow) ) return FALSE ; //初始化DirectDraw環境,並實現DirectDraw功能 if ( !InitDDraw() ){ MessageBox ( GetActiveWindow(), "初始化DirectDraw過程中出錯!", "Error", MB_OK ) ; FreeDDraw () ; DestroyWindow ( GetActiveWindow() ) ; return FALSE ; } 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(bActive){ //如果程式處於啟用狀態,進入遊戲主迴圈 MainLoop(); } //等待訊息 else WaitMessage(); } return msg.wParam ; } //-------------------------------------- //函式:InitWindow() //功能:建立主視窗 //-------------------------------------- static BOOL InitWindow ( HINSTANCE hInstance, int nCmdShow ) { // HWND hwnd ; //視窗控制程式碼 WNDCLASS wc ; //視窗類結構 //填充視窗類結構 wc.style = 0 ; wc.lpfnWndProc = WinProc ; wc.cbClsExtra = 0 ; wc.cbWndExtra = 0 ; wc.hInstance = hInstance ; wc.hIcon = LoadIcon ( hInstance, IDI_APPLICATION ) ; wc.hCursor = LoadCursor ( NULL, IDC_ARROW ) ; wc.hbrBackground = (HBRUSH)GetStock(BLACK_BRUSH) ; wc.lpszMenuName = NULL ; wc.lpszClassName = "dxHello" ; //註冊視窗類 RegisterClass ( &wc ) ; //建立主視窗 hwnd = CreateWindowEx ( 0, "dxHello", "", WS_POPUP, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), NULL, NULL, hInstance, NULL ) ; if ( !hwnd ) return FALSE ; ShowWindow ( hwnd, nCmdShow ) ; UpdateWindow ( hwnd ) ; return TRUE ; } //-------------------------------------------------- //函式:WinProc() //功能:處理主視窗訊息 //-------------------------------------------------- LRESULT CALLBACK WinProc ( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) { switch ( message ){ case WM_ACTIVATEAPP: bActive = wParam; break; case WM_KEYDOWN: //擊鍵訊息 switch ( wParam ){ case VK_ESCAPE: PostMessage ( hWnd, WM_CLOSE, 0, 0 ) ; break ; } break ; case WM_SETCURSOR: SetCursor ( NULL ) ; return TRUE ; case WM_DESTROY: //退出訊息 FreeDDraw () ; PostQuitMessage ( 0 ) ; break ; } //預設訊息處理過程 return DefWindowProc ( hWnd, message, wParam, lParam ) ; } //---------------------------------------------------------------- //函式: InitDDraw() //功能: 初始化DirectDraw環境並實現其功能.包括:建立DirectDraw物件, //設定顯示,建立主表面 //---------------------------------------------------------------- BOOL InitDDraw (void) { DDSURFACEDESC ddsd ; //表面描述 DDSCAPS ddscaps ; // HDC hdc ; //裝置環境控制程式碼 //建立DirectDraw物件 if ( DirectDrawCreate(NULL,&lpDD,NULL)!=DD_OK ) return FALSE ; //取得獨佔和全屏模式 if ( lpDD->SetCooperativeLevel(GetActiveWindow(),DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN)!=DD_OK ) return FALSE ; //設定顯示模式 if ( lpDD->SetDisplayMode(640,480,8)!=DD_OK) return FALSE ; //填充主表面資訊 ddsd.dwSize = sizeof(ddsd) ; ddsd.dwFlags = DDSD_CAPS|DDSD_BACKBUFFERCOUNT ; ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE|DDSCAPS_FLIP|DDSCAPS_COMPLEX ; ddsd.dwBackBufferCount = 1 ; //建立主表面物件 if ( lpDD->CreateSurface(&ddsd,&lpDDSPrimary,NULL)!=DD_OK ) return FALSE ; //提取後臺快取表面指標 ddscaps.dwCaps = DDSCAPS_BACKBUFFER ; if ( lpDDSPrimary->GetAttachedSurface ( &ddscaps, &lpDDSBack )!=DD_OK ) return FALSE ; return TRUE ; } //------------------------------------------------------- //函式:FreeDDraw() //功能:釋放所有的DirectDraw物件 //------------------------------------------------------- void FreeDDraw(void) { if ( lpDD!=NULL ){ if ( lpDDSPrimary!=NULL ){ lpDDSPrimary->Release() ; lpDDSPrimary = NULL ; } lpDD->Release() ; lpDD = NULL ; } } //------------------------------------------------------- //函式:MainLoop() //功能:遊戲主迴圈 //------------------------------------------------------- void MainLoop (void) { static int i = 0; HDC hdc ; //後臺緩衝表面上的操作 if ( lpDDSBack->GetDC(&hdc)==DD_OK ){ SetBkColor ( hdc, RGB(0+i,255-i,0+i) ) ; SetTextColor ( hdc, RGB( 0+i,0+i,255-i) ) ; TextOut ( hdc, 220, 200, szMsg1, lstrlen(szMsg1) ) ; TextOut ( hdc, 280, 220, szMsg2, lstrlen(szMsg2) ) ; lpDDSBack->ReleaseDC (hdc) ; i+=10 ; if ( i>255) i=0 ; } if (lpDDSPrimary->Flip(NULL,0)!=DD_OK){ //一經Flip,兩個表面的指標互換!lpDDSPrimary指向後臺表面,所以 FreeDDraw() ; //你就看到剛才在後臺表面上寫的字了,而lpDDSBack指向了原來的前臺主表面 PostQuitMessage(0); //把它掉到後臺進行操作 } } 3.例程詳解 初次看這個程式,可能會覺得有點複雜,先讓我們看看這個程式能做什麼。在VC++6.0中建一個名helloDX 的Win32 Application工程,新建一個空白的cpp檔案helloDX.cpp,把這斷程式碼貼上上去。先別急著執行,還要把DirectDraw庫新增到工程中。方法是在選單project中選Setting,開啟project setting對話方塊,在右面的link選擇卡里面的物件/庫模組裡新增ddraw.lib,確定就行了。下面執行程式,你看到了什麼? 我們先分析一下這個程式的結構,首先它是一個典型的Win32 API程式,有WinMain和WinProc兩大塊組成。但稍微有點不同的是,它對於訊息迴圈的處理採用了Peek方式: 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(bActive){ //如果程式處於啟用狀態,進入遊戲主迴圈 MainLoop(); } //等待訊息 else WaitMessage(); } 函式PeekMessage判斷訊息佇列中是否有訊息,如果有就處理訊息,如果沒有再判斷程式是否啟用,如果啟用就進入遊戲主迴圈,如果沒有啟用就什麼都不做,等待訊息產生。這個迴圈不斷進行下去,直到視窗收到WM_QUIT訊息。在進入這個迴圈之前,我們初始化了視窗(註冊視窗類,建立視窗),注意在建立視窗時我們指定視窗樣式為WS_POPUP,這樣就不會出現視窗邊框標題欄什麼的了。而且視窗的大小指定為顯示器螢幕的大小,因為我們要實現的是全屏的DirectDraw程式設計。初始化視窗之後我們初始化了DirectDraw環境並建立了DirectDraw物件(下面將詳細說明),這樣程式就可以使用DirectDraw進行圖形輸出了。 下面介紹我們的主角DirectDraw。你一定注意到了程式開頭的全域性變數宣告: LPDIRECTDRAW lpDD; //DirectDraw物件 LPDIRECTDRAWSURFACE lpDDSPrimary ; //DirectDraw主表面 LPDIRECTDRAWSURFACE lpDDSBack ; //後臺緩衝表面這個LPDIRECTDRAW就是DirectDraw物件的型別了(我們姑且把它作為一個型別好了,其實它是一個COM介面,這個我也不怎麼懂:)),程式中至少有一個LPDIRECTDRAW型別的物件,它是建立其他物件的基礎,兩個LPDIRECTDRAWSURFACE型別的物件被稱為是DirectDraw表面。lpDD對應著你的,lpDDSPrimary,lpDDSBack這樣的物件對應著視訊記憶體(或主存)中的一塊區域。建立DirectDraw表面的時候可以指定它的型別,這裡的lpDDSPrimary是主表面,也是前臺你能看到的表面(它被顯示到螢幕上),lpDDSBack是後臺緩衝表面,它是附帶於lpDDSPrimary的。當然也可以建立不帶後臺緩衝表面的主表面(手冊的第一個例程就是),也可以建立帶多個後臺緩衝的主表面。在這裡我介紹最最常用的帶一個緩衝的主表面。建立主表面之前,我們必須先建立DirectDraw物件,就是這裡的lpDD,請看InitDDraw函式的內容。函式DirectDrawCreate(NULL, &lpDD,NULL)建立了一個LPDIRECTDRAW物件,這個函式的具體用法可以查相關資料我就不在贅述了。建立lpDD後還要設定DirectDraw的環境,具體就是這裡的取得獨佔和全屏模式和設定顯示模式,注意SetDisplayMode的第三個引數8不是顏色8色,而是色深度為8位,即256色。然後是建立主表面,先要填寫一個DDSURFACEDESC表面描述結構體, ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE|DDSCAPS_FLIP|DDSCAPS_COMPLEX 指出要建立的表面是主表面,且能夠filp(後面再說),是帶緩衝的複雜表面; ddsd.dwBackBufferCount = 1 指明緩衝表面數為1。一般都是這樣填的,具體的可以查資料。填好後就可以透過lpDD 的CreateSurface方法建立主表面了。然後是後臺表面...這裡的程式碼怎麼有點不同?我們不是透過填寫 DDSURFACEDESC結構體,在透過 CreateSurface建立後臺表面的!因為後臺緩衝表面是同主表面聯絡在一起的,主表面建立好了它就自動建立好了,我們要作的是獲取它的指標以便去應用它!使用lpDDSPrimary->GetAttachedSurface,明白這裡的AttachedSurface了吧! 至此所有的表面建立完畢,可以用來圖形操作了。(順便提一下,還有一種表面叫離屏表面,它上面的東西不會被直接顯示到螢幕上,它的作用是存放影像,然後把影像複製到後臺表面上,即Blt,呵呵,以後再說...) 我們在什麼地方用DirectDraw呢?當然是在遊戲模組中,在這個程式中是MainLoop(MainLoop每個遊戲都有,這是最簡單的了)。在MainLoop 裡面我們先是獲取後臺表面的裝置環境控制程式碼,然後用Windows的GDI寫了幾個字。再然後運用主表面的Flip方法,把前後臺表面的指標互換,這樣你就看到剛才寫的字了。由於MainLoop是不斷迴圈的,而且我們透過靜態變數i改變了字元的顏色,因此你看到的就是不斷變色的字元了。關於Flip一定要理解,其實就是指標互換,因此非常快!經過Flip,lpDDSPrimary指向後臺表面,所以你就看到剛才在後臺表面上寫的字了,而lpDDSBack指向了原來的前臺主表面把它掉到後臺進行操作(換個顏色在寫一遍),然後再Flip回去,你再看到的字顏色就變了。明白了吧? 再多說幾句,我們設定了lpDD為全屏模式,只有在這種模式下才能使用Flip,如果是視窗模式就只能Blt(Blt就是複製啊)了。 在程式結束之前不要忘了釋放DirectDraw物件和表面物件,這裡放在FreeDDraw()裡面的,注意我沒有釋放後臺表面,因為它隨著主表面釋放了。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-993088/,如需轉載,請註明出處,否則將追究法律責任。

相關文章