初入Ddraw

来源:互联网 发布:淘宝秋水伊人女装 编辑:程序博客网 时间:2024/04/29 20:32

可能并不是每个人都明白什么是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)

{/*Display mode was set*/}

else

{/*Display mode was not set*/}

很显然可以通过一个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的说明,即可提高英文水平又能比较全面地理解这些代码
原创粉丝点击