<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    weidagang2046的專欄

    物格而后知致
    隨筆 - 8, 文章 - 409, 評論 - 101, 引用 - 0
    數據加載中……

    游戲開發新手入門之DirectX入門

      ☆ 簡介

      今天我們要接觸到令人敬畏的DirectX。它比Windows GDI要快好幾倍,可用于不同的語言和多種平臺,支持從繪制象素到高級3D圖象,從播放簡單聲音到數字音樂,從鍵盤控制到反震手柄……它給你游戲編程所需的一切(有點夸張)。當然了,它是巨大的,需要好幾本書才能含蓋它的全部。先不要去擔心我在這里所教給你之外的數不清的知識,畢竟我把你推到了起跑線上。

      閱讀本章,你需要前幾章的知識和C語言的知識,由于我們還要談到組件對象模型(COM),它是面向對象系統的基礎,你最好還要有一點兒C++的知識。沒有也不太要緊,我在講到這處時會照顧你的。反正你記住,使用DirectX并不需要多少C++的知識。開始吧!

      ☆ 什么是DirectX?

      DirectX是游戲制作者的API(Application Development Interface)。它是一組允許你直接控制計算機硬件設備的軟件。如果你的硬件支持DirectX,并且你用硬件加速你的程序,這就意味著一個字——快。不用擔心你的硬件知識,你不會真正的接觸到它們。我們是通過硬件抽象層(HAL)和硬件仿真層(HEL)來保證設備無關性和讓你的程序正常運行。

      DirectX由很多組件構成,每一個都有特定的用途。組件DirectDraw是最為重要的一個,因為所有的圖形都要用到它,它是2D圖形的引擎,3D圖形也同樣離不開它。DirectDraw是我們今天就要說的。其它的組件是:

      ▲ DirectSound:提供硬件和軟件的聲音混合與回放。

      ▲ DirectMusic:處理基于消息的音樂數據。它支持樂器數字接口(MIDI)并為創建交互式音樂提供創作工具。

      ▲ DirectPlay:使得通過調制解調器鏈接或通過網絡來與應用程序相連成為可能。

      ▲ Direct3D:是一個三維圖形包,它提供一個高級的保留模式(Retained Mode)接口,這使得你能夠實現一個完整的三維圖形系統。它還包含一個低級的即時模式(Immediate Mode)接口,使得應用程序獲得對渲染管線的完全控制。

      ▲ DirectInput:為包括游戲桿、鼠標、鍵盤和游戲控制器在內的輸入設備提供支持。它還為反饋游戲設備提供支持。

      ▲ DirectSetup:為DirectX提供了一個簡單的安裝過程。它簡化了更新顯示和音頻驅動程序的過程,并且確保沒有硬件或軟件沖突的存在。

      ▲ AutoPlay:讓你能夠制作一張一旦插入驅動器就能自動安裝的光盤。AutoPlay并非DirectX所獨有,因為它是Microsoft Win32 API的一部分。

      組件對象模型(COM)是DirectX的基礎,有一些技巧建立COM對象——別問我怎么做——但你知道一點點還是有好處的。我只是簡單說一下,如果你有興趣,具體的細節就自己查資料吧!可能下一節你有些困惑,但不要緊,我所說的你不用太明白,畢竟我們的目的是使用COM對象,這可比創建容易多了。

      ☆ 組件對象模型(COM)

      COM接口是DirectX技術的基礎,沒有COM就沒有DirectX。(不用擔心,你只需要對COM技術有一個粗淺的了解就可以使用DirectX——只要你在編寫DirectX應用程序時遵循一定的步驟,甚至都可以在不了解COM的情況下使用DirectX。

      DirectX的大多數API都是基于COM結構的。COM為軟件模塊化和軟件重用提供了最堅實的基礎,它的最重要的概念就是接口(interface),接口是軟件重用的最基本方法。更專業的說,接口是一系列操作的規范描述,即接口規范。

      所有的COM接口都是從Iunknown接口繼承而來的,IUnknown接口是所有COM接口的根。IUnknown接口具有3個方法:

      · QueryInterface():此方法查詢新接口,并在新接口存在時返回之。

      · AddRef():此方法在接口或其它應用程序連編到此COM對象上時將引用計數值遞加1。

      · Release():此方法將COM對象的引用計數遞減1。當引用計數遞減到0時,該COM對象自動釋放。

      所有COM對象都具有這三個方法。雖然DirectX應用程序一般不需要考慮引用計數的問題,但引用計數確實是存在的,它已經由DirectX自動完成了。我們所要做的,就是創建DirectX對象,然后在使用完畢后調用Release方法釋放引用。

      ☆ 設置

      用DirectX創建程序,你需要有三件主要的事要做。第一件事是COM對象本身,它們包含在.DLL文件里,這些.DLL文件需要在Windows里注冊,這在安裝DirectX軟件包時已經完成了。這些對象是我們創建DirectX應用程序時用到的接口,例如IdirectDraw。但這還不夠,因為在COM層上直接使用DirectX是令人沮喪的和乏味的。我們希望有更容易的辦法解決它。利用靜態庫(.LIB文件)是個好辦法,它是DirectX軟件包的一部分,你可以從Microsoft免費獲得。它有一個“打包”函數使你工作更輕松。使用DirectX的不同組件,你需要鏈接不同的靜態庫。例如你要使用DirectDraw組件,你就需要ddraw.lib。

      最后,你還需要DrectX頭文件,它包含函數原形、宏、常量和你需要用到的各種類型。對于DirectDraw,這個頭文件是ddraw.h。
    要確認你使用了正確的文件版本,你還得讓編譯器包含軟件開發包的目錄。具體的做法是:

      首先點擊Tool菜單,選擇Options,然后點擊Directories,在Show Directories for 組合框下拉菜單中選擇Include files,增加一個新的目錄。將你的DirectX的路徑填入。(例如:C:DXSDKinclude)然后將它移到列表的第一位,使編譯時第一個尋找它(防止尋找老版本)。然后選擇Show Directories for組合框下拉菜單中的Library files,方法同前,只是把include改成lib。現在,你已經設置完了DirectX。你仍然需要手動增加一些庫文件到你的項目中,但先不急,我將在以后講它。我們將使用DirectX 7.0。

      ☆ DirectX版本號

      你可能認為版本號沒有什么好講的,但我們確實要說一說。Microsoft在DirectX里創建了令人難以置信的科技,但它并不代表不使人迷惑。對于每一個DirectX版本,并不是所有的接口都一次次的升級。因此,盡管DirectX有了7個版本(我寫文章時DirectX8.0正準備發布),但DirectDraw并沒有7個版本。當DirectX6是最新版本時,DirectDraw的最新接口版本是IDirectDraw4,不是IDirectDraw6。現在最新的版本是DirectX7,所以我們要用IDrectDraw7。很奇怪,是不是?我想你已經明白了我的意思,請不用因為以后看到的感到困惑了。

      最后一件事。當我寫這篇文章時,DirectX7是最新的可用版本,但或許現在你已經有了DirectX8,并且或許你還聽說了,DirectDraw將不再升級了,取代它的是DirectX Graphics,這是一個功能強大的圖形API。但DirectDraw不升級就意味著我們不學習它了,畢竟都離不開COM。如果你想用DirectX8的接口寫2D的游戲,你需要用3D方法去創建2D觀點。聽起來很棒,是的,的確如此,因為使用3D接口將給你更多的硬件支持,例如阿爾發混合。但這也恰恰是個問題,如果機器沒有相應的硬件設備,程序會以更慢的速度運行。

      DirectDraw是很容易學的。由于DirectX中的3D圖形是基于DirectDraw的,3D應用程序在DirectDraw環境中執行;極少有應用程序專門使用3D。大多數程序使用3D函數對一些對象建模,而另一些對象,諸如背景和精靈,是以2D圖形渲染的。所以本系列將使用DirectDraw。關于DirectX8,我還沒有太多的了解,因此我只能對DirecX7做詳細介紹。總的來說,你使用DirectX,還是離不開DirectDraw的。

      ☆ DirectDraw概述

      在你的程序中使用DirectDraw,你至少要做四件事,它們是:

      1、建立一個DirectDraw對象。

      2、設置協作等級。

      3、設置顯示模式和色彩深度(全屏模式)。

      4、至少創建一個DirectDraw表面。

      在講怎樣完成以上步驟之前,先讓我們了解一下每一步的含義。第一個,要創建一個DirectDraw對象,這意味著我們要建立一個指向IDirectDraw7接口的指針。這很簡單,不是嗎?有三種辦法可以實現它,你可以直接使用COM,或使用DirectDraw的兩個函數之一。三種辦法各有千秋,我們過一會兒將詳細介紹。第二個,設置操作等級。這可能對你來說比較新鮮。協作是由于Windows是一個多任務操作系統而產生的概念。意思是所有運行的程序都要隨時告知Windows它們將要或正在使用的資源,這將保證你所要使用的資源不會被windows再分配給別的應用程序。不用擔心,有一個很簡單的函數完成它。

      第三個你是否有點熟悉?如果你要寫一個全屏的程序,通常是游戲程序,你需要設置顯示模式和色彩深度。在Windows的應用程序里做這些通常不是一個好主意,因為它能導致其它程序的同時運行出現問題。當你結束自己的程序時,你當然要恢復到改變前的狀態。設置全屏模式,只是調用一個單獨的DirectDraw的函數,程序結束后,要恢復原來的狀態。

      最后,也是最重要的,是DirectDraw表面的概念。操縱表面是DirectDraw的全部。簡單的說,DirectDraw表面是一個用于存儲圖象數據的線性內存區域。DirectDraw表面的大小就是以象素為單位,用寬和高來定義。所以你可以認為表面是一個用來畫圖的矩形區域。它有自己的接口,稱作IDirectDrawSurface7。有三種主要的表面,我們將在本章和下一章分別用到它們。

      · 主表面:每一個DirectDraw應用程序都有一個主表面。主表面就相當于用戶的顯示器。它的內容是可見的。同理,主表面就是根據顯示器的顯示模式設定寬和高。

      · 后緩沖區:后緩沖區是緊隨主表面的表面,但它不可見。它是動畫沒有閃爍的主因,通常,你在后緩沖區畫好每一幀,然后把后緩沖區的內容拷貝到主表面,使它顯示出來。由于它緊隨著主表面,所以它的大小同主表面相同。(你就理解為樓上和樓下的關系)

      · 離屏緩沖區:它很象后緩沖區,只是它不是緊挨著主表面。盡管你可以用它作任何事,但它經常被用來存儲位圖。離屏緩沖區你可以任意設置大小,唯一的限制是你內存的大小。

      DirectDraw表面可以在系統內存中建立,或直接建立在顯示內存中。如果你都建立在顯示內存中,速度效果將是最好的,如在系統內存就要慢一些了。如果你把一個表面存儲在顯示內存中,另一個在系統內存中,性能會有一些損失的,尤其是顯示卡與主板之間有一個令人惡心的帶寬。總之,如果能把表面都建立在顯示內存中,你或許應該盡力做到。

      OK,我們總算有了一點兒認知,讓我們看看具體怎么做吧!這兒有一個計劃,我們將建立一個全屏模式下,16位色彩,640×480分辨率的程序,我將告訴你全部你需要做的。但開始前,你需要對Windows編程有一點了解。想必你看過了前面幾章,應該對創建窗口已經熟悉了。由于這是一個全屏的程序,你不需要任何地窗口控制,所以你的窗口風格應該用WS_POUP|WS_VISIBLE。弄好了嗎?All right,出發吧!

      ☆ 建立DIRECTDRAW對象

      象我前面說過的,有三種方法。我們可以用兩個DIRECTDRAW函數中的任何一個,或者直接調用COM對象。讓我們每一個都試試,使我們自己熟悉它們。我將告訴你的最后一個方法可能是目前為止最簡單的,可能你會喜歡用它。至于另外兩個,打眼兒一看,你會覺得有些奇怪。首先,看看DirectDrawCreate():

    HRESULT WINAPI DirectDrawCreate(
    GUID FAR *lpGUID,
    LPDIRECTDRAW FAR *lplpDD,
    IUnknown FAR *pUnkOuter
    );

      看起來是不是有點兒復雜?HRESULT返回的類型是DirectDraw函數的標準。如果成功,返回值是DD_OK。如果失敗,函數將返回一個錯誤常量,有幾個錯誤常量供選擇,但我不想細講,更不想列出這些常量,反正你可以通過幫助文件隨時查閱它們。但有一件事兒我得告訴你,有兩個非常有用的宏可以幫助你知道函數調用成功與否:SUCCEEDED()和FAILED()。從字面上你就知道它們的分工了,是不是?只要把函數放到宏里面,你就知道結果了。無論如何,我們還得看看函數的參數:

      · GUID FAR *lpGUID:是一個全局唯一標識符(GUID)的地址,代表將要創建的驅動程序。如果該參數是NULL,那么該調用指向當前的顯示驅動程序。新版本的DirectDraw允許向該參數傳遞下列兩種標志之一,以控制當前顯示的行為:

      ◎ DDCREATE_EMULATIONONLY:DirectDraw只使用仿真(HEL),不使用硬件支持特性。

      ◎ DDCREATE_HARDWAREONLY:DirectDraw對象不使用仿真特性。只能使用硬件抽象層(HAL),如果硬件不能支持應用程序,將不再尋求硬件仿真層(HEL)的支持而返回錯誤信號。

      · LPDIRECTDRAW FAR *lplpDD:表示如果調用成功則返回有效的DirectDraw對象指針的地址,它是DirectDraw對象指針的指針(“DD”表示DirectDraw,“lp”表示32位長指針,“lplp”表示長指針的長指針)。應用程序一般需要使用此指針的地址(即DirectDraw對象指針)初始化DirectDraw對象。

      · IUnknown FAR *pUnkOuter:這是為高級COM應用保留的參數,設置為NULL好了。

      不要被我羅里羅嗦的解釋嚇倒,實際應用起來很簡單,解釋這么多,不過是為了讓你明白根本道理。現在有一個問題,這個函數給你一個指向IDirectDraw接口的指針,但我們想要一個指向IDirectDraw7接口的指針,我們應該怎么做呢?一旦DirectDraw應用程序通過DirectDrawCreate()函數獲得了指向DirectDraw對象的指針,COM就有一種機制可以用來查看該對象是否支持其它接口。IUnknown的QueryInterface()方法使得你能夠確定一個對象是否支持一個特定的接口:

    HRESULT QueryInterface(
     REFIID iid, // Identifier of the requested interface
     void **ppvObject // Address of output variable that receives the
    );

      第一個參數是一個要查詢的對象的引用標識符。對于IDirectDraw7來說就是IID_IDirectDraw7。使用它,你必須把dxguid.lib鏈接入你的項目中;第二個參數是一個變量的地址,我們應該在程序的頭部先聲明一個LPDIRECTDRAW7類型的指針,再把指針的地址傳遞給這個參數。如果你使用的是Visual C++6.0,你在這兒或許還需要一個類型強制符。如果機器支持你指定的接口,函數將返回一個指向該接口的指針。通過該指針,代碼就獲得對新接口的方法的訪問。如果函數調用成功,返回值是S_OK。

      現在我們有了兩個接口指針:一個是IDirectDraw接口,另一個是IDirectDraw7。后一個是我們想要的;前一個就沒有用了。我們注意,在代碼中每當找到一個新的有效對象時,前一個對象就通過Release()函數被釋放掉。這個函數很簡單:

    ULONG Release(void);

      返回的值是一個參考數字,只有在程序測試和調試時才用得著這個數字。為了安全起見,你還應該把釋放了的指針賦值為NULL。我們也通常在聲明這樣的指針時設置它為NULL。你跟上我的節奏了嗎?可能要記憶的東西太多了,但是你不得不記憶。讓我們把談到的這些做個實例吧,實例的目的是得到IDirectDraw7接口的指針:

    LPDIRECTDRAW lpdd = NULL; // pointer to IDirectDraw (temporary)
    LPDIRECTDRAW7 lpdd7 = NULL; // pointer to IDirectDraw7 (what we want)

    // get the IDirectDraw interface pointer
    if (FAILED(DirectDrawCreate(NULL, &lpdd, NULL)))
    {
     // error-handling code here
    }

    // query for IDirectDraw7 pointer
    if (FAILED(lpdd->QueryInterface(IID_IDirectDraw7, (void**)&lpdd7)))
    {
     // error-handling code here
    }
    else
    {
     // success! release IDirectDraw since we don't need it anymore
     lpdd->Release();
     lpdd = NULL;
    }

      現在,如果你是一個C程序員,你可能被調用QueryInterface()和Release()這兩個函數的方法弄得有點模糊。你以前可能看過“->”這個符號,在C語言的結構部分,當結構聲明了一個指針變量,調用結構成員時,就用“結構指針名->結構成員”,同樣的道理,只是這里把結構成員換成了函數。既然說到這個話題,我就介紹一下另一個C++符號,范圍定義符號“::”,它是表示從屬關系的符號,舉個例子你就明白了:比如QueryInterface()函數是屬于IUnknown類的,就可以表示為IUnknown::QueryInterface()。我們將來會經常用到這個符號的,所以記住它。

      說實在的,以上的主要目的是為了演示怎樣使用QueryInterface()方法,它是所有DirectX接口的一部分,所以讓我們往下進行。我們將直接使用COM方法獲得接口指針。這種方法的好處是你可以立即獲得IDirectDraw7接口指針,不用象剛才那么麻煩。首先,你必須得初始化COM,象這樣:

    HRESULT CoInitialize(LPVOID pvReserved);

      不能在容易了,你必須把參數設置為NULL。當你結束COM調用,你需要拋棄它,也很簡單:

    void CoUninitialize(void);

      我通常在DirecX程序的一開始就調用CoInitialize()函數,在程序的最末端,當我釋放了所有的DirectX對象后,使用CoUninitialize()。一旦你初始化了COM,你就可以用CoCreateInterface()函數得到你想要的指針,它看起來有點丑陋:

    STDAPI CoCreateInstance(
     REFCLSID rclsid, // Class identifier (CLSID) of the object
     LPUNKNOWN pUnkOuter, // Pointer to whether object is or isn't part
     // of an aggregate
     DWORD dwClsContext, // Context for running executable code
     REFIID riid, // Reference to the identifier of the interface
     LPVOID *ppv // Address of output variable that receives
    ); // the interface pointer requested in riid

      如果成功,返回值是S_OK。參數需要好好解釋一下,看下面:

      · REFCLSID rclsid:這是一個類標識符(不要同GUID搞混了哦),有為它準備好的常量標識符供你選擇。對于IDirectDraw7來說,使用CLSID_DirectDraw。注意沒有版本號,因為它是類標識符,不是接口標識符。

      · LPUNKNOWN pUnkOuter:這個同我們在DirectDrawCreate()中看到的一樣,設置為NULL。

      · DWORD dwClsContext:這個必需的值叫作執行上下文,它定義了控制新生成對象的代碼將要執行的方式。這個值可以從CLSCTX列表中選取,對于我們現在的情況,我們用CLSCTX_ALL,它包含了所有可能的值。

      · REIID riid:我們在QueryInterface()中看過它。這個IID是IID_DirectDraw7。

      · LPVOID *ppv:依然同DirectDrawCreate()中的一樣,是指向接口指針的地址。

      調用這個函數將取代我們上一個方法中的DirectDrawCreate()、QueryInterface()和Release()三個函數,所以簡捷一些。當然,使用哪種隨便你了。直接調用COM比我們先前用的方法少了一個多于地接口指針。一旦你用CoCreateInstance()建立了一個對象,你還得調用Initialize()函數初始化這個對象。在C++里可能寫成這樣IDirectDraw7::Initialize()。以下是它的原形:

    HRESULT Initialize(GUID FAR *lpGUID);

      將使用同DirectDrawCreate()中一樣的GUID,就是NULL。在我們繼續前,讓我給你看一個使用COM創建DirectDraw對象的例子:

    LPDIRECTDRAW7 lpdd7; // interface pointer

    // initialize COM
    CoInitialize(NULL);

    // create the object
    CoCreateInstance(CLSID_DirectDraw, NULL, CLSCTX_ALL, IID_IDirectDraw7, (void**)&lpdd7);

    // initialize the object
    lpdd7->Initialize(NULL);

      直接看例子可能使你更容易理解一些。好了,建立DirectDraw對象的最難的兩種方法你已經學會了,那就讓我們看看最簡單的方法吧! 它只有一步,沒有多于的接口指針,不用設置COM,什么都沒有。就是下面這個函數:

    DirectDrawCreateEx(
     GUID FAR *lpGuid,
     LPVOID *lplpDD,
     REFIID iid,
     IUnknown FAR *pUnkOuter
    );

      所有的參數我們看起來都比較熟悉,因為我們剛才看過它們了。第一個,第二個和第四個參數同DirectDrawCreate()中的一樣,只是這里需要用(void**)來修飾一下我們接口指針的地址——別問我為什么,這不是我的主意。第三個參數,riid,是我們在函數CoCreateInstance()中傳遞的接口ID,所以我們就用IID_IDirectDraw7。就這樣,無論用哪種方法,我們得到了我們的DirectDraw對象,我們可以繼續使用這個對象了。要做的頭兩件事是設置協作等級和顯示協議。

      ☆ 設置協作等級和顯示模式

      我不需要說太多。Windows編程設置協作級別你只需要調用IDirectDraw7::SetCooperativeLevel()函數;設置顯示模式你就調用IDirectDraw7::SetDisplayMode()函數。就這么簡單!先來看看協作級別。這就是函數原形:

    HRESULT SetCooperativeLevel(
     HWND hWnd,
     DWORD dwFlags
    );

      返回的類型是HRESULT,你應該已經熟悉它了。對于所有的DirectX函數調用,你都可以用SUCCEEDED()和FAILED()宏檢測調用的結果。以下是函數SetCooperativeLevel()的參數:

      · HWND hWnd:很熟悉吧!傳遞主窗口的句柄給它,使Windows知道誰將使用它的資源。

      · DWORD dwFlags:這個也很眼熟吧!每次我們看到dwFlags參數,幾乎都有一個大的標志常量列表供我們選擇,并且可以用“|”組合。這次也不會讓你失望的哦!

      ◎ DDSCL_ALLOWMODEX:啟用Mode X 顯示模式(如320×200,320×240或者320×400)。該標志只能用于DDSCL_EXCLUSIVE和DDSCL_FULLSCREEN模式。

      ◎ DDSCL_ALLOWREBOOT:在獨占模式中啟用Ctrl+Alt+Del組合鍵功能。

      ◎ DDSCL_EXCLUSIVE:請求獨占模式,必須與DDSCL_FULLSCREEN同時使用。

      ◎ DDSCL_FULLSCREEN:獨占模式的擁有者負責整個主表面,GDI被忽略,必須與DDSCL_EXCLUSIVE同時使用。

      ◎ DDSCL_NORMAL:表示常規的Windows應用程序,不能與DDSCL_ALLOWMODEX、DDSCL_EXCLUSEIVE或DDSCL_FULLSCREEN標志同時使用,在該模式下運行的應用程序不能進行頁交換或者更改主調色板。

      ◎ DDSCL_NOWINDOWCHANGES:防止DirectDraw最小化或恢復應用程序窗口。
     
      還有幾個標志常量我們暫時用不到,就不說了。由于我們要建立一個全屏的640×480×16的顯示模式,所以我們得這樣設置:

    lpdd7->SetCooperativeLevel(hwnd, DDSCL_ALLOWREBOOT | DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);

      現在協作級別已經設置好了,讓我們再看看改變顯示模式的函數:

    HRESULT SetDisplayMode(
    DWORD dwWidth,
    DWORD dwHeight,
    DWORD dwBPP,
    DWORD dwRefreshRate,
    DWORD dwFlags
    );

      別忘了用宏去檢測調用函數的成功或失敗!大多數的參數同你料想的差不多:

      · DWORD dwWidth,dwHeight:以象素為單位,新顯示模式的尺寸。

      · DWORD dwBPP:新顯示模式的色彩深度。就是每一個象素有多少位字節。可以設置為8,16,24或32。警告:很多顯示卡不支持24-bits。

      · DWORD dwRefreshRate:屏幕的刷新頻率。但你最好設置為0,使用默認的刷新頻率。

      · DWORD dwFlags:對不起,這次沒有列表了^_^,唯一的選擇是DDSDM_STANDARDVGAMODE,它把顯示模式設置為0x13(DOS程序員的好朋友),取代了Mode X的320×200×8的模式。如果你還想使用其它的模式(你可能經常需要),沒有問題,把它設置為0好了。

      這些就是顯示模式的設置,事先最好了解你的顯示卡支持的顯示模式,它們通常都支持640×480,800×600,1024×768等等,這些都是標準的模式。但是如果你非得設置成542×366的模式,你可能就會得到錯誤的反饋。科技在發展嗎,什么都是可能的。讓我們繼續吧!

      ☆ DirectDraw概述

      在你的程序中使用DirectDraw,你至少要做四件事,它們是:

      1、建立一個DirectDraw對象。

      2、設置協作等級。

      3、設置顯示模式和色彩深度(全屏模式)。

      4、至少創建一個DirectDraw表面。

      在講怎樣完成以上步驟之前,先讓我們了解一下每一步的含義。第一個,要創建一個DirectDraw對象,這意味著我們要建立一個指向IDirectDraw7接口的指針。這很簡單,不是嗎?有三種辦法可以實現它,你可以直接使用COM,或使用DirectDraw的兩個函數之一。三種辦法各有千秋,我們過一會兒將詳細介紹。第二個,設置操作等級。這可能對你來說比較新鮮。協作是由于Windows是一個多任務操作系統而產生的概念。意思是所有運行的程序都要隨時告知Windows它們將要或正在使用的資源,這將保證你所要使用的資源不會被windows再分配給別的應用程序。不用擔心,有一個很簡單的函數完成它。

      第三個你是否有點熟悉?如果你要寫一個全屏的程序,通常是游戲程序,你需要設置顯示模式和色彩深度。在Windows的應用程序里做這些通常不是一個好主意,因為它能導致其它程序的同時運行出現問題。當你結束自己的程序時,你當然要恢復到改變前的狀態。設置全屏模式,只是調用一個單獨的DirectDraw的函數,程序結束后,要恢復原來的狀態。

      最后,也是最重要的,是DirectDraw表面的概念。操縱表面是DirectDraw的全部。簡單的說,DirectDraw表面是一個用于存儲圖象數據的線性內存區域。DirectDraw表面的大小就是以象素為單位,用寬和高來定義。所以你可以認為表面是一個用來畫圖的矩形區域。它有自己的接口,稱作IDirectDrawSurface7。有三種主要的表面,我們將在本章和下一章分別用到它們。

      · 主表面:每一個DirectDraw應用程序都有一個主表面。主表面就相當于用戶的顯示器。它的內容是可見的。同理,主表面就是根據顯示器的顯示模式設定寬和高。

      · 后緩沖區:后緩沖區是緊隨主表面的表面,但它不可見。它是動畫沒有閃爍的主因,通常,你在后緩沖區畫好每一幀,然后把后緩沖區的內容拷貝到主表面,使它顯示出來。由于它緊隨著主表面,所以它的大小同主表面相同。(你就理解為樓上和樓下的關系)

      · 離屏緩沖區:它很象后緩沖區,只是它不是緊挨著主表面。盡管你可以用它作任何事,但它經常被用來存儲位圖。離屏緩沖區你可以任意設置大小,唯一的限制是你內存的大小。

      DirectDraw表面可以在系統內存中建立,或直接建立在顯示內存中。如果你都建立在顯示內存中,速度效果將是最好的,如在系統內存就要慢一些了。如果你把一個表面存儲在顯示內存中,另一個在系統內存中,性能會有一些損失的,尤其是顯示卡與主板之間有一個令人惡心的帶寬。總之,如果能把表面都建立在顯示內存中,你或許應該盡力做到。

      OK,我們總算有了一點兒認知,讓我們看看具體怎么做吧!這兒有一個計劃,我們將建立一個全屏模式下,16位色彩,640×480分辨率的程序,我將告訴你全部你需要做的。但開始前,你需要對Windows編程有一點了解。想必你看過了前面幾章,應該對創建窗口已經熟悉了。由于這是一個全屏的程序,你不需要任何地窗口控制,所以你的窗口風格應該用WS_POUP|WS_VISIBLE。弄好了嗎?All right,出發吧!

      ☆ 建立DIRECTDRAW對象

      象我前面說過的,有三種方法。我們可以用兩個DIRECTDRAW函數中的任何一個,或者直接調用COM對象。讓我們每一個都試試,使我們自己熟悉它們。我將告訴你的最后一個方法可能是目前為止最簡單的,可能你會喜歡用它。至于另外兩個,打眼兒一看,你會覺得有些奇怪。首先,看看DirectDrawCreate():

    HRESULT WINAPI DirectDrawCreate(
    GUID FAR *lpGUID,
    LPDIRECTDRAW FAR *lplpDD,
    IUnknown FAR *pUnkOuter
    );

      看起來是不是有點兒復雜?HRESULT返回的類型是DirectDraw函數的標準。如果成功,返回值是DD_OK。如果失敗,函數將返回一個錯誤常量,有幾個錯誤常量供選擇,但我不想細講,更不想列出這些常量,反正你可以通過幫助文件隨時查閱它們。但有一件事兒我得告訴你,有兩個非常有用的宏可以幫助你知道函數調用成功與否:SUCCEEDED()和FAILED()。從字面上你就知道它們的分工了,是不是?只要把函數放到宏里面,你就知道結果了。無論如何,我們還得看看函數的參數:

      · GUID FAR *lpGUID:是一個全局唯一標識符(GUID)的地址,代表將要創建的驅動程序。如果該參數是NULL,那么該調用指向當前的顯示驅動程序。新版本的DirectDraw允許向該參數傳遞下列兩種標志之一,以控制當前顯示的行為:

      ◎ DDCREATE_EMULATIONONLY:DirectDraw只使用仿真(HEL),不使用硬件支持特性。

      ◎ DDCREATE_HARDWAREONLY:DirectDraw對象不使用仿真特性。只能使用硬件抽象層(HAL),如果硬件不能支持應用程序,將不再尋求硬件仿真層(HEL)的支持而返回錯誤信號。

      · LPDIRECTDRAW FAR *lplpDD:表示如果調用成功則返回有效的DirectDraw對象指針的地址,它是DirectDraw對象指針的指針(“DD”表示DirectDraw,“lp”表示32位長指針,“lplp”表示長指針的長指針)。應用程序一般需要使用此指針的地址(即DirectDraw對象指針)初始化DirectDraw對象。

      · IUnknown FAR *pUnkOuter:這是為高級COM應用保留的參數,設置為NULL好了。

      不要被我羅里羅嗦的解釋嚇倒,實際應用起來很簡單,解釋這么多,不過是為了讓你明白根本道理。現在有一個問題,這個函數給你一個指向IDirectDraw接口的指針,但我們想要一個指向IDirectDraw7接口的指針,我們應該怎么做呢?一旦DirectDraw應用程序通過DirectDrawCreate()函數獲得了指向DirectDraw對象的指針,COM就有一種機制可以用來查看該對象是否支持其它接口。IUnknown的QueryInterface()方法使得你能夠確定一個對象是否支持一個特定的接口:

    HRESULT QueryInterface(
     REFIID iid, // Identifier of the requested interface
     void **ppvObject // Address of output variable that receives the
    );

      第一個參數是一個要查詢的對象的引用標識符。對于IDirectDraw7來說就是IID_IDirectDraw7。使用它,你必須把dxguid.lib鏈接入你的項目中;第二個參數是一個變量的地址,我們應該在程序的頭部先聲明一個LPDIRECTDRAW7類型的指針,再把指針的地址傳遞給這個參數。如果你使用的是Visual C++6.0,你在這兒或許還需要一個類型強制符。如果機器支持你指定的接口,函數將返回一個指向該接口的指針。通過該指針,代碼就獲得對新接口的方法的訪問。如果函數調用成功,返回值是S_OK。

      現在我們有了兩個接口指針:一個是IDirectDraw接口,另一個是IDirectDraw7。后一個是我們想要的;前一個就沒有用了。我們注意,在代碼中每當找到一個新的有效對象時,前一個對象就通過Release()函數被釋放掉。這個函數很簡單:

    ULONG Release(void);

      返回的值是一個參考數字,只有在程序測試和調試時才用得著這個數字。為了安全起見,你還應該把釋放了的指針賦值為NULL。我們也通常在聲明這樣的指針時設置它為NULL。你跟上我的節奏了嗎?可能要記憶的東西太多了,但是你不得不記憶。讓我們把談到的這些做個實例吧,實例的目的是得到IDirectDraw7接口的指針:

    LPDIRECTDRAW lpdd = NULL; // pointer to IDirectDraw (temporary)
    LPDIRECTDRAW7 lpdd7 = NULL; // pointer to IDirectDraw7 (what we want)

    // get the IDirectDraw interface pointer
    if (FAILED(DirectDrawCreate(NULL, &lpdd, NULL)))
    {
     // error-handling code here
    }

    // query for IDirectDraw7 pointer
    if (FAILED(lpdd->QueryInterface(IID_IDirectDraw7, (void**)&lpdd7)))
    {
     // error-handling code here
    }
    else
    {
     // success! release IDirectDraw since we don't need it anymore
     lpdd->Release();
     lpdd = NULL;
    }

      現在,如果你是一個C程序員,你可能被調用QueryInterface()和Release()這兩個函數的方法弄得有點模糊。你以前可能看過“->”這個符號,在C語言的結構部分,當結構聲明了一個指針變量,調用結構成員時,就用“結構指針名->結構成員”,同樣的道理,只是這里把結構成員換成了函數。既然說到這個話題,我就介紹一下另一個C++符號,范圍定義符號“::”,它是表示從屬關系的符號,舉個例子你就明白了:比如QueryInterface()函數是屬于IUnknown類的,就可以表示為IUnknown::QueryInterface()。我們將來會經常用到這個符號的,所以記住它。

      說實在的,以上的主要目的是為了演示怎樣使用QueryInterface()方法,它是所有DirectX接口的一部分,所以讓我們往下進行。我們將直接使用COM方法獲得接口指針。這種方法的好處是你可以立即獲得IDirectDraw7接口指針,不用象剛才那么麻煩。首先,你必須得初始化COM,象這樣:

    HRESULT CoInitialize(LPVOID pvReserved);

      不能在容易了,你必須把參數設置為NULL。當你結束COM調用,你需要拋棄它,也很簡單:

    void CoUninitialize(void);

      我通常在DirecX程序的一開始就調用CoInitialize()函數,在程序的最末端,當我釋放了所有的DirectX對象后,使用CoUninitialize()。一旦你初始化了COM,你就可以用CoCreateInterface()函數得到你想要的指針,它看起來有點丑陋:

    STDAPI CoCreateInstance(
     REFCLSID rclsid, // Class identifier (CLSID) of the object
     LPUNKNOWN pUnkOuter, // Pointer to whether object is or isn't part
     // of an aggregate
     DWORD dwClsContext, // Context for running executable code
     REFIID riid, // Reference to the identifier of the interface
     LPVOID *ppv // Address of output variable that receives
    ); // the interface pointer requested in riid

      如果成功,返回值是S_OK。參數需要好好解釋一下,看下面:

      · REFCLSID rclsid:這是一個類標識符(不要同GUID搞混了哦),有為它準備好的常量標識符供你選擇。對于IDirectDraw7來說,使用CLSID_DirectDraw。注意沒有版本號,因為它是類標識符,不是接口標識符。

      · LPUNKNOWN pUnkOuter:這個同我們在DirectDrawCreate()中看到的一樣,設置為NULL。

      · DWORD dwClsContext:這個必需的值叫作執行上下文,它定義了控制新生成對象的代碼將要執行的方式。這個值可以從CLSCTX列表中選取,對于我們現在的情況,我們用CLSCTX_ALL,它包含了所有可能的值。

      · REIID riid:我們在QueryInterface()中看過它。這個IID是IID_DirectDraw7。

      · LPVOID *ppv:依然同DirectDrawCreate()中的一樣,是指向接口指針的地址。

      調用這個函數將取代我們上一個方法中的DirectDrawCreate()、QueryInterface()和Release()三個函數,所以簡捷一些。當然,使用哪種隨便你了。直接調用COM比我們先前用的方法少了一個多于地接口指針。一旦你用CoCreateInstance()建立了一個對象,你還得調用Initialize()函數初始化這個對象。在C++里可能寫成這樣IDirectDraw7::Initialize()。以下是它的原形:

    HRESULT Initialize(GUID FAR *lpGUID);

      將使用同DirectDrawCreate()中一樣的GUID,就是NULL。在我們繼續前,讓我給你看一個使用COM創建DirectDraw對象的例子:

    LPDIRECTDRAW7 lpdd7; // interface pointer

    // initialize COM
    CoInitialize(NULL);

    // create the object
    CoCreateInstance(CLSID_DirectDraw, NULL, CLSCTX_ALL, IID_IDirectDraw7, (void**)&lpdd7);

    // initialize the object
    lpdd7->Initialize(NULL);

      直接看例子可能使你更容易理解一些。好了,建立DirectDraw對象的最難的兩種方法你已經學會了,那就讓我們看看最簡單的方法吧! 它只有一步,沒有多于的接口指針,不用設置COM,什么都沒有。就是下面這個函數:

    DirectDrawCreateEx(
     GUID FAR *lpGuid,
     LPVOID *lplpDD,
     REFIID iid,
     IUnknown FAR *pUnkOuter
    );

      所有的參數我們看起來都比較熟悉,因為我們剛才看過它們了。第一個,第二個和第四個參數同DirectDrawCreate()中的一樣,只是這里需要用(void**)來修飾一下我們接口指針的地址——別問我為什么,這不是我的主意。第三個參數,riid,是我們在函數CoCreateInstance()中傳遞的接口ID,所以我們就用IID_IDirectDraw7。就這樣,無論用哪種方法,我們得到了我們的DirectDraw對象,我們可以繼續使用這個對象了。要做的頭兩件事是設置協作等級和顯示協議。

      ☆ 設置協作等級和顯示模式

      我不需要說太多。Windows編程設置協作級別你只需要調用IDirectDraw7::SetCooperativeLevel()函數;設置顯示模式你就調用IDirectDraw7::SetDisplayMode()函數。就這么簡單!先來看看協作級別。這就是函數原形:

    HRESULT SetCooperativeLevel(
     HWND hWnd,
     DWORD dwFlags
    );

      返回的類型是HRESULT,你應該已經熟悉它了。對于所有的DirectX函數調用,你都可以用SUCCEEDED()和FAILED()宏檢測調用的結果。以下是函數SetCooperativeLevel()的參數:

      · HWND hWnd:很熟悉吧!傳遞主窗口的句柄給它,使Windows知道誰將使用它的資源。

      · DWORD dwFlags:這個也很眼熟吧!每次我們看到dwFlags參數,幾乎都有一個大的標志常量列表供我們選擇,并且可以用“|”組合。這次也不會讓你失望的哦!

      ◎ DDSCL_ALLOWMODEX:啟用Mode X 顯示模式(如320×200,320×240或者320×400)。該標志只能用于DDSCL_EXCLUSIVE和DDSCL_FULLSCREEN模式。

      ◎ DDSCL_ALLOWREBOOT:在獨占模式中啟用Ctrl+Alt+Del組合鍵功能。

      ◎ DDSCL_EXCLUSIVE:請求獨占模式,必須與DDSCL_FULLSCREEN同時使用。

      ◎ DDSCL_FULLSCREEN:獨占模式的擁有者負責整個主表面,GDI被忽略,必須與DDSCL_EXCLUSIVE同時使用。

      ◎ DDSCL_NORMAL:表示常規的Windows應用程序,不能與DDSCL_ALLOWMODEX、DDSCL_EXCLUSEIVE或DDSCL_FULLSCREEN標志同時使用,在該模式下運行的應用程序不能進行頁交換或者更改主調色板。

      ◎ DDSCL_NOWINDOWCHANGES:防止DirectDraw最小化或恢復應用程序窗口。
     
      還有幾個標志常量我們暫時用不到,就不說了。由于我們要建立一個全屏的640×480×16的顯示模式,所以我們得這樣設置:

    lpdd7->SetCooperativeLevel(hwnd, DDSCL_ALLOWREBOOT | DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);

      現在協作級別已經設置好了,讓我們再看看改變顯示模式的函數:

    HRESULT SetDisplayMode(
    DWORD dwWidth,
    DWORD dwHeight,
    DWORD dwBPP,
    DWORD dwRefreshRate,
    DWORD dwFlags
    );

      別忘了用宏去檢測調用函數的成功或失敗!大多數的參數同你料想的差不多:

      · DWORD dwWidth,dwHeight:以象素為單位,新顯示模式的尺寸。

      · DWORD dwBPP:新顯示模式的色彩深度。就是每一個象素有多少位字節。可以設置為8,16,24或32。警告:很多顯示卡不支持24-bits。

      · DWORD dwRefreshRate:屏幕的刷新頻率。但你最好設置為0,使用默認的刷新頻率。

      · DWORD dwFlags:對不起,這次沒有列表了^_^,唯一的選擇是DDSDM_STANDARDVGAMODE,它把顯示模式設置為0x13(DOS程序員的好朋友),取代了Mode X的320×200×8的模式。如果你還想使用其它的模式(你可能經常需要),沒有問題,把它設置為0好了。

      這些就是顯示模式的設置,事先最好了解你的顯示卡支持的顯示模式,它們通常都支持640×480,800×600,1024×768等等,這些都是標準的模式。但是如果你非得設置成542×366的模式,你可能就會得到錯誤的反饋。科技在發展嗎,什么都是可能的。讓我們繼續吧!

    from: http://dev.yesky.com/SoftChannel/72342376173010944/20050309/1919921.shtml

    posted on 2006-06-13 23:48 weidagang2046 閱讀(859) 評論(0)  編輯  收藏 所屬分類: Windows

    主站蜘蛛池模板: 午夜dj在线观看免费视频| 毛片a级毛片免费观看免下载| 亚洲国产成人a精品不卡在线| 亚洲日韩国产精品乱-久| 4399影视免费观看高清直播| 亚洲影院在线观看| 最近新韩国日本免费观看| 久久青青草原亚洲av无码app| 午夜理伦剧场免费| 自怕偷自怕亚洲精品| 最近中文字幕免费2019| 亚洲一级片在线播放| 国产精品视频免费一区二区| 亚洲风情亚Aⅴ在线发布| 在线观看国产情趣免费视频 | 日本免费人成网ww555在线 | 91亚洲导航深夜福利| 亚洲精品视频免费在线观看| 亚洲一级特黄特黄的大片| 成人免费看吃奶视频网站| 精品一区二区三区无码免费直播| 亚洲七七久久精品中文国产| 国产午夜精品久久久久免费视 | 亚洲成人影院在线观看| 中国精品一级毛片免费播放| 亚洲国产精品人久久| 最近免费中文字幕大全视频| 亚洲av无码一区二区三区人妖| 久久久久亚洲av成人无码电影| 污污网站18禁在线永久免费观看| 亚洲av永久无码嘿嘿嘿| 亚洲精品国产va在线观看蜜芽| 久久99精品视免费看| 亚洲gay片在线gv网站| 国产AⅤ无码专区亚洲AV| 国产91免费视频| 猫咪www免费人成网站| 亚洲第一页在线播放| 午夜国产羞羞视频免费网站| 久久免费精彩视频| 国产亚洲Av综合人人澡精品|