通向DirectDraw之捷径

    技术2022-05-11  77

    这篇文章描述了怎样通过使用DirectX SDK中的通用库文件来轻松地建立一个 DirectDraw对象及其显示表面(surface)。(这篇文章)对那些想要在不破坏原应用程序架构的情况下快速掌握它来做些事的人特别有帮助,请注意事实上这些类中抽象了相当多且复杂的事物,因此我强烈地推荐你在掌握它们功能的同时,尽量关注一下其底层的实现,这样有助于你尽快掌握它们的工作方式。 1.DirectDraw的安装  在本文中,我假定你拥有微软公司的 Visual C++ , 和 DirectX 8.1 SDK。如果没有,就快去准备一份吧。 首先,启动你的 Visual C++, 创建一个新的 Win32 应用程序工程。 然后进入 你 DirectX SDK 文件夹中的 multimedia/common/include 目录 , 拷贝 dxutil.h 和 ddutil.h 至你新建工程的目录下。 然后将你 DirectX SDK文件夹中的 /multimedia/common/src目录下的dxutil.cpp 和 ddutil.cpp 也拷贝至你新建工程的目录下。把四个文件加入你的工程, 然后连接上下列库文件: dxguid.lib ,ddraw.lib,winmm.lib。现在,你创建一个新的 C++源文件文件, 而且也把它加入你的工程。 这些是整个教程中我们将会用到的工作文件。 2.DirectDraw程序代码 既然我们已经准备好了, 让我们开始写一些代码什么的实在的东西吧! #define WIN32_LEAN_AND_MEAN #include #include "dxutil.h" #include "ddutil.h" 这是标准的代码。#define WIN32_LEAN_AND_MEAN,这句的目的是指示编译器不要包含与MFC相关的操作。( 只是一个好的练习——如果你不在使用 MFC) 然后我们包括 dxutil.h 和 ddutil.h,这是两个很有用的头文件。 他们能够使你以一种比通常的DirectX编程更轻松的方式来工作。 //globals bool g_bActive = false; CDisplay *g_pDisplay = NULL; CSurface *g_pText = NULL; //function prototypes bool InitDD(HWND); void CleanUp(); void GameLoop(); 代码自身说的很清楚,不是吗?我们的第一个全局变量,g_bActive,是一个让我们的应用程序知道是否应该运行游戏的标志。 如果我们没有这个全局变量, 我们的应用程序可能在它自己被注销之後还在尝试着在我们已经创建的 DirectDraw 的表面上画点儿什么呢!虽然这通常是一个在程序结束的时候出现的一个不大的问题,但它会导致一个非法操作的错误,我们并不想那样,不是吗? g_pDisplay 是我们的显示对象。CDisplay 在 ddutil.h 中是最主要的类。它控制着我们前后的缓冲区,并提供一些针对该缓冲区的功能,如访问及存储缓冲区,将表面写入缓冲区,创建一个表面,等等。 g_pText 是一个文本表面。 我们将会在这个表面 (你或许已经有所理解 ) 之上写文本, 然后将它传送到我们的屏幕之上。 注意它们两者都是指向对象的指针, 而且被初始化为NULL。 现在来看看函数的原型。 InitDD() 只是用来初始化 DirectDraw 。 多亏了 DirectDraw 的通用文件(common files),这还算是一个简单的程序。( 但是晚些时候我们才会接触到它们) CleanUp()调用了对象 g_pDisplay 的析构函数来释放 ( release)我们所创建的DirectDraw对象及其表面。很明显, GameLoop() 则是将来用来放你的游戏的地方。 LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch(uMsg) { case WM_CREATE: InitDD(hWnd); g_bActive=true; break; case WM_CLOSE: g_bActive=false; CleanUp(); DestroyWindow(hWnd); break; case WM_DESTROY: PostQuitMessage(0); break; case WM_MOVE: g_pDisplay->UpdateBounds(); break; case WM_SIZE: g_pDisplay->UpdateBounds(); break; default: return DefWindowProc(hWnd,uMsg,wParam,lParam); break; } return 0; } 这里是标准的 Windows 程序消息处理机制的代码。 当产生 WM_CREATE 消息时,我们初始化 DirectDraw, 并将我们的全局变量 g_bActive 设定为true,这样就可以执行游戏循环 GameLoop() 了。当消息 WM_CLOSE 被传递的时候,我们就把全局变量 g_bActive 设定为 false ( 这样我们的应用程序就不会在它自己被注销之後还在尝试着向我们已经创建的 DirectDraw 的表面上写数据了)。然后调用 CleanUp() 函数,最终注销我们的窗口程序。处理 WM_MOVE 和 WM_SIZE 事件非常重要,因为若处理不当,DirectDraw 将不顾窗口本身是否已经被移动或重新设定其大小,仍然在屏幕上原来的位置上继续作画,从而造成错误。 bool InitDD(HWND hWnd) { //dd init code g_pDisplay = new CDisplay(); if(FAILED(g_pDisplay->CreateWindowedDisplay(hWnd,640,480))) { MessageBox(NULL,"Failed to Initialize DirectDraw", "DirectDraw Initialization Failure",MB_OK|MB_ICONERROR); return false; } return true; } 该死的 InitDD() 函数... 不过别急,它只不过几行而已! 这儿就是通常那些个库文件的用武之地。现在那些妨碍我们建立 DirectDraw 对象的冗长的玩意儿已被我们轻松搞定了,而你将会再次注意到它挺麻烦的,不是吗?如果你真的不想弄明白那些惹人烦的玩意儿,那么至少你得知道个梗概吧!如果你得回去改变协作等级什么的,它也许帮得上忙。注意到这是一个返回 bool 值的函数, 因此如果你不厌倦的话, 你应当做一下错误检查。(基于你所可以理解的篇幅问题之原因,我决定在这篇文章中省略) void CleanUp() { SAFE_DELETE(g_pDisplay); } 真是够简单的! 这个函数调用了在 dxutil.h 中定义的宏 SAFE_DELETE 来删除我们的显示对象,同时调用析构函数。 void MainLoop() { g_pDisplay->CreateSurfaceFromText(&g_pText,NULL,"DDraw using Common Files", RGB(0,0,0),RGB(0,255,0)); g_pDisplay->Clear(0); g_pDisplay->Blt(0,0,g_pText,0); g_pDisplay->Present(); g_pText->Destroy(); } 这儿就是你将来放游戏的地方。 为了要给你一个表面对象(surface object)如何工作的例子,我们已经做了一个简单的文本表面而且写了一些文本上去。注意我们在最後释放(destroy)了全局指针变量 g_pText,否则每次循环它都将被重新创建一次,直到吃光最后一点儿内存。 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iShowCmd) { WNDCLASSEX wc; HWND hWnd; MSG lpMsg; wc.cbClsExtra=0; wc.cbSize=sizeof(WNDCLASSEX); wc.cbWndExtra=0; wc.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH); wc.hCursor=LoadCursor(NULL,IDC_ARROW); wc.hIcon=LoadIcon(NULL,IDI_APPLICATION); wc.hIconSm=LoadIcon(NULL,IDI_APPLICATION); wc.hInstance=hInstance; wc.lpfnWndProc=WndProc; wc.lpszClassName="wc"; wc.lpszMenuName=0; wc.style=CS_HREDRAW|CS_VREDRAW; if(!RegisterClassEx(&wc)) { MessageBox(NULL,"Couldn't Register Window Class", "Window Class Registration Failure",MB_OK|MB_ICONERROR); return 0; } hWnd = CreateWindowEx(NULL,"wc","DirectDraw Common Files in Action", WS_POPUPWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,640,480,0,0, hInstance,0); if(hWnd == NULL) { MessageBox(NULL,"Failed to Create Window","Window Creation Failure", MB_OK|MB_ICONERROR); return 0; } ShowWindow(hWnd,SW_SHOW); UpdateWindow(hWnd); while(lpMsg.message != WM_QUIT) { if(PeekMessage(&lpMsg,0,0,0,PM_REMOVE)) { TranslateMessage(&lpMsg); DispatchMessage(&lpMsg); } else if(g_bActive) { MainLoop(); } } return lpMsg.wParam; } 这就是我们的应用程序中最长的函数——WinMain()。像往常一样, 我们创建一个窗口类,然后再创建一个窗口,显示及更新它, 并且进入消息循环。看上去主循环不同於平常是因为我们不想要主游戏进程被消息的处理所干扰。我们通过观察全局指针变量 g_bActive 的状态 来判断该时刻调用游戏循环是否安全以及在循环中向屏幕上传送(blit)图象,而最后全部结束的时候我们返回一个长指针 lpMsg.wParam.( 至于为什么我实在是不敢肯定,可其它每个 Win32 应用程序都是这样做的,对,就是这样) 实在是太简单了,嗯!?我们只用了 135 行程序编码就已经将对象写到了屏幕上。自由地去进一步探究这些类的结构,做一些载入位图至表面一类的实验等等。这是使用 DDraw的一个很棒的捷径。它能在不牺牲控制 (你总是能在你需要的时候回去编辑那些类) 和性能的情况下而使事情变得容易些。有一件事得要注意的是在计算机上如果我使用了这个结构却不绘制任何对象到屏幕上, 应用程序将会锁定。( 对,这就是我为什麽把文本输出也包括在这里) 如果我说做游戏很大程度上就是把对象传送( blit )到屏幕上,大概不会引起什么争议(除非又有一些新的艺术风格诞生了)。 

    最新回复(0)