DirectX詳解
?DirectX是一套為Windows程序提供對系統(tǒng)硬件更緊密控制的組件。(表1列出了DirectX?5.0?
的組件及其作用)。那么,緊密控制是什么意思呢??
組件?用途?
DirectDraw?高速2D圖象?
DirectSound?短響應時間聲音輸出?
Direct3D?高速3D圖象?
DirectInput?面向游戲的對游戲桿和其它輸入設備的訪問?
DirectSetup?方便的安裝DirectX組件?
DirectPlay?面向游戲的通信和網(wǎng)絡支持?
DirectShow?視頻流支持?
DirectAnimation?動畫錄放支持?
DirectX提供的硬件控制常常被描述成底層控制,這會使人聯(lián)想起位操作和其它討厭的事?
情。實際上,DirectX組件包含許多高層API,使得象復制位圖和播放聲音等復雜的工作變?
得相當簡單。用"為程序提供比過去更好的對硬件的控制"來形容DirectX更準確。這在?
Windows中是一個顯著的特性,因為在Windows中,資源是共享的,并由操作系統(tǒng)控制。?
DirectX組件遵守稱為COM的二進制對象的工業(yè)標準。?
開始DirectX?
下面從DirectX的安裝開始講起。大多數(shù)情況下,某個好玩的游戲就會為系統(tǒng)安裝DirectX。?
為得到最新的版本,應該從最新的Microsoft?Platform?SDK中將DirectX安裝到系統(tǒng)中。?
可以在http://www.microsoft.com/msdn站點或者MSDN光盤中找到platform?SDK。缺省情?
況下,Microsoft?Platform?SDK被安裝到缺省驅(qū)動器根目錄下的\MSSDK目錄中。DirectX?
的頭文件安裝在\MSSDK\INCLUDE目錄中,Lib文件安裝在\MSSDK\LIB目錄中。?
Platform?SDK包含了一些非常好的DirectX例子和文檔。早期發(fā)布的DirectX?文檔非常粗?
略而且有些是錯誤的,現(xiàn)在的版本已經(jīng)極大地改正了這一問題。最好要熟悉這些文檔。?
現(xiàn)在已經(jīng)為安裝利用DirectX的程序做好了準備。所幸的是,不必一次就處理DirectX的全?
部功能。DirectX是一套可以分別使用的組件。實際上,在編程概念中,DirectX的不同部?
分互相沒有聯(lián)系。它們僅僅是具有相同的設計風格和目標:使Windows的游戲編程變得容易。?
使用DirectX組件的程序有什么特殊的地方嗎?根本沒有。使用DirectX組件的程序是基于?
Win32的程序,它們使用普通Win32?API集,并且可以訪問所有可以獲得的操作系統(tǒng)工具。?
實際上,DirectX既可以用于GUI程序,也可以用于控制臺程序。可以直接用Petzold-style?
SDK編程開發(fā)程序,也可以用基本類庫,如MFC。總的說,唯一的要求是大多數(shù)DirectX組?
件在程序中需要HWND,所以至少要有一個窗口。?
雖然DirectX組件是分離的,但是每個組件的實現(xiàn)風格和使用都是相同的。DirectInput是?
學習DirectX的非常好的出發(fā)點,原因是DirectInput是最簡單的組件之一。?
用力?
以后在游戲中要"用力",這是電影《星球大戰(zhàn)》中的說法,因為DirectInput中加入了相?
當令人陶醉的力反饋支持。DirectX?5.0以前,DirectInput支持從鼠標和鍵盤讀取輸入,?
這是一個有用但卻令人厭煩的特性。DirectX?5.0中,DirectInput被擴充到支持具有以物?
理力的形式向用戶傳播反饋的能力的設備。?
如果不能立即理解上面的內(nèi)容,下面就用一個游戲進行解釋。假設你剛啟動了你最喜歡的超?
現(xiàn)實3D越野賽車游戲,正手握力反饋游戲桿。在起跑線上,你可以聽到賽車引擎的空轉(zhuǎn)聲,?
同時也能夠通過游戲桿感覺到賽車引擎的空轉(zhuǎn)!比賽開始后,你可以感覺到引擎高速旋轉(zhuǎn)的?
嗡嗡震動。當行駛到賽程中崎嶇的地段時,你將會不停的感覺到電子碰撞。賽車在整個賽場?
上撞來撞去,你的游戲桿也會如此。賽車車輪卡在車轍中導致賽車被拉向左邊,游戲桿也會?
被拉向左邊!整個過程中你可以感覺到每次顛簸、刮擦、撞擊和撞毀。?
剖析DirectInput?
DirectInput由三個對象組成:DirectInput,?DirectInputDevice,?和DirectInputEffect?(見?
表2)。DirectInput是一個高層的對象,通過DirectInput對象可以對相關(guān)的輸入設備進行?
基本的初始化和查找。DirectInput對象最終用來創(chuàng)建低層的DirectInputDevice對象。?
DirectX中的每個主要組件都采用相同的方法,首先創(chuàng)建高層對象,如DirectInput或?
DirectSound對象,然后創(chuàng)建低層對象與硬件進行實際的通信。?
表2:?DirectInput對象?
對象?說明?
DirectInput?封裝高層DirectInput功能,列舉設備并用來創(chuàng)建DirectInputDevice對象。?
DirectInputDevice?與物理輸入設備的接口,例如游戲桿,包括收集和設置設備狀態(tài)信息的?
接口,并且用來創(chuàng)建DirectInputEffect對象?(對于力反饋設備)。?
DirectInputEffect?封裝能夠在力反饋設備上"播放"的簡單效果,提供啟動、停止和設置?
力反饋效果等功能。?
DirectInput對象是三個對象中最容易理解的。實際上,它在一個接口形式IDirectInput?(見?
表3)中只提供五個函數(shù)。這是DirectInput的一個非常重要的部分,因為這是出發(fā)點。?
表3:IdirectInput接口:?
成員函數(shù)?說明?
CreateDevice?創(chuàng)建一個DirectInputDevice對象并返回一個指向其IdirectInputDevice接?
口的指針。?
EnumDevices?為找到的與給定標準匹配的每個設備調(diào)用一個回調(diào)函數(shù),每個回調(diào)函數(shù)提供一?
個GUID,可以用在CreateDevice中創(chuàng)建DirectInputDevice對象。?
GetDeviceStatus?測試物理設備是否連接到系統(tǒng)。?
Initialize?如果DirectInput對象是使用CoCreateInstance創(chuàng)建的,那么在使用前必須調(diào)?
用Initialize成員。如果DirectInput對象是使用DirectInputCreate創(chuàng)建的,那么就已?
經(jīng)初始化過了。?
RunControlPanel?為設備運行Windows?Control?Panel程序,讓用戶安裝新設備或者更改已?
有設備的配置。?游戲桿校準可以在此處做。?
創(chuàng)建DirectInput對象?
為了創(chuàng)建DirectInput對象并得到其IdirectInput接口指針,應該在程序初始化階段使用?
兩種方法之一完成。?
第一種方法相當簡單。DirectX提供了一個助手函數(shù)DirectInputCreate來創(chuàng)建并初始化?
DirectInput對象。它與所有DirectInput的函數(shù)、接口和宏定義都在頭文件DINPUT.H中?
聲明。實際的函數(shù)體在DINPUT.LIB文件中。DirectInputCreate如下定義:?
HRESULT?WINAPI?DirectInputCreate(?
HINSTANCE?hinst,?
DWORD?dwVersion,?
LPDIRECTINPUT?*?lplpDirectInput,?
LPUNKNOWN?punkOuter?
);?
第一個參數(shù)是應用程序的實例。第二個參數(shù)是程序需要的DirectInput版本,通常使用?
DIRECTINPUT_VERSION宏,定義為當前版本。第三個參數(shù)最重要,如果對COM非常陌生的化?
就很難理解,它是指向IdirectInput接口的指針的地址。程序中應該定義一個LPDIRECTINPUT?
類型的變量(可以是全局的)并將其地址作為第三個參數(shù)傳遞給DirectInputCreate。?
最后一個參數(shù)叫作punkOuter,與COM技術(shù)中的聚合有關(guān),可以用NULL安全的忽略。返回?
值是一個HRESULT,是COM的標準返回類型,可以將返回值與可能的返回值比較,也可以使?
用COM宏定義SUCCESS或FAILED來檢查。?
使用DirectInputCreate能夠容易地創(chuàng)建高層對象并得到其主接口指針。這是DirectX的又?
一個設計方法,每個DirectX組件都提供助手函數(shù)來創(chuàng)建高層對象,例如DirectInputCreate?
或DirectDrawCreate。在程序中可以用這些助手函數(shù)創(chuàng)建DirectX對象,然而,這些函數(shù)?
實際上創(chuàng)建的是COM對象。這個工作也可以用叫作CoCreateInstance的標準Win32?API函?
數(shù)來完成。這就引出了創(chuàng)建DirectInput對象的第二中方法。?
在Win32中用CoCreateInstance創(chuàng)建COM對象非常普遍。如果程序中已經(jīng)使用?
CoCreateInstance創(chuàng)建了其他COM對象,開發(fā)者可能就會希望也用它來創(chuàng)建DirectX對象。?
因為COM對象在安裝時就在系統(tǒng)中注冊過,所以唯一需要知道的就是對象的GUID,用它來?
創(chuàng)建一個實例。創(chuàng)建DirectX對象需要的全部GUID都在頭文件中聲明,并在庫文件?
DXGUID.LIB中定義。可以將一個預定義的GUID傳遞給CoCreateInstance,讓Windows為你?
創(chuàng)建對象。CoCreateInstance定義如下:?
STDAPI?CoCreateInstance(?
REFCLSID?rclsid,?
LPUNKNOWN?pUnkOuter,?
DWORD?dwClsContext,?
REFIID?riid,?
LPVOID?*?ppv?
);?
第一個參數(shù)是要創(chuàng)建對象的GUID,DirectX定義的GUID是叫作CLSID_DirectInput的GUID?
結(jié)構(gòu)變量。第二個參數(shù)是熟悉的pUnkOuter,同樣可以用NULL忽略。第三個參數(shù)dwClsContext?
定義COM對象在何處創(chuàng)建,DirectX只支持進程內(nèi)服務器,所以必須使用?
CLSCTX_INPROC_SERVER。?
第四個參數(shù)是兩種方法真正的不同之處。記住COM對象對外提供接口,與對象本身一樣,接?
口也用GUID識別。使用第一種方法,不能選擇得到的接口,總是得到IdirectInput。使用?
CoCreateInstance可以請求對象所支持的任何接口,方法是使用為接口預定義的GUID。但?
是在DirectInput這是沒有意義的,因為DirectInput對象的唯一有用的接口就是?
IdirectInput。其它DirectX組件支持多個有用的接口。(例如,DirectDraw對象可以用?
IdirectDraw或IDirectDraw2接口操作。)?
最后一個參數(shù)是程序中接口指針變量的實際地址。?
現(xiàn)在就擁有了對象和對象的一個接口。CoCreateInstance方法還需要另外一步:必須要首?
先調(diào)用一個接口函數(shù)初始化對象。DirectInputCreate提供的是一個已經(jīng)初始化過的?
DirectInput對象,但CoCreateInstance沒有特定于DirectInput的認識,因此必須調(diào)用?
IdirectInput接口的初始化成員函數(shù)。?假設如下定義IdirectInput接口指針變量:?
LPDIRECTINPUT?g_lpDI?
可以如下調(diào)用初始化函數(shù):?
g_lpDI->Initialize(?hInstance,?DIRECTINPUT_VERSION);?
既然選擇采取這種標準方法創(chuàng)建對象,就不得不注意COM需要的其他標準,例如需要調(diào)用?
CoInitialize和CoUninitialize。?
使用DirectInput對象?
一旦擁有了DirectInput對象,就可以用它來創(chuàng)建DirectInputDevice對象,來管理系統(tǒng)中?
特定的設備。創(chuàng)建DirectInputDevice對象要使用CreateDevice函數(shù),它是作為?
IdirectInput接口一部分的五個函數(shù)之一。CreateDevice需要所請求設備的GUID,返回新?
DirectInputDevice對象的IdirectInputDevice接口指針。?
HRESULT?CreateDevice(?
REFGUID?rguid,?
LPDIRECTINPUTDEVICE?*lplpDirectInputDevice,?
LPUNKNOWN?pUnkOuter?
);?
這些內(nèi)容看起來很熟悉,因為它與CoCreateInstance和DirectInputCreate類似。但是,?
現(xiàn)在還沒有完全準備好開始DirectInputDevice對象,原因是在創(chuàng)建DirectInputDevice對?
象前需要該設備的GUID。?
DirectInput庫為創(chuàng)建DirectInputDevice對象預定義了兩個GUID:GUID_SysKeyboard和?
GUID_SysMouse。將兩者之一直接傳遞給CreateDevice函數(shù),就會得到相應設備的?
DirectInputDevice對象。?
注意,令人感到奇怪的是缺少對游戲桿的預定義GUID。在Windows中,通常都有系統(tǒng)鍵盤?
和系統(tǒng)鼠標,另一方面,系統(tǒng)本身并不使用游戲桿。可以安裝一個或者多個游戲桿,但系統(tǒng)?
管理的范圍只限于驅(qū)動程序級。系統(tǒng)并為這些設備指定特殊的系統(tǒng)狀態(tài),也不會在日常事務?
中使用這些設備。因此,為游戲桿定義GUID對DirectInput來說是不合理的。?
那么,如何才能找到與系統(tǒng)連接的游戲桿的GUID呢?要得到它們,必須要列舉設備。列舉?
系統(tǒng)設備和性能在DirectX中相當普遍。要列舉系統(tǒng)中的輸入設備,需要使用EnumDevices?
函數(shù)。EnumDevices是IdirectInput接口的一部分,如下定義:?
HRESULT?EnumDevices(?
DWORD?dwDevType,?
LPDIENUMCALLBACK?lpCallback,?
LPVOID?pvRef,?
DWORD?dwFlags?
);?
注意此函數(shù)與Windows中其它列舉API相同,例如EnumWindows。第二個參數(shù)是一個回調(diào)函?
數(shù)。第三個參數(shù)是程序中定義的32位值。第一個參數(shù)是想要列舉的設備類型,對游戲桿來?
說,是DIDEVTYPE_JOYSTICK(全部的設備類型列在表4中)。最后一個參數(shù)是詳細描述想要?
列舉的設備的標志。現(xiàn)在支持的標志是DIEDFL_ATTACHEDONLY和DIEDFL_ALLDEVICES(這兩?
個標志是互斥獨占的),此外還有DIEDFL_FORCEFEEDBACK,此標志表示力反饋設備,能夠和?
另兩個標志位或操作。?
圖4:定義列舉的輸入設備:?
以下定義的值可以傳遞給EnumDevices來選擇列舉哪種類型的輸入設備。另外也支持子類?
型,見SDK中DIDEVICEINSTANCE結(jié)構(gòu)的文檔。?
值?說明?
DIDEVTYPE_MOUSE?列舉鼠標設備?(標準、軌跡球等)?
DIDEVTYPE_KEYBOARD?列舉鍵盤設備?(標準、鍵區(qū)等)?
DIDEVTYPE_JOYSTICK?列舉游戲桿設備?(操縱桿、操縱輪、方向舵等)?
DIDEVTYPE_DEVICE?列舉其它設備?
當EnumDevices列舉系統(tǒng)中的輸入設備時,反復地調(diào)用回調(diào)函數(shù)。回調(diào)函數(shù)定義如下:?
BOOL?CALLBACK?EnumProc(LPCDIDEVICEINSTANCE?lpddi,?LPVOID?pvRef)?;?
因為回調(diào)函數(shù)是由用戶程序定義并傳遞給EnumDevices的,所以是調(diào)用CreateDevice的最?
合適地方,直到創(chuàng)建了滿足需要的足夠DirectInputDevice對象為止。但是回調(diào)函數(shù)并非一?
定要如此實現(xiàn),可以簡單的將列舉設備的所有GUID保存在一個表中,在以后的代碼中使用。?
回調(diào)函數(shù)接受兩個參數(shù)。第二個參數(shù)是程序定義的傳遞給EnumDevices的32位值。更重要?
的是,第一個參數(shù)傳遞指向一個結(jié)構(gòu)的指針,該結(jié)構(gòu)包含關(guān)于能夠與列舉標準匹配的單個設?
備的許多信息。這是一個DIDEVICEINSTANCE結(jié)構(gòu)。此結(jié)構(gòu)中最重要的一條信息是設備的?
GUID,保存在結(jié)構(gòu)的guidInstance成員中。?
當程序中完全完成DirectInput有關(guān)的工作后,就應該調(diào)用IdirectInput接口的Release?
成員。這就告訴DirectInput對象可以釋放自己了。在DirectX中,最好養(yǎng)成釋放對象的習?
慣,從低層對象開始,到高層對象結(jié)束。正常情況下程序會作為清除或者關(guān)閉的例行公事的?
一部分調(diào)用Release。這是使用每個DirectX組件的必要步驟,也是使用每個COM組件的必?
要步驟。?
現(xiàn)在已經(jīng)用CreateDevice成員函數(shù)獲得了DirectInputDevice對象的一個接口,為開始處?
理與系統(tǒng)連接的實際物理設備做好了準備。?
使用DirectInputDevice對象?
DirectInputDevice對象的每個實例都與系統(tǒng)中的特定設備相關(guān)。此對象提供了對系統(tǒng)硬件?
更多的控制和能力,從而使DirectX的允諾實現(xiàn)。下面討論擁有了DirectInputDevice對象?
后下一步干什么。?
擁有了IdirectInputDevice接口的一個接口指針,現(xiàn)在干什么?首先,設置設備的數(shù)據(jù)格?
式。通過調(diào)用SetDataFormat來完成,該函數(shù)是一個接口成員函數(shù)。設置數(shù)據(jù)格式包括無數(shù)?
可能的決定,包括軸信息、相對或絕對坐標信息、等等。所有這些細節(jié)通過一個叫作?
DIDATAFORMAT的結(jié)構(gòu)傳遞給此函數(shù)。實際上,SetDataFormat唯一的參數(shù)就是指向此結(jié)構(gòu)的?
指針。?
填寫這個結(jié)構(gòu)的細節(jié)會使人發(fā)憷。值得感謝的是這一工作并不是必須的,因為DirectInput?
已經(jīng)定義了幾個DIDATAFORMAT結(jié)構(gòu)變量,可以用于比較普通的輸入設備:c_dfDIKeyboard,?
c_dfDIMouse,?c_dfDIJoystick,?和c_dfDIJoystick2。為普通的力反饋游戲桿設置數(shù)據(jù)格?
式,可以使用下面的調(diào)用形式:?
lpdid->SetDataFormat(?&c_dfDIJoystick?)?;?
在此例中,lpdid是指向IdirectInputDevice接口的指針。?
設置完設備對象的數(shù)據(jù)格式后,就需要設置設備的協(xié)作級別。因為協(xié)作級別在整個DirectX?
中很常見,所以這里要做一下說明。大多數(shù)直接處理系統(tǒng)硬件的DirectX對象在接口的成員?
中都有一個叫作SetCooperativeLevel函數(shù)。這個函數(shù)很重要,因為它定義了程序操縱與系?
統(tǒng)中其它進程有關(guān)的硬件的控制級別。同其它DirectX對象一樣,只有設置了協(xié)作級別才能?
使DirectInputDevice對象工作。要理解協(xié)作級別,就需要熟悉Acquire函數(shù)。調(diào)用此函數(shù)?
是為了獲得對物理設備的實際訪問(不要和邏輯上的DirectInputDevice對象混了)。相反?
的,Unacquire函數(shù)釋放對物理設備的訪問。?
下面是函數(shù)SetCooperativeLevel的定義:?
HRESULT?SetCooperativeLevel(?
HWND?hwnd,?
DWORD?dwFlags?
);?
hwnd是程序的主窗口。標志是下面一些值的或操作的結(jié)合:?DISCL_BACKGROUND,?
DISCL_FOREGROUND,?DISCL_EXCLUSIVE,?DISCL_?NONEXCLUSIVE。?
如果標志參數(shù)中或上了DISCL_EXCLUSIVE,則當獲得設備后本程序就成為唯一允許訪問該物?
理設備的進程。另一方面,如果選擇了DISCL_NONEXCLUSIVE,那么系統(tǒng)中可以有多個進程?
同時協(xié)作獲得和使用該設備。如果或上了DISCL_BACKGROUND,程序?qū)⒉粫ノ锢碓O備。?
然而,象Ctrl+Alt+Del組合鍵被按下這樣的系統(tǒng)事件仍然能夠隱含地"unacquire"程序中?
的設備。如果使用了DISCL_?FOREGROUND,當不是活動窗口時,程序?qū)詣俞尫盼锢碓O備。?
這就是將程序主窗口句柄傳遞給SetCooperativeLevel的意義。DirectX根據(jù)窗口是否是系?
統(tǒng)當前活動窗口自動調(diào)整設備共享。?
那么所有這些值的意義是什么呢?下面舉個例子說明。如果力反饋游戲桿的協(xié)作模式是?
DISCL_FOREGROUND?|?DISCL_EXCLUSIVE,那么只要程序處于活動狀態(tài),就能夠從游戲桿讀數(shù)?
據(jù)并播放力反饋效果(力反饋需要exclusive-level協(xié)作)。只要用戶一選擇其它程序,程?
序就失去對物理設備的控制,新激活的程序就能夠訪問該設備。這意味著在調(diào)試程序時,如?
果切換到調(diào)試器窗口,程序就會因為窗口變?yōu)榉腔顒拥亩τ螒驐U的控制。?
如果將同一游戲桿的協(xié)作級別設為DISCL_BACKGROUND?|?DISCL_EXCLUSIVE將會是什么情況?
呢?程序?qū)袝r間都能訪問游戲桿,不管窗口的狀態(tài)。但是現(xiàn)在系統(tǒng)中其它進程就不能?
獲得游戲桿,除非程序釋放了游戲桿,不管用戶在做什么!?
非常明顯,在正式發(fā)布的產(chǎn)品中應該使用DISCL_FOREGROUND?|?DISCL_EXCLUSIVE,而在調(diào)?
試版本中應該使用DISCL_BACKGROUND|DISCL_EXCLUSIVE。但是也不總是這樣選擇。例如,?
如果設備是系統(tǒng)鍵盤,那么DirectInputDevice想獨占使用而調(diào)用SetCooperativeLevel將?
會失敗。這是因為操作系統(tǒng)想要允許用戶自由地從一個程序切換到另一個程序。類似的,?
DirectInputDevice不會允許以協(xié)作級別DISCL_BACKGROUND|DISCL_EXCLUSIVE請求系統(tǒng)鼠?
標。Windows不希望一個程序能夠完全將用戶與操作系統(tǒng)的聯(lián)系切斷。?
在能夠從物理設備讀取信息或向物理設備發(fā)送信息之前,必須要用Acquire獲得設備。在臨?
時或永久結(jié)束設備使用時要明確地使用Unacquire函數(shù)釋放設備。但Unacquire并不是失去?
設備控制的唯一方法。?
如果設置協(xié)作級別時使用DISCL_FOREGROUND標志,那么程序的主窗口不再是系統(tǒng)中的活動?
窗口時設備將被明確釋放。這就是說,在程序調(diào)用Acquire和實際試圖從設備讀取信息之間,?
能夠失去對設備的占有。所以需要檢查返回值來捕捉這樣的錯誤,并準備好在任何時間重新?
獲得該設備。?
關(guān)于Acquire和Unacquire的決定性要點:當程序獲得獨占協(xié)作級別的設備時,DirectX擁?
有該設備。例如,如果鼠標被DirectX(獨占)獲得,那么程序窗口中的按鈕就不會對鼠標?
做出響應。這就是說,如果想讓Windows對設備響應,就應該釋放該設備。換句話說,如果?
不想讓DirectInput從設備中讀取數(shù)據(jù),就調(diào)用Unacquire。?
設置完設備的協(xié)作級別后,接著應該為設備配置其它設置。獲得了設備后,接著就應該開始?
使用GetDeviceState函數(shù)輪流檢測輸入的數(shù)據(jù)。當完成與設備對象的操作后,調(diào)用Unacquire?
釋放DirectInputDevice對象。設備與設備之間存在細節(jié)上的差別;下面講解游戲桿和鍵盤,?
應該能為從其它設備讀取輸入提供足夠的基礎(chǔ)知識。?
鍵盤?
鍵盤是到目前為止最容易讀取的設備。實際上,設置完數(shù)據(jù)格式、協(xié)作級別、獲得設備以后,?
就可以讀取鍵盤狀態(tài)了。讀取鍵盤狀態(tài)要使用IdirectInputDevice接口的GetDeviceState?
成員。GetDeviceState用關(guān)于物理設備的狀態(tài)信息組裝一個結(jié)構(gòu),所組裝結(jié)構(gòu)的類型由前?
面對SetDataFormat的調(diào)用決定。對鍵盤來說,此數(shù)據(jù)結(jié)構(gòu)是一個簡單的256個字節(jié)組成的?
數(shù)組。每個字節(jié)對應于鍵盤上的一個鍵,如果某個鍵按下,相應字節(jié)的高位就被設置。?
DirectInput定義了一套以DIK_XXX為前綴的常量,這些常量可以用來索引字節(jié)數(shù)組以找到?
關(guān)于特定鍵的數(shù)據(jù)。例如,如果要檢查右Shif鍵當前是否按下,可以使用DIK_RSHIFT定義:?
GetDeviceState(256,(LPVOID)?cKeyboardData)?;?
if(cKeyboardData[DIK_?RSHIFT]&0x80)?
DoWhatever()?;?
CKeyboardData是256個字節(jié)的緩沖區(qū)。幾乎就是這么簡單,但是要記住,不管GetDeviceState?
在何時返回DIERR_INPUTLOST,就必須使用Acquire獲得設備。這種情況發(fā)生在每次用戶從?
程序切換離開的時候。?
還有一點很重要,就是能夠請求DirectInput緩沖鍵盤信息。這要求提供一個緩沖區(qū)并使用?
SetProperty為設備設置緩沖區(qū)大小。在本文中沒有篇幅討論這一技術(shù),但這一技術(shù)在程序?
不能相當頻繁的檢查鍵盤狀態(tài)時非常有用。用戶有可能在程序中兩次GetDeviceState調(diào)用?
之間按下又松開了一個鍵,如果DirectInput不緩沖鍵盤數(shù)據(jù)的化,這種擊鍵動作就丟失了。?
游戲桿?
游戲桿非常好玩。與其好聽的名稱(Joystick--原意為歡樂桿)相符,這種設備為游戲體?
驗添加了許多樂趣,同時也為程序員的體驗添加了一些東西。正常情況下,通過調(diào)用?
IdirectInput接口的CreateDevice成員得到IdirectInputDevice接口(和對象),這對游?
戲桿也適用。?
但是開發(fā)人員都希望立即將接口升級到IDirectInputDevice2,那么可以象下面這樣使用?
QueryInterface調(diào)用請求CreateDevice返回新的接口:?
hr?=?lpDIDeviceJoystickTemp->QueryInterface(?IID_IDirectInputDevice2,?
(void?**)?&g_lpDIDeviceJoystick);?
如果成功,就可以釋放原來的接口,開始使用漂亮的新IDirectInputDevice2接口。但是為?
什么要這么做?IDirectInputDevice2接口提供IdirectInputDevice的所有功能,而且還?
有另外兩個重要特性:支持查詢設備和支持力反饋設備。?
其次,需要設置上的一些考慮。還記得SetDataFormat定義了GetDeviceState返回的數(shù)據(jù)?
的類型。對于游戲桿設備,使用c_dfDIJoystick或c_dfDIJoystick2兩個預定義變量之一,?
將返回數(shù)據(jù)的類型設置為DIJOYSTATE或DIJOYSTATE2結(jié)構(gòu)。選擇哪種主要取決于要使用游?
戲桿哪種類型的特性。瀏覽這些結(jié)構(gòu)中的成員應該對弄清這個問題有幫助。?
同所有輸入設備一樣,要為游戲桿設置數(shù)據(jù)格式和協(xié)作級別。游戲桿往往比鍵盤需要更多一?
點注意。這是因為現(xiàn)在還幾乎沒有功能完美的游戲桿,所以程序應該檢查以確保控制的設備?
能滿足要求。如果不能,就調(diào)整要求或者提醒用戶游戲桿太落后!設備的能力可以并且應該?
調(diào)用IdirectInputDevice接口的成員函數(shù)GetCapabilities探測。?
這就引出了適用于所有DirectX組件的另一個討論點。DirectX為多種設備提供廣泛的支持。?
軟件開發(fā)環(huán)境和使用環(huán)境可能有很大差別,不同的計算機支持不同水平的DirectX功能。編?
寫好使用DirectX的軟件,需要檢查硬件的能力。最差的情況下,如果某個功能不支持,可?
以退出程序。最好的情況當然是程序能夠聰明地根據(jù)缺少的特性調(diào)整本身的需求。?
在開始從設備得到輸入之前,需要設置設備的特性。這些特性包括象返回值的范圍、游戲桿?
的中心點等此類的細節(jié)。這一工作由函數(shù)SetProperty完成,相當復雜。?
SetProperty設置設備的一個特性。首先,必須使用關(guān)于要改變的設置的一些信息填寫一個?
數(shù)據(jù)結(jié)構(gòu)。請參考Platform?SDK中的文檔,得到所有數(shù)據(jù)結(jié)構(gòu)。每個結(jié)構(gòu)都以一個?
DIPROPHEADER結(jié)構(gòu)開始,此結(jié)構(gòu)中填寫描述要改變的設置的信息。然后,用特定于所改變?
的設置的數(shù)據(jù)填寫結(jié)構(gòu)中剩余的部分。最后,調(diào)用SetProperty,參數(shù)是GUID和指向結(jié)構(gòu)?
中DIPROPHEADER部分的指針。下面的代碼片段將游戲桿的垂直范圍設置為-100到100:?
DIPROPRANGE?dipRange?;?
dipRange.diph.dwSize?=?sizeof(dipRange);?dipRange.diph.dwHeaderSize?=?
sizeof(dipRange.diph);?dipRange.diph.dwObj?=?DIJOFS_Y;?
dipRange.diph.dwHow?=?DIPH_BYOFFSET;?
dipRange.lMin?=?-100;?
dipRange.lMax?=?+100;?
g_lpDIDeviceJoystick->SetProperty(?DIPROP_RANGE,?&dipRange.diph)?;?
此結(jié)構(gòu)中最難懂的部分是diph.dwObj和diph.dwHow。diph.dwHow描述diph.dwObj中保存?
何種信息。diph.dwObj實際描述哪個屬性被設置。大多數(shù)情況下,diph.dwHow的值是?
DIPH_BYOFFSET,diph.dwObj的值是傳遞給SetDataFormat的結(jié)構(gòu)中一個預定義的偏移。?
應該指出能夠列舉設備的對象,包括按鈕和其它特點。這一工作由EnumObjects函數(shù)完成。?
這樣做時,應該提供一個對象標志符。將此標志符傳遞給diph.dwObj成員,將diph.dwHow?
成員填寫為DIPH_BYID。?
在從設備讀取數(shù)據(jù)之前,至少要為設備的X和Y坐標軸設置最小和最大值。設置好設備屬性?
后,就可以獲得設備并開始從設備獲得數(shù)據(jù)。從游戲桿獲取數(shù)據(jù)與從鍵盤或鼠標獲取數(shù)據(jù)不?
同,因為游戲桿是查詢設備。?
鍵盤和鼠標會引發(fā)硬件中斷,由系統(tǒng)中的驅(qū)動程序處理,并用來更新通過調(diào)用?
GetDeviceState由DirectInput返回的數(shù)據(jù)。查詢設備(如大多數(shù)游戲桿)不產(chǎn)生硬件中?
斷,因此,DirectInput必須被告知從設備獲取狀態(tài)信息。這一工作通過調(diào)用?
IDirectInputDevice2接口的Poll成員函數(shù)完成。此時也是檢查?設備是否需要重新獲得的?
適當時機。設備被成功查詢后,就可以調(diào)用GetDeviceState獲取狀態(tài)信息。?
如果調(diào)用SetDataFormat時使用c_dfDIJoystick變量,那么GetDeviceState將用游戲桿當?
前的狀態(tài)信息填充一個DIJOYSTATE結(jié)構(gòu)。此結(jié)構(gòu)的內(nèi)容主要取決于物理設備的特性和?
SetProperty的設置。例如,如果結(jié)構(gòu)中的lY成員等于-50,并且Y軸的范圍設置為-100到?
100,那么就是說游戲桿在垂直方向上處于中心和最頂端的中間。程序中應該確保設備的范?
圍設置為能合理滿足需求的值。為了從游戲桿設備中獲取數(shù)據(jù),程序應該定期查詢設備。?
使用DirectInputEffect?
首先,應該解釋一些力反饋技術(shù)。力反饋設備是能夠產(chǎn)生用戶可以感覺到的力的設備,這些?
力叫作效果,例如顛簸效果或者持續(xù)的將操縱桿推向右上方的力。這些效果是“播放?
”出來的,效果由程序控制播放,或者對函數(shù)調(diào)用響應,或者對用戶按鍵自動反應。?
DirectInput目前支持大約一打不同的效果類型(見表5)。這些效果的范圍從完全由程序控?
制的低級持續(xù)力效果,到由DirectInput或設備自己控制的高級傾斜或波動效果。效果有四?
種基本類型:持續(xù)力、傾斜效果、周期效果和條件。持續(xù)力是單一方向上不改變強度的力。?
傾斜效果是強度隨時間線性變化的持續(xù)的力。周期效果是沿著給定的軸重復變化,其量級或?
者力的強度由周期效果定義。條件是對用戶與游戲桿的交互作用做出響應的效果。這種效果?
可能是象一根彈簧,操縱桿向某個方向推得越遠,反彈力就越強。?
表5:DirectInput效果的類型?
GUID?說明?使用方法注解?
GUID_ConstantForce?固定強度、特定方向的持續(xù)拉力。?使用DICONSTANT力結(jié)構(gòu)作為?
DIEFFECT結(jié)構(gòu)的一部分實現(xiàn)持續(xù)力。?
GUID_CustomForce?一序列持續(xù)力下傳到設備,按順序播放。?DICUSTOMFORCE結(jié)構(gòu)被用來定?
義力。?
GUID_Damper?隨沿坐標軸的移動增加的條件效果。?實現(xiàn)這種效果的特定類型結(jié)構(gòu)是?
DICONDITION結(jié)構(gòu)。條件效果通常不支持包。?
GUID_Friction?阻礙沿坐標軸移動的條件效果。?實現(xiàn)這種效果的特定類型結(jié)構(gòu)是?
DICONDITION結(jié)構(gòu)。條件效果通常不支持包。?
GUID_Inertia?隨沿坐標軸移動的加速度增加的條件效果。?實現(xiàn)這種效果的特定類型結(jié)構(gòu)是?
DICONDITION結(jié)構(gòu)。條件效果通常不支持包。?
GUID_RampForce?特定方向上大小線性增加或減小的拉力。?DIRAMPFORCE結(jié)構(gòu)被用來作為?
DIEFFECT結(jié)構(gòu)中的類型相關(guān)部分。?
GUID_SawtoothDown?力瞬間達到最大然后線性減小到最小的周期效果。?需要的特定類型結(jié)?
構(gòu)是DIPERIODIC結(jié)構(gòu)。?
GUID_SawtoothUp?力從最小線性增加到最大然后瞬間降到最小的周期效果?需要的特定類型?
結(jié)構(gòu)是DIPERIODIC結(jié)構(gòu)。?
GUID_Sine?力正弦變化的周期效果。?需要的特定類型結(jié)構(gòu)是DIPERIODIC結(jié)構(gòu)。?
GUID_Spring?力隨到某個中點的相對距離而增大的條件效果。?實現(xiàn)這種效果的特定類型結(jié)?
構(gòu)是DICONDITION結(jié)構(gòu)。條件效果通常不支持包。?
GUID_Square?力瞬時在最大與最小之間轉(zhuǎn)變的周期效果。?需要的特定類型結(jié)構(gòu)是DIPERIODIC?
結(jié)構(gòu)。?
GUID_Triangle?力在最大與最小之間線性變化的周期效果。?需要的特定類型結(jié)構(gòu)是?
DIPERIODIC結(jié)構(gòu)。?
下面所有與力反饋游戲桿有關(guān)的工作都是針對Microsoft?SideWinder?Force?Feedback?Pro?
游戲桿,這就是說,本文中的某些細節(jié)對其它設備可能多少會產(chǎn)生一些問題。?
在創(chuàng)建力反饋效果以前先獲得設備是一個不錯的想法。雖然這不是必須的,但是在效果能夠?
被下傳到設備前必須要獲得設備。這一點對于播放對用戶按下按鈕做出反應的力效果尤其重?
要。?
要創(chuàng)建效果,首先要為每個打算使用的效果創(chuàng)建DirectInputEffect對象的實例。這一工作?
通過調(diào)用IDirectInputDevice2接口的CreateEffect成員函數(shù)完成。此函數(shù)需要效果的?
GUID,以及指向DIEFFECT結(jié)構(gòu)的指針,該結(jié)構(gòu)中填寫的是效果的細節(jié)。最后,CreateEffect?
返回一個指向IdirectInputEffect接口的指針,該指針的地址是CreateEffect的一個參數(shù)。?
這個調(diào)用的核心部分集中在DIEFFECT結(jié)構(gòu)的填充。?
DIEFFECT結(jié)構(gòu)如下定義:?
typedef?struct?{?
DWORD?dwSize;?
DWORD?dwFlags;?
DWORD?dwDuration;?
DWORD?dwSamplePeriod;?
DWORD?dwGain;?
DWORD?dwTriggerButton;?
DWORD?dwTriggerRepeatInterval;?
DWORD?cAxes;?LPDWORD?rgdwAxes;?
LPLONG?rglDirection;?
LPDIENVELOPE?lpEnvelope;?
DWORD?cbTypeSpecificParams;?
LPVOID?lpvTypeSpecificParams;?
}?DIEFFECT,?*LPDIEFFECT;?
dwSize成員是此結(jié)構(gòu)的字節(jié)數(shù)。DwFlags指出效果使用的坐標類型,以及是使用偏移方法還?
是ID方法描述按鈕(就向前面說明的SetProperty)。通常情況下,可以設置為?
DIEFF_CARTESIAN|DIEFF_OBJECTOFFSETS,即按鈕采用偏移描述,坐標使用XYZ坐標形式。?
DwDuration說明效果播放多少毫秒。注意dwDuration可以設為INFINITE。DwSamplePeriod?
說明效果播放一個周期花費多少毫秒。不同設備支持不同的周期。實際中,SideWinder游?
戲桿支持的周期不大于1秒,不小于1/80秒。DwGain可以看作效果的主要量,因為它說明?
效果多么有力。此值的范圍是0到10000。?
DwTriggerButton和dwTriggerRepeatInterval用來設置觸發(fā)效果播放的按鈕,以及重復頻?
率。當然,可以通過將dwTriggerButton的值設置為DIEB_NOTRIGGER來將效果設置為與按?
鈕無關(guān)。否則,dwFlags定義通過ID還是偏移方式描述按鈕。因為偏移方式不需要調(diào)用?
EnumObjects,所以一般可以將值指定為DIJOFS_?BUTTON0和DIJOFS_BUTTON1。?
CAxes成員說明效果將影響幾個軸。RgdwAxes指向一個描述所包含的軸的DWORD數(shù)組,數(shù)組?
中每個軸是一個成員。同按鈕一樣,軸也是用偏移或者ID來指明。一般的偏移值包括DIJOFS_X?
和DIJOFS_Y。?
同樣,rglDirection成員指向一個long型數(shù)組,每個軸是一個成員。在笛卡兒坐標中,?
(Y=-1,X=1)與(Y=-10,X=10)描述的是同一個方向。這就是說,如果想得到一個不是45?
度整數(shù)倍方向上的斜的力,就應該調(diào)整兩個值的相對大小。例如,(Y=-10,X=1)描述與上?
面例子在同一象限的方向,但卻明顯靠近Y軸。?
效果也可以有描述它們的包。填充一個DIENVELOPE結(jié)構(gòu),并將其地址填寫到lpEnvelope成?
員就可以完成。包可以在一段時間內(nèi)影響效果的數(shù)量或力量。其中,起動水平是效果的開始?
變化點,啟動時間說明效果達到力量保持階段花費多少毫秒。衰減水平是效果在包最后達到?
的水平,衰減時間是衰減用掉了多少毫秒。包可以用來制造初始狀態(tài)較強,然后慢慢衰減的?
力效果。圖1中描繪了包如何改變效果。?
DIEFFECT結(jié)構(gòu)的最后兩個成員是cbTypeSpecificParams和lpvTypeSpecificParams。它們?
保存特定于所創(chuàng)建效果類型的結(jié)構(gòu)的字節(jié)數(shù)和地址。特定類型的效果使用何種結(jié)構(gòu)的信息見?
表5。?
填寫完這個結(jié)構(gòu)并調(diào)用CreateEffect后,就會獲得指向IdirectInputEffect接口的指針,?
現(xiàn)在可以使用此接口播放效果,改變效果等。如果沒有將效果聯(lián)系到按鈕,就必須用?
IdirectInputEffect接口的Start和Stop成員播放和停止效果。如果效果與按鈕關(guān)聯(lián),那?
么在創(chuàng)建時下傳到設備;否則,效果在播放時自動下傳到設備。如果程序必須重新獲得設備,?
那么所有與按鈕相關(guān)的效果必須通過明確的調(diào)用Download成員才能下傳到設備。?
效果能夠用Unload成員卸載,也能夠通過向SetParameters成員函數(shù)傳遞新的DIEFFECT結(jié)?
構(gòu)重新設置參數(shù)。當程序用完效果后,必須調(diào)用接口的Release成員。?
?
posted on 2006-06-20 16:21 楊粼波 閱讀(743) 評論(0) 編輯 收藏 引用 所屬分類: 文章收藏