天天看點

初入Ddraw

可能并不是每個人都明白什麼是DirectDraw,其實它是DirectX的一部分。一看它的名字也大概猜到它是用來畫圖之類的。不管用來幹嘛,先看看著名的John De Goes 在他的名著《3D GAME PROGRAMMING WITH C++》裡對DirectX的定義。

DirectX is a software development kit (SDK) specifically to promote hardware assisted, high performance games for Windows, consists of many components...

The components of DirectX are divided into two groups: DirectX Foundation contains low level components that supply basic functionality; DirectX Media contains high level components that perform many complex operations.

這是他的原文,為了讓一些英文不太好的朋友不必浪費時間去翻詞典我将其翻譯一下,這沒有任何商業目的,是以請别告我侵權。

DirectX是一個開發軟體特别用于提高硬體協助能力以支援高品質的Windows遊戲。

DirectX有兩組元件:DirectX基本庫,包含低級元件用于基本功能;DirectX媒體,包括進階元件用于完成許多複雜的操作。

以上文字好象有點隐晦難懂。其實我對上面的英文的了解也就相當于上面這段中文的水準。雖然John De Goes的這本書被公認為3D遊戲的教材,而且我的偶像Andre LaMothe還特地為這本書寫了序但我在看這本800頁的英文書時常常被許多類似于上面的文字搞的頭昏腦脹。

講了這麼多你可能還是沒明白我到底要說些什麼,不要着急,所謂心急吃不了熱豆腐,特别是美女的豆腐。再看看下面的文字。

DirectX Foundation consists of the following components: DirectDraw, DirectSound, DirectMusic, Direct3D Immediate Mode (IM), DirectInput, DirectSetup, and AutoPlay.

DirectDraw provides a way for software developers to access the display's attributes - including the size of the desktop and how many colors can be displayed at once - and the display's features, such as playing back video, displaying images and bitmaps, and so on. DirectDraw is not graphics library, in that it cannot draw lines, polygons, or text, but it is still nonetheless for today's 3D games.

這段比較好了解,我也照着翻譯:

DirectX 基本庫包括DirectDraw, DirectSound, DirectMusic......(參見英文)

DirectDraw 提供了一個方法給軟體開發人員進入顯示的屬性——包括桌面的大小和能被顯示的顔色數——顯示特征,列如播放背景視訊,顯示圖象和位圖,等等。DirectDraw不是圖形函數庫,也就是說它不能畫線,多邊形,或文字,但是它并非對今天的3D遊戲毫無用處。

天啊!為什麼譯成中文後反而看不太明白了!看來我還是不适合當翻譯。是以大家将就點,反正就是那個意思,用一句教我軟體工程的老師Mr. Christopher常挂在嘴上的話“你明白就是明白,不明白就是不明白”

哈,如果真不明白的話,就忘掉上面所有的廢話(forget these bullshit)。這完全沒有關系,我保證如果你看完本文後能從地獄爬回來而且能解釋的比我更好的話,你就可以毫不客氣地揍我一頓。

從程式員的角度來說,DirectDraw是一個很複雜的類。不必管它到底是什麼,反正你如果要使用DirectDraw, 首先必須建立一個DirectDraw的對象(object)。也就是說聲明一個DirectDraw變量。用下面的代碼:

LPDIRECTDRAW lpDD; //LPDIRECTDRAW 就是微軟寫的DirectDraw類的指針類型。

有了這個變量後就可以對DirectDraw進行初始化:

HRESULT Result;

Result = DirectDrawCreate( NULL, &lpDD, NULL);

DirectDrawCreate函數用來初始化DirectDraw的對象(object),這個函數有三個參數,它的原型如下:

HRESULT WINAPI DirectDrawCreate(

GUID FAR *lpGUID,

LPDIRECTDRAW FAR *lplpDD,

IUnknown FAR *pUnkOuter);

lpGUID:一個指向GUID(Globally Unique Identifier)的位址變量,它描述了一個用于在其上面建立DirectDraw的驅動器。如果這個變量為NULL那麼就是說使用目前的顯示驅動程式。

lplpDD:就是上面的lpDD的指針。

pUnkOuter:微軟說這是用于将來對COM的相容而設定的參數,但微軟又說如果這個變量不為NULL的話,将傳回一個錯誤。是以如果你不是反微軟聯盟的成員的話就給它NULL吧。

如果初始化成功的話函數傳回的值為DD_OK。

初始化成功後就可以設定Cooperative Level(應該是合作層之類的意思吧,反正就是指定遊戲是運作于視窗模式還是令人激動的全屏模式)。

調用SetCooperativeLevel函數:

Result = lpDD->SetCooperativeLevel(

GetActiveWindow(),

DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN | DDSCL_ALLOWREBOOT);

此函數的原型為:

HRESULT SetCooperativeLevel(

HWND hwnd,

DWORD dwFlags);

參數說明:

hwnd:要設定視窗的句柄。

dwFlags:這包括很多莫名其妙卻非常有用的标志。最常用的當然是DDSCL_FULLSCREEN和DDSCL_NORMAL分别指定全屏模式或普通視窗模式。DDSCL_EXCLUSIVE表示該視窗獨占所有操作。DDSCL_ALLOWREBOOT當然就是指可以讓你重新開機電腦,就是不将Ctrl+Alt+Del給屏蔽掉。至于其他标志可參考MSDN,當然如果你有的話。

同樣,函數成功将傳回一個DD_OK。

如果将視窗模式設為全屏的話,可以設定顯示模式:

Result = lpDD->SetDisplayMode(800, 600, 32);

if(Result == DD_OK)

{}

else

{}

很顯然可以通過一個SetDisplayMode的函數來設定顯示模式,大家一眼就能明白上面的代碼将顯示模式設成800*600顔色數為32位。還是在看看該函數的原型:

HRESULT SetDisplayMode(

DWORD dwWidth,

DWORD dwHeight,

DWORD dwBPP,

參數說明:

dwWidth:螢幕寬度。

dwHeight:螢幕高度。

dwBPP:顔色的位數。

好了,請注意,下面我要說的是DirectDraw的精華,建立一個首頁面。聽起來好象沒什麼了不起,但我們玩遊戲所看到的圖象正是畫在這個首頁面上的。它其實是一段顯示卡上的記憶體。DirectX允許我們直接向這段記憶體裡寫資料。這意味着顯示速度将會大大的提高,如果你開發一個射擊遊戲,那麼玩家就不必在發出子彈後要一邊泡咖啡,一邊等待子彈命中目标了。看下面的代碼:

LPDIRECTDRAWSURFACE lpDDPS; //聲明一個DirectDrawSurface的對象的指針。

DDSURFACEDESC ddsd;//建立一個DDSURFACEDESC對象。

ddsd.dwSize = sizeof(ddsd); //指定該結構所占的記憶體位元組數。

ddsd.dwFlag = DDSD_CAPS; //使ddsCaps成員有效。

ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; //指定為首頁面。

Result = lpDD->CreateSurface(&ddsd, &lpDDPS, NULL); //建立這個頁面

我敢肯定這是本文裡最讓人迷惑的代碼。但不必擔心,我會使出吃奶的力氣來将其解釋清楚,雖然我并不喜歡喝牛奶。

首先大家要明白一個概念,什麼是DirectDraw頁面?這其實就是一個頁面,類似于一張白紙,你可以在上面畫任何東西。在做遊戲時所有的圖象都是畫在不同的頁面上,将某一個頁面複制到首頁面去就能立即在螢幕上看到這些圖象。

我們來看看那個似乎比長城還長的類型名LPDIRECTDRAWSURFACE,它的意思是lp(long point)-DirectDraw-Surface,就是一個指向DirectDraw頁面的長指針。這裡聲明了一個變量lpDDPS我用該變量來代表我要建立的首頁面。記住這最好是個全局變量。

然後我還要聲明一個ddsd的變量,它的資料類型是DDSURFACEDESC。DDSURFACEDESC其實是DirectDraw Surface Description的縮寫。意即DirectDraw頁面描述。這是一個用于描述頁面的結構。我們定義了這個結構的對象ddsd後就可以對其進行填充,比如指定它為一個首頁面。

ddsd.dwSize就是ddsd所占的位元組數,記住在你使用DDSURFACEDESC之前一定要填充這個數。不過這是我所見過的最滑稽的指派運算。好比在說“我的身高等于用尺子量我的身高所得到的長度”!

ddsd.dwFlag是控制标志,這裡讓它等于DDSD_CAPS意思是讓ddsd結構裡的ddsCaps成員有效。而ddsCaps是DDSCAPS結構,它包含着一些頁面性能的描述。

ddsd.ddsCaps.dwCaps裡有許多進階和複雜的設定,比如是否為3D頁面,是否具備Alpha特性等等。但現在我們隻需要指定該頁面為首頁面。

最後用CreateSurface函數來建立這個頁面。

函數原型:

HRESULT CreateSurface(

LPDDSURFACEDESC lpDDSurfaceDesc,

LPDIRECTDRAWSURFACE7 FAR *lplpDDSurface,

IUnknown FAR *pUnkOuter);

參數說明:

lpDDSurfaceDesc:當然是DDSURFACEDESC的指針變量。

lplpDDSurface:一個指針變量指向那個頁面指針變量。

pUnkOuter:不用說又是微軟的陷阱,如果不想掉進去摔個粉身粹骨就放NULL吧。

好了終于完成了,現在你可以在螢幕上寫字了,或者調入一幅bmp的位圖。記得前面尊敬的John De Goes 說過DrectDraw并不能用來畫點或畫線,但實際上可以做到。我無意冒犯Geos先生,隻不過電腦實際上是個傻瓜而已,無論它多麼鐵面無私不講人情隻要一點點技巧往往就能瞞天過海。不過這不是我今天要透露的秘密。還是先寫兩個字上去吧。

HDC hDC;

if(lpDDSP->GetDC(&hDC) != DD_OK)

return FALSE;

SetBkColor( hDC, RGB( 0, 0, 255 ) );

SetTextColor( hDC, RGB( 255, 255, 0 ));

TextOut( hDC, 220, 200, "Yes, I did it!",14);

LPSTR lpMesg;

lpMesag = "Press Esc to exit";

TextOut( hDC, 280, 240, lpMesg, lstrlen(lpMesg));

lpDDSP->ReleaseDC(hDC);

HDC(Handle of Device Context)為一個裝置環境的句柄。這裡用來表示顯示裝置。

GetDC(&hDC)函數用來取得一個顯示裝置然後将這個裝置的句柄賦給hDC。這聽起來讓人糊塗,其實意思是說,用GetDC(&hDC)函數來建立一個同Windows GDI相容的裝置環境給指定頁面。用變量hDC來表示這個裝置環境。

SetBkColor和SetTextColor當然就是設定背景顔色和字元顔色了。它們和TextOut都是标準的Windows GDI函數,可以參考任何一本關于Windows GDI的書。其實一看便明白TextOut(hDC, 220, 200, "Yes, I did it!", 14)中,hDC當然就是前面說的顯示裝置,220肯定是X坐标,那麼200當然就是Y坐标了,"Yes, I did it!"意思是“我成功了!”,而14則表示這個字元串有14個字元。下面的LPSTR則是Win32的字元串類型,lstrlen(lpMesg)也是Windows的标準函數用來計算字元串的長度。最後别忘了删除掉這個裝置。

我好象快成長舌婦了,如果我再羅嗦下去的話,你可能會找月光寶盒逃命了。但悟空,為師不得不再說一句,千萬别忘了清除掉幾個全局變量,不然你可能每運作一次程式都得重新開機一次電腦,電腦這東西是寶物,重新開機會影響壽命,就算不影響壽命浪費能源也是不好的嘛......

清除變量:

if(lpDD != NULL)

{

if(lpDDPS != NULL)

lpDDPS->Release();

lpDDPS = NULL;

}

lpDD->Release();

lpDD = NULL;

}

将上面的代碼整理放入上次的最簡單的Windows程式就能可以運作了。但記得在VC的Project/Settings裡将ddraw.lib加入連接配接庫裡。

下面是完整的程式:

#include <windows.h>

#include <ddraw.h>

LPDIRECTDRAW lpDD;

LPDIRECTDRAWSURFACE lpDDPS;

HWND InitWindow(HINSTANCE hInstance, int nShowCmd);

LRESULT CALLBACK WindowProc(HWND hWindow, UINT message, WPARAM wParam,

LPARAM lParam);

bool InitDDraw(HWND hWindow);

void FreeDDraw();

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nShowCmd)

{

MSG msg;

HWND hWindow;

hWindow = InitWindow(hInstance, nShowCmd);

if(hWindow == FALSE)

{

DestroyWindow(hWindow);

return false;

}

if(InitDDraw(hWindow) == false)

{

MessageBox(hWindow, "Error","!" ,MB_OK);

return false;

}

while(GetMessage(&msg, NULL, 0, 0) != 0)

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return msg.wParam;

}

HWND InitWindow(HINSTANCE hInstance, int nShowCmd)

{

HWND hWindow;

WNDCLASS WindowClass;

WindowClass.hIcon = LoadIcon(hInstance, IDI_APPLICATION);

WindowClass.hCursor = LoadCursor(NULL, IDC_ARROW);

WindowClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);

WindowClass.style = CS_HREDRAW | CS_VREDRAW;

WindowClass.lpfnWndProc = WindowProc;

WindowClass.cbClsExtra = 0;

WindowClass.cbWndExtra = 0;

WindowClass.hInstance = hInstance;

WindowClass.lpszMenuName = "ClassName";

WindowClass.lpszClassName = "ClassName";

RegisterClass(&WindowClass);

hWindow = CreateWindowEx(0, "ClassName", "WindowTitle",

//WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_SYSMENU,

WS_POPUP,

(GetSystemMetrics(SM_CXFULLSCREEN) - 640)/2,

(GetSystemMetrics(SM_CYFULLSCREEN) - 480)/2,

640, 480,

NULL, NULL, hInstance, NULL);

if(!hWindow)

return FALSE;

ShowWindow(hWindow, nShowCmd);

UpdateWindow(hWindow);

return hWindow;

}

bool InitDDraw(HWND hWindow)

{

DDSURFACEDESC ddsd;

HDC hdc;

LPSTR lpMesg;

if(DirectDrawCreate(NULL, &lpDD, NULL) != DD_OK)

return false;

if(lpDD->SetCooperativeLevel(hWindow,

DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN | DDSCL_ALLOWREBOOT) != DD_OK)

return false;

if ( lpDD->SetDisplayMode(800, 600, 32) != DD_OK)

return false;

ddsd.dwSize = sizeof(ddsd);

ddsd.dwFlags = DDSD_CAPS;

ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

if(lpDD->CreateSurface(&ddsd, &lpDDPS, NULL) != DD_OK)

return false;

if(lpDDPS->GetDC(&hdc) != DD_OK)

return false;

SetBkColor(hdc, RGB(0, 0, 255));

SetTextColor(hdc, RGB(255, 255, 0 ));

TextOut(hdc, 220, 200, "Yeh, I did it!", 14);

lpMesg = "Press ESC to exit";

TextOut( hdc, 280, 240, lpMesg, lstrlen(lpMesg));

lpDDPS->ReleaseDC(hdc);

return true;

}

void FreeDDraw()

{

if(lpDD != NULL)

{

if(lpDDPS != NULL)

{

lpDDPS->Release();

lpDDPS = NULL;

}

lpDD->Release();

lpDD = NULL;

}

}

LRESULT CALLBACK WindowProc(HWND hWindow, UINT message, WPARAM wParam, LPARAM lParam)

{

switch(message)

{

case WM_KEYDOWN:

switch(wParam)

{

case VK_ESCAPE:

PostMessage(hWindow, WM_CLOSE, 0, 0);

break;

}

break;

case WM_DESTROY:

FreeDDraw();

PostQuitMessage(0);

break;

}

return DefWindowProc(hWindow, message, wParam, lParam);

}

這個程式将那個史上最愚蠢的Windows程式更新為在DirectX下的全屏模式下顯示兩句傻兮兮的英文。我相信這足以讓很多有志于做專業的遊戲卻又不知如何入手的人高興上三天三夜。因為進入遊戲程式設計的神秘殿堂似乎就在那漆黑的前方。雖然還不知道前面有多少艱苦的路要走,但起碼我們已經找到了正确的方向。

我仍然強烈建議你仔細地閱讀MSDN的說明,即可提高英文水準又能比較全面地了解這些代碼

繼續閱讀