在本篇文章中,我們一起詳細探索了DirectInput這套在PC游戲即時控制方面一手遮天的API。下面先來看一下這篇一萬多字文章的大體脈絡。首先我們對DirectInput接口進行了整體上的講解,然后深入DirectInput的使用步驟進行具體的探索,最后淺出,歸納出DirectInput使用五步曲,方便大家的快速掌握。文章最后,我們配了一個比較好玩的demo來讓大家對本篇文章所學的DirectInput的使用融會貫通,最后提供了這個demo詳細注釋的源代碼下載。先放一個demo的截圖吊吊大家胃口,哈哈:
一、引言
眾所周知,在普通的Windows程序中,用戶通過鍵盤或者鼠標輸入的消息並不是應用程序直接處理的,而是通過Windows的消息機制轉發給Windows操作系統的。Windows操作系統對這些消息進行響應后,在通過回調應用程序的窗口過程函數進行相應的消息處理。
這顯然滿足不了對於性能要求比較苛刻的游戲程序的。在DirectX中,微軟為我們提供了名為DirectInput接口對象來實現用戶輸入的。DirectInput直接和硬件驅動打交道,因此處理起用戶的輸入來說非常迅速。
首先需要給大家說明的是,DirectInput這套API自DirectX8更新以來,功能已經足夠完善了。所以盡管當前DirectX的最新版本上升到了DirectX 11,DirectInput還是DirectX 8那個版本時代的老樣子,API的內容和功能隨着最近幾個版本的更迭卻原封不動,名稱上也保留了8這個版本號,依然叫DirectInput 8,可謂以不變應萬變。即目前最新版本的DirectInput ,依舊是DirectInput 8。
二、DirectInput接口概述
DirectInput作為DirectX的組件之一,自然依然是一些COM對象的集合。DirectInput由IDirectInput8、IDirectInputDevice8,IDirectInputEffect這三個接口組成,這三個接口中又分別含有各自的方法。
總的來說,當前版本的DirectInputAPI中,三個接口,四十七個方法,組成了這個在電腦游戲開發中不可或缺的組件。
由於IDirectInput8 API整體來說規模不大,說白了也就是三個接口,四十七個方法,不妨我們在文章中將他們一一列舉出來,也讓大家窺一窺DirectInput API的全貌。
1.IDirectInput8接口 函數一覽
IDirectInput8::ConfigureDevices Method
IDirectInput8::CreateDevice Method
IDirectInput8::EnumDevices Method
IDirectInput8::EnumDevicesBySemantics Method
IDirectInput8::FindDevice Method
IDirectInput8::GetDeviceStatus Method
IDirectInput8::Initialize Method
IDirectInput8::RunControlPanel Method
2.IDirectInputDevice8接口 函數一覽
IDirectInputDevice8::Acquire Method
IDirectInputDevice8::BuildActionMap Method
IDirectInputDevice8::CreateEffect Method
IDirectInputDevice8::EnumCreatedEffectObjects Method
IDirectInputDevice8::EnumEffects Method
IDirectInputDevice8::EnumEffectsInFile Method
IDirectInputDevice8::EnumObjects Method
IDirectInputDevice8::Escape Method
IDirectInputDevice8::GetCapabilities Method
IDirectInputDevice8::GetDeviceData Method
IDirectInputDevice8::GetDeviceInfo Method
IDirectInputDevice8::GetDeviceState Method
IDirectInputDevice8::GetEffectInfo Method
IDirectInputDevice8::GetForceFeedbackState Method
IDirectInputDevice8::GetImageInfo Method
IDirectInputDevice8::GetObjectInfo Method
IDirectInputDevice8::GetProperty Method
IDirectInputDevice8::Initialize Method
IDirectInputDevice8::Poll Method
IDirectInputDevice8::RunControlPanel Method
IDirectInputDevice8::SendDeviceData Method
IDirectInputDevice8::SendForceFeedbackCommand Method
IDirectInputDevice8::SetActionMap Method
IDirectInputDevice8::SetCooperativeLevel Method
IDirectInputDevice8::SetDataFormat Method
IDirectInputDevice8::SetEventNotification Method
IDirectInputDevice8::SetProperty Method
IDirectInputDevice8::Unacquire Method
IDirectInputDevice8::WriteEffectToFile Method
3.IDirectInputEffect接口 函數一覽
IDirectInputEffect::Download Method
IDirectInputEffect::Escape Method
IDirectInputEffect::GetEffectGuid Method
IDirectInputEffect::GetEffectStatus Method
IDirectInputEffect::GetParameters Method
IDirectInputEffect::Initialize Method
IDirectInputEffect::SetParameters Method
IDirectInputEffect::Start Method
IDirectInputEffect::Stop Method
IDirectInputEffect::Unload Method
其中,IDirectInput8作為DirectInput API中最主要的接口,用於初始化系統以及創建輸入設備接口,DirectInput中其他的所有的接口都需要依賴於我們的IDirectInput8之上,都是通過這個接口進行查詢的。而DirectInputDevice8接口用於表示各種輸入設備(如鍵盤、鼠標和游戲桿),並提供了相同的訪問和控制方法。對於某些輸入設備(如游戲桿和鼠標),都能通過查詢各自的IDirectInputDevice8接口對象,得到另一個接口IDirectInputEffect8。而IDirectInputEffect8接口則用於控制設備的力反饋效果。
三、DirectInput使用步驟詳解
1.頭文件和庫文件的包含
我們首先需要注意的是,在使用DirectInput時,需要保證我們包含了DInput.h頭文件,並且在項目中已經鏈接了DInput8.lib庫文件。
當然,庫文件我們也可以動態添加:
#pragma comment(lib, "dinput8.lib") // 使用DirectInput必須包含的頭文件,注意這里有8
2.創建DirectInput接口和設備
在DirectInput中我通過們調用DirectInputCreate函數創建並初始化IDirectInput接口,我們可以在MSDN中查到該函數的聲明如下:
1 HRESULT DirectInput8Create( 2 3 HINSTANCE hinst, 4 5 DWORD dwVersion, 6 7 REFIID riidltf, 8 9 LPVOID * ppvOut, 10 11 LPUNKNOWN punkOuter 12 13 )
■ 第一個參數,HINSTANCE類型的hinst,表示我們當前創建的DirectInput的Windows程序句柄,這個值填我們在WinMain函數的參數中的實例句柄就可以了。
■ 第二個參數,DWORD類型的dwVersion,表示我們當前使用的DirectInput版本號,通常可以取DIRECTINPUT_VERSION或者DIRECTINPUT_HEADER_VERSION,這兩個值對應的是同一個值,為0x0800。所以我們在這里還可以直接填0x0800。
歸根揭底的話,可以通過【轉到定義】大法在dinput.h中查到有如下代碼:
1 #define DIRECTINPUT_HEADER_VERSION 0x0800 2 3 #ifndef DIRECTINPUT_VERSION 4 5 #define DIRECTINPUT_VERSION DIRECTINPUT_HEADER_VERSION
大體意思很清楚了吧,先定義一下DIRECTINPUT_HEADER_VERSION=0x0800,然后再說如果沒有定義DIRECTINPUT_VERSION的話,就定義一個DIRECTINPUT_VERSION= DIRECTINPUT_HEADER_VERSION。
■ 第三個參數,REFIID類型的riidltf,表示接口的標志,通常取IID_IDirectInput8就可以了。
■ 第四個參數,LPVOID 類型的* ppvOut,用於返回我們新創建的IDirectInput8接口對象的指針。
■ 第五個參數,LPUNKNOWN類型的punkOuter,一個和COM對象接口相關的參數,通常我們設為NULL就可以了。
這個函數執行成功的話TINPUTVER會返回HRESULT類型的DI_OK,而失敗的話根據不同的調用失敗原因,會返回DIERR_BETADIRECSION,DIERR_INVALIDPARAM,DIERR_OLDDIRECTINPUTVERSION, DIERR_OUTOFMEMORY中的一個。所以我們可以根據FAILED宏來判斷我們IDirectInput8接口對象是否創建成功了。
下面是一個調用的例子:
1 // 創建DirectInput設備 2 3 LPDIRECTINPUT8 g_pDirectInput = NULL; 4 5 if(FAILED(DirectInput8Create(hInstance, 0x0800, IID_IDirectInput8,( void**)&g_pDirectInput, NULL))) 6 7 return E_FAIL;
這步完成之后,咱們的定義的DIRECTINPUT8接口對象g_pDirectInput就有了權利,新官上任了。
在IDirectInput8接口中包含了很多用於初始化輸入設備及獲得設備接口的方法。其中,常用的方法為EnumDevices和CreateDevices。前者EnumDevices用於獲得輸入設備的類型,而后者CreateDevices用於為輸入設備創建IDirectInputDevice8接口對象。
系統中每一個已安裝的設備都有一個系統分配的全局唯一標示符(GUID,Global Unique Identification),從英文單詞意義上就可以知道,系統中的每個設備都有着獨一無二的GUID,這個GUID又唯一的標志了系統中的某某設備。就像我們每個人都有着獨一無二的的身份證號碼。
要使用某個設備的話,首先我們就需要知道他的GUID。
鼠標和鍵盤作為我們電腦中最為重要的外設,DirectInput對他們做了特殊對待,給了后門,定義了他們的GUID分別為GUID_Keyboard和GUID_SysMouse。而對於其他的輸入設備,我們就用上面提到過的EnumDevices方法枚舉出這些設備,以得到他們的GUID,我們可以在MSDN中查到這個方法有如下聲明:
1 HRESULTEnumDevices( 2 3 DWORD dwDevType, 4 5 LPDIENUMDEVICESCALLBACKlpCallback, 6 7 LPVOID pvRef, 8 9 DWORD dwFlags 10 11 )
■ 第一個參數,DWORD類型的dwDevType,指定我們需要枚舉的設備類型。
可取的值為DI8DEVCLASS_ALL,DI8DEVCLASS_DEVICE,DI8DEVCLASS_GAMECTRL,DI8DEVCLASS_KEYBOARD,DI8DEVCLASS_POINTER中的一個。
■ 第二個參數,LPDIENUMDEVICESCALLBACK類型的lpCallback,用於指定一個回調函數的地址,當系統中每找到一個匹配的設備時,就會自動調用這個回調函數。
■ 第三個參數,LPVOID類型的pvRef,返回我們當前匹配設備的GUID值。
■ 第四個參數,DWORD類型的dwFlags,指定我們枚舉設備的方式。取值可以下面的一個或者多個值:DIEDFL_ALLDEVICES,DIEDFL_ATTACHEDONLY,DIEDFL_FORCEFEEDBACK,DIEDFL_INCLUDEALIASES,DIEDFL_INCLUDEHIDDEN,DIEDFL_INCLUDEPHANTOMS。
取得我們需要使用的設備的GUID后,就可以根據這個GUID來調用IDirectInput8接口的CreateDevice方法,進而來創建設備的IDirectInputDevice8接口對象了。
我們可以在MSDN中查到IDirectInput8::CreateDevice方法的聲明如下:
1 HRESULTCreateDevice( 2 3 REFGUID rguid, 4 5 LPDIRECTINPUTDEVICE*lplpDirectInputDevice, 6 7 LPUNKNOWN pUnkOuter 8 9 )
■ 第一個參數,REFGUID類型的rguid,就是填我們上面講到的輸出設備的GUID。系統中當前使用的鍵盤對應GUID_SysKeyboard,當前使用的鼠標對應GUID_SysMouse。其他設備的話,就用我們剛剛講過的EnumDevices獲取一下就行了。
■ 第二個參數,LPDIRECTINPUTDEVICE類型的*lplpDirectInputDevice,表示我們所創建的輸入設備對象的指針地址,可以說調用這個CreateDevice參數就是在初始化這個參數。
■ 第三個參數,LPUNKNOWN類型的pUnkOuter,、和COM對象的IUnknown接口相關的一個參數,一般我們不去管它,設為NULL就可以了。
講解完了,當然得看一個調用實例。下面的代碼中CreateDevice方法的第二個參數我們填的是GUID_SysMouse,所以我們在為系統鼠標創建一個DirectInput設備接口對象:
1 LPDIRECTINPUTDEVICE8 g_pMouseDevice = NULL; 2 3 if(FAILED (g_pDirectInput->CreateDevice(GUID_SysKeyboard,&g_pKeyboardDevice, NULL))) 4 5 return E_FAIL;
3.設置數據格式
數據格式用於表示設備狀態信息的存儲方式,每種設備都有一種用於讀取對應數據的特定數據格式,所以對每種設備都要區別對待。所以要使程序從設備讀入數據的話,首先我們需要告訴DirectInput讀取這種數據所采用的格式。
設置數據格式通常我們都是通過IDirectInputDevice8接口的SetDataFormat方法來做到的,這個方法可以把設備的數據格式填充到一個DIDATAFORMAT接口類型的對象。該方法的聲明如下:
HRESULT SetDataFormat( LPCDIDATAFORMAT lpdf )
SetDataFormat方法唯一的變量就是LPCDIDATAFORMAT類型的lpdf,DirectInput已經為我們准備好了一些備選的參數,下面是一個列表:
數據格式
精析
c_dfDIkeyboard
標准鍵盤結構,包含256個字符,每個字符對應着每個鍵
c_dfDIMouse
標准鼠標結構,帶有3個軸和4個按鈕
c_dfDIMouse2
擴展鼠標結構,帶有3個軸和8個按鈕
c_dfDIJoystick
標准游戲桿,帶有三個定位軸,3個旋轉軸,兩個滑塊,1個POV hat和32個按鈕
c_dfDIJoystick2
擴展的游戲桿
依然是一個調用實例,設置鼠標的數據格式:
g_pMouseDevice->SetDataFormat(&c_dfDIMouse);
4.設置協作級別
在Windows操作系統中,系統中的每個應用程序都通常會使用多個輸入設備,並且同一輸入設備也可能被多個應用程序同時使用。因此,需要一種方式來共享和協調應用程序對設備的訪問。在DirectInput中,祭出的是協作級別(Cooperative Level)這套處理方式。
協作級別定義了進程與其他應用程序和操作系統共享設備的方式。設備一旦創建就需要設置它的協作級別,協作級別表示了應用程序對設備的控制權。
DirectInput的協作級別可以以兩套方案來分類:前台、后台模式和共享、獨占模式。
Ⅰ.前台模式與后台模式
其中,前台模式表示只有當窗口處於激活狀態時,才能獲得設備的控制權。而當處於非激活狀態時,會自動失去設備的控制權;后台模式表示可以在任何狀態下獲取設備,即使是在窗口處於非激活狀態時。后天模式可以被任何應用程序在任何時候使用並獲取設備數據。
Ⅱ.共享模式與獨占模式
共享模式表示多個應用程序可以共同使用該設備,而獨占模式表示應用程序是唯一使用該設備的應用程序。這里需要注意一下,獨占模式並非意味着其他應用程序不能獲取輸入設備狀態,如果進程同時使用了后台模式與獨占模式的話,當其他進程申請了獨占模式的話,這個進程就會失去設備的控制權。
我們平常都是通過IDirectInputDevice8接口的SetCooperativeLevel方法來設置設備的協作級別的,我們可以在MSDN中查到SetCooperativeLevel的聲明如下:
HRESULT SetCooperativeLevel( HWND hwnd, DWORD dwFlags )
■ 第一個參數,HWND類型的hwnd,顯然就是填想要與當前設備相關聯的窗口句柄了,且這個窗口需要屬於當前進程的頂級窗口。
■ 第二個參數,DWORD類型的dwFlags,描述了當前設備的協作級別類型,也就是填我們上面講到的前台、后台模式和共享、獨占模式等一些模式的標識符,可取一個值到多個值,淺墨把取值在下表中出來了:
協作級別類型
精析
DISCL_BACKGROUND
后台模式,一般我們讓他與DISCL_NONEXCLUSIVE(非獨占模式)配合使用
DISCL_FOREGROUND
前台模式,一般我們讓他與DISCL_EXCLUSIVE(獨占模式)配合使用
DISCL_EXCLUSIVE
獨占模式
DISCL_NONEXCLUSIVE
非獨占(共享)模式
DISCL_NOWINKEY
讓鍵盤上煩人的Windows鍵失效
注意,后台模式和獨占模式不能同時選擇,用腳丫子來想都知道他們兩個組合起來不符合邏輯,既然都是在后台了,還談什么獨占呢?
下面依舊是一個調用實例,將鼠標設備的協作級別設為前台、獨占模式:
g_pMouseDevice->SetCooperativeLevel(hwnd,DISCL_FOREGROUND |DISCL_EXCLUSIVE);
5.設置特殊屬性
設備的特殊屬性包含設備的數據模式、緩沖區大小、以及設備的最小最大范圍等等。DirectInput為我們提供了SetProperty方法來設置設備的特殊屬性,我們可以在MSDN中查到這個方法有如下原型:
HRESULT SetProperty( REFGUID rguidProp, LPCDIPROPHEADER pdiph )
這個方法平常用得不算多,因為篇幅原因暫且先不詳細講了,需要用的時候大家去查一下文檔就可以了。
6.獲取和輪詢設備
首先是一個常識,在訪問和使用任何輸入設備之前,首先必須獲得該輸入設備的控制權。權力這東西,人人都喜歡,對其趨之若鶩,在我們的計算機中也不例外。其他的程序隨時都可能勾心斗角,爭奪並搶走對輸入設備的控制權。所以我們在使用之前,往往都要重新獲取一下設備的控制權,以確保權力在我們手中。
在DirectInput中,權力的敲門磚為IDirectInput8接口的Acquire方法,我們可以在MSDN中查到這個“權力權杖”有如下的原型:
HRESULT Acquire()
我們可以發現他簡簡單單清清白白,沒有參數,返回值為HRESULT。調用起來當然是非常簡單:
g_pMouseDevice->Acquire();
為了大家看起來簡明扼要,咱們這里沒有用if和FAILD宏給他括起來,進行錯誤處理。
另外需要注意的是,在獲得輸入設備的控制權之前,必須先調用IDirectInputDevice8接口的SetDataFormat或者SetActionMap方法來設置一下數據格式,不然我們調用Acquire方法的話,將直接給我們返回DIERR_INVALIDPARAM錯誤的。
另外需要講到的是輪詢。
輪詢可以准備在合適的情況下讀取設備數據。因為數據可能具有臨界時間的。這個輪詢的原型也是非常非常的簡單:
HRESULT Poll()
輪詢用起來當然也是非常簡單的:
g_pMouseDevice ->Poll();
7.讀取設備信息
在Direct3D應用程序中,拿到對輸入設備的控制權之后,就可調用IDirectInputDevice8接口的GetDeviceState方法來讀取設備的數據。而為了存儲設備的數據信息,在調用該方法時,須傳遞一個數據緩沖區給GetDeviceState方法,這個GetDeviceState方法的原型我們可以在MSDN中查到是如下:
HRESULT GetDeviceState( DWORD cbData, LPVOID lpvData )
■ 第一個參數,DWORD類型的cbData,指定了我們緩沖區的大小(具體是哪個緩沖區在第二個參數中)。
■ 第二個參數,LPVOID類型的lpvData,表示一個獲取當前設備狀態的結構體的地址值。
他的數據格式和我們之前調用的IDirectInputDevice8::SetDataFormat方法有着前后呼應的密切聯系。下面我們通過一個表格來看看是如何聯系的:
SetDataFormat中指定的數據格式
GetDeviceState中對應的緩沖區結構體
c_dfDIMouse
DIMOUSESTATE結構體
c_dfDIMouse2
c_dfDIKeyboard
大小為256個字節的數組
c_dfDIJoystick
DIJOYSTATE結構體
c_dfDIJoystick2
DIJOYSTATE2結構體
比如,我們先調用了SetDataFormat設置了設備的數據格式為c_dfDIMouse:
g_pMouseDevice->SetDataFormat(&c_dfDIMouse);
那么我們在讀取設備信息的時候調用GetDeviceState就需要把第二個參數填與dfDIMouse對應的DIMOUSESTATE結構體的一個實例:
DIMOUSESTATE dimouse g_pMouseDevice-> GetDeviceState( sizeof(dimouse),(LPVOID)&dimouse);
對此,我們可以抽象出一個函數,專門對付疑難雜症,應對各種類型的設備的數據讀取,而且還考慮到了設備如果丟失掉了,在合適的時間自動重新獲取該設備:
1 //***************************************************************************************** 2 3 // Name: Device_Read(); 4 5 // Desc: 智能讀取設備的輸入數據 6 7 //***************************************************************************************** 8 9 BOOL Device_Read(IDirectInputDevice8*pDIDevice, void* pBuffer, longlSize) 10 11 { 12 13 HRESULThr; 14 15 while( true) 16 17 { 18 19 pDIDevice->Poll(); // 輪詢設備 20 21 pDIDevice->Acquire(); // 獲取設備的控制權 22 23 if(SUCCEEDED(hr = pDIDevice->GetDeviceState(lSize, pBuffer))) break; 24 25 if(hr !=DIERR_INPUTLOST || hr != DIERR_NOTACQUIRED) return FALSE; 26 27 if(FAILED(pDIDevice->Acquire())) return FALSE; 28 29 } 30 31 returnTRUE; 32 33 }
到這一步之后,就是調用一下Device_Read來讀取數據了。調用之后,我們的鍵位數據其實就存在了g_pKeyStateBuffer之中,我們接下來要做的就是用if語句對g_pKeyStateBuffer數組中對應的鍵位進行試探,看看這個鍵是否被按下了。如果按下,就進行相關的處理就可以了,比如:
1 Device_Read(g_pKeyboardDevice,(LPVOID)g_pKeyStateBuffer, sizeof(g_pKeyStateBuffer)); 2 3 4 5 if (g_pKeyStateBuffer[DIK_A] & 0x80) 6 7 fPosX -= 0.005f;
當然,在最后,使用完輸入設備后,必須調用IDirectInputDevice8接口的Unacquire方法釋放設備的控制權,所謂的杯酒釋兵權,且需要接着調用Release方法釋放掉設備接口對象。
g_pMouseDevice->Unacquire(); g_pMouseDevice->Release();
四、精煉:DirectInput使用五步曲
上面講解了洋洋灑灑七千字,信息量有些大,為了突出下重點,落實到一個字“用”上,讓大家有的放矢,快速掌握DirectInput的使用方法。淺墨在這里依舊是來一個使用幾步曲的歸納,主要以代碼為載體,把上面講得知識歸納一下。這回的DirectInput同樣是五步曲。需要說明下的是,下面的代碼是關於處理鍵盤消息的,而對於鼠標設備,需要改的地方非常少,也就是在第一步調用CreateDevice方法時GUID填GUID_SysKeyboard,然后在第二步SetDataFormat中填c_dfDIKeyboard就可以了(相關知識上面我們有詳細講到)。對於其他設備。依然是改這兩個地方,其他設備的GUID用EnumDevices枚舉一下就知道了,廢話也不多說,下面就開始DirectInput使用五步曲的講解:
這五步曲分別是:
一、創鍵DirectInput接口和設備,簡稱創設備
二、設置數據格式和協作級別,簡稱設格式
三、獲取設備控制權,簡稱拿權力
四、獲取按鍵情況並做響應,簡稱取按鍵
五、釋放控制權和接口對象,簡稱釋對象
DirectInput使用五步曲載體代碼:
1 //首先是全局變量的定義 2 3 LPDIRECTINPUTDEVICE8 g_pKeyboardDevice = NULL; 4 5 char g_pKeyStateBuffer[ 256] ={ 0}; 6 7 //--------------------------------------------------------------------------------------—---------------- 8 9 //【DirectInput使用五步曲之一】,創鍵DirectInput接口和設備,簡稱創設備 10 11 //--------------------------------------------------------------------------------------—---------------- 12 13 14 15 //創建DirectInput設備 16 17 DirectInput8Create(hInstance, 0x0800, IID_IDirectInput8,( void**)&g_pDirectInput, NULL); 18 19 g_pDirectInput->CreateDevice(GUID_SysKeyboard,&g_pKeyboardDevice, NULL); 20 21 //--------------------------------------------------------------------------------------—---------------- 22 23 //【DirectInput使用五步曲之二】,設置數據格式和協作級別,簡稱設格式 24 25 //--------------------------------------------------------------------------------------—---------------- 26 27 //設置數據格式和協作級別 28 29 g_pKeyboardDevice->SetDataFormat(&c_dfDIKeyboard); 30 31 g_pKeyboardDevice->SetCooperativeLevel(hwnd,DISCL_FOREGROUND |DISCL_NONEXCLUSIVE); 32 33 34 35 //--------------------------------------------------------------------------------------—---------------- 36 37 //【DirectInput使用五步曲之三】,.獲取設備控制權,簡稱拿權力 38 39 //--------------------------------------------------------------------------------------—---------------- 40 41 g_pKeyboardDevice->Acquire(); 42 43 44 45 //--------------------------------------------------------------------------------------—---------------- 46 47 //【DirectInput使用五步曲之四】,.獲取按鍵情況並做響應,簡稱取按鍵 48 49 //--------------------------------------------------------------------------------------—---------------- 50 51 52 53 54 55 // 讀取鍵盤輸入 56 57 ::ZeroMemory(g_pKeyStateBuffer, sizeof(g_pKeyStateBuffer)); 58 59 Device_Read(g_pKeyboardDevice,(LPVOID)g_pKeyStateBuffer, sizeof(g_pKeyStateBuffer)); 60 61 //定義的全局函數 62 63 BOOL Device_Read(IDirectInputDevice8*pDIDevice, void* pBuffer, longlSize) 64 65 { 66 67 HRESULThr; 68 69 while( true) 70 71 { 72 73 pDIDevice->Poll(); // 輪詢設備 74 75 pDIDevice->Acquire(); // 獲取設備的控制權 76 77 if(SUCCEEDED(hr = pDIDevice->GetDeviceState(lSize, pBuffer))) break; 78 79 if(hr !=DIERR_INPUTLOST || hr != DIERR_NOTACQUIRED) return FALSE; 80 81 if(FAILED(pDIDevice->Acquire())) return FALSE; 82 83 } 84 85 returnTRUE; 86 87 } 88 89 //然后就是用if判斷並做響應了,如下面一句代碼 90 91 if (g_pKeyStateBuffer[DIK_A] & 0x80)fPosX -= 0.005f; 92 93 94 95 //--------------------------------------------------------------------------------------—---------------- 96 97 //【DirectInput使用五步曲之五】,.釋放控制權和接口對象,簡稱釋對象 98 99 //--------------------------------------------------------------------------------------—---------------- 100 101 g_pKeyboardDevice->Unacquire(); 102 103 SAFE_RELEASE(g_pKeyboardDevice)
所以,上述DirectInput使用五步曲精煉總結起來就十五個字:
創設備,設格式,拿權力,取按鍵,釋對象
五、DirectInput鍵盤按鍵鍵值總結
與一般的Windows應用程序相比,DirectInput處理鍵盤事件的方式是有很多獨特之處的。首先,在我們寫的游戲程序中,鍵盤主要並不是用於文字輸入的,而是用於控制3D世界中人物,對象的運動,或者視角的變換等等。且在游戲程序中我們常常只需要知道具體是哪個鍵被按下,而忽略了該鍵所對應的字符。所以我們只需讀取已按下鍵的掃描碼就可以了。
另外,為了提高程序運行的效率,DirectInput並非使用Windows中的消息機制來讀取鍵盤的狀態,而是直接讀取硬件的狀態獲取按鍵的掃描碼的。
我們在按照流程創建好和打理好DirectInput之后,就能在程序中不斷獲取從鍵盤輸入的那些鍵盤數據。而在程序中,我們需要定義一個大小為256字節的數組,其中的每一個字節都存儲一個按鍵的狀態,這樣就可以保存256個按鍵的狀態信息了。微軟在DirectInput中為每個鍵都設置了一個對應的宏,這些宏都是以DIK_為前綴的。例如C鍵就定義為DIK_C,主鍵盤數字鍵8就對應DIK_8等等,下面就是淺墨對DirectInput鍵碼做的一個總結表格,查起來非常方便:
DirectInput鍵碼
說明
DIK_0~ DIK_9
主鍵盤上數字鍵0~9
DIK_A~ DIK_Z
字母鍵A~Z
DIK_F1~ DIK_F15
功能鍵F1~F15
DIK_NUMPAD0~ DIK_NUMPAD9
小鍵盤0~9
DIK_ESCAPE,DIK_TAB, DIK_BACK
Esc鍵,Tab鍵,退格鍵
DIK_RETURN,DIK_SPACE,DIK_NUMBERENTER
回車鍵、空格鍵、小鍵盤回車鍵,
DIK_LSHIFT, DIK_RSHIFT
左右Shift鍵
DIK_LMENU, DIK_RMENU
左右菜單鍵
DIK_LALT, DIK_RALT
左右Alt鍵
DIK_LCONTROL, DIK_RCONTROL
左右Ctrl鍵
DIK_UPARROW, DIK_DOWNARROW
DIK_LEFTARROW, DIK_RIGHTARROW
上下左右方向鍵
DIK_HOME, DIK_DELETE, DIK_INSERT
Home鍵,Delete鍵,Insert鍵
DIK_PRIOR, DIK_NEXT, DIK_END
PageUp鍵,PageDown鍵,End鍵
比如我們要檢測左Alt鍵是否按下,按下的話就做出響應,就可以在上表中找到左Alt鍵的鍵碼為DIK_LALT,然后就是一句if( ){ }語句:
if (g_pKeyStateBuffer[DIK_A] & 0x80) fPosX -= 0.005f;
六、DirectInput鼠標按鍵鍵值總結
在通常的Windows應用程序中,系統檢測鼠標的移動並通過消息處理函數將鼠標的移動作為消息報告給用戶,然而這樣做的效率非常低下,因為傳遞給消息處理函數的每個消息首先都要走消息隊列這條“官道”,需要慢悠悠地在消息隊列中排隊,排隊完全滿足不了我們對游戲即時處理消息的要求。而在Direct3D中,咱們就可以屌絲逆襲走后門了,我們可以直接同鼠標的驅動程序進行交互,而不用走消息隊列這條慢悠悠的“官道”。
另外,我們有兩種方式來跟蹤鼠標的移動為:絕對模式和相對模式。在絕對模式下,鼠標是基於某個固定點的,這個點通常是屏幕左上角,而此時返回的鼠標坐標是鼠標指針所處位置在屏幕坐標系中的坐標。
而另外一種模式,也就是相對模式下,鼠標坐標則是根據上一個已知位置到當前位置所發生的移動量來得到鼠標的坐標值的。在相對模式下得到的鼠標坐標是一個相對位置,而非絕對位置,大家需要注意。
好了,回到正題上來。在DirectInput中,鼠標的移動信息我們通常都是通過一個名叫DIMOUSESTATE結構體來記錄的,我們可以在MSDN中查到這個結構體定義如下:
1 typedef struct DIMOUSESTATE { 2 3 LONG lX; 4 5 LONG lY; 6 7 LONG lZ; 8 9 BYTE rgbButtons[ 4]; 10 11 } DIMOUSESTATE, *LPDIMOUSESTATE;
這個結構體中,lX,lY,lZ分別記錄了X軸,Y軸和Z軸(鼠標滾輪的相對移動量,鼠標沒移動的話,他們的值就是0.)。而結構體中的第四個參數rgbButtons[4]記錄了四個按鈕的狀態信息,其中rgbButtons[0]代表鼠標左鍵,rgbButtons[1]對應鼠標右鍵。如果需要處理支持更多按鈕的鼠標的話,就去用DIMOUSESTATE2結構體吧。
下面我們來看看實例:
1 DIMOUSESTATE g_diMouseState = { 0}; 2 3 ::ZeroMemory(&g_diMouseState, sizeof(g_diMouseState)); 4 5 Device_Read(g_pMouseDevice,(LPVOID)&g_diMouseState, sizeof(g_diMouseState)); 6 7 8 9 //按住鼠標左鍵並拖動,為平移操作 10 11 staticFLOAT fPosX = 0.0f, fPosY = 30.0f, fPosZ = 0.0f; 12 13 if(g_diMouseState.rgbButtons[ 0] & 0x80) 14 15 { 16 17 fPosX+=g_diMouseState.lX * 0.08f; 18 19 fPosY+=g_diMouseState.lY * -0.08f; 20 21 }
七、詳細注釋的源代碼欣賞
首先需要說明的是,本篇文章配套的源代碼中用到了我們目前還未講到的一點技術,就是X模型的載入。源代碼中X模型的載入相關的代碼大家如果看不懂沒關系,請鎖定淺墨的博客,后面一定會有相關技術精彩的講解的。
然后這篇文章中的demo我們對細節部分做了升級,新加了三個功能,他們分別是:
1.在窗口左上角智能讀取運行的機器使用的顯卡名稱。
2.在窗口左下角給出了幫助信息。
3. 在窗口左上角給出了模型當前的三維坐標。
下面我們分別來對這三個新功能進行講解:
1.在窗口左上角智能讀取運行的機器使用的顯卡名稱。
這個其實很簡單,借助一個GetAdapterIdentifier方法就可以了。這個方法可以獲取獲取顯卡的廠商類型等信息。原型如下
HRESULT GetAdapterIdentifier( [in] UINT Adapter, [in] DWORD Flags, [out] D3DADAPTER_IDENTIFIER9*pIdentifier );
注意到第三個參數類型是一個D3DADAPTER_IDENTIFIER9結構體,這個結構體的第三個參數Description就保存着顯卡的名稱的char類型的字符串。思路也就是圍繞着這個GetAdapterIdentifier方法來的,用GetAdapterIdentifier方法取得顯卡的名稱的char類型的字符串,然后轉換成wchar_t類型並在顯卡名稱之前拼接上“當前顯卡型號:”字樣,然后把結果存在全局的字符串數組g_strAdapterName中,最后在Render函數中用TextOut寫出來就可以了。另外注意一點,因為IDirect3D9::GetAdapterIdentifier是IDirect3D9中的方法,而在我們的代碼中IDirect3D9接口對象僅局部存在於Direct3D_Init( )方法中,所以我們絕大部分實現代碼是在這個Direct3D_Init( )方法中完成的。具體做法咱們直接看代碼,這可是每行都詳細注釋的代碼:
首先是一個全局變量:
wchar_t g_strAdapterName[60]={0}; //包含顯卡名稱的字符數組
然后就是Direct3D_Init( )方法中的功能實現代碼:
//獲取顯卡信息到g_strAdapterName中,並在顯卡名稱之前加上“當前顯卡型號:”字符串 wchar_tTempName[ 60]= L"當前顯卡型號:"; //定義一個臨時字符串,且方便了把"當前顯卡型號:"字符串引入我們的目的字符串中 D3DADAPTER_IDENTIFIER9Adapter; //定義一個D3DADAPTER_IDENTIFIER9結構體,用於存儲顯卡信息 pD3D->GetAdapterIdentifier( 0, 0,&Adapter); //調用GetAdapterIdentifier,獲取顯卡信息 int len =MultiByteToWideChar(CP_ACP, 0,Adapter.Description, -1, NULL, 0); //顯卡名稱現在已經在Adapter.Description中了,但是其為char類型,我們要將其轉為wchar_t類型 MultiByteToWideChar(CP_ACP, 0,Adapter.Description, -1, g_strAdapterName,len); //這步操作完成后,g_strAdapterName中就為當前我們的顯卡類型名的wchar_t型字符串了 wcscat_s(TempName,g_strAdapterName); //把當前我們的顯卡名加到“當前顯卡型號:”字符串后面,結果存在TempName中 wcscpy_s(g_strAdapterName,TempName); //把TempName中的結果拷貝到全局變量g_strAdapterName中,大功告成~
最后就是在Direct3D_Render函數中調用一下DrawText顯示出來了:
//顯示顯卡類型名 g_pTextAdaperName->DrawText( NULL,g_strAdapterName, -1,&formatRect, DT_TOP| DT_LEFT, D3DXCOLOR( 1.0f, 0.5f, 0.0f, 1.0f));
2.在窗口左下角給出幫助信息。
其實非常簡單,就是定義一些LPD3DXFONT接口對象,然后在Objects_Init()函數中用D3DXCreateFont創建不同的字體,最后在Direct3D_Render全DrawText出來就行了。
3. 在窗口左上角給出了模型當前的三維坐標。
其實也非常簡單,就是用swprintf_s把世界矩陣g_matWorld的幾個分量格式化到一個靜態的wchar_t類型的字符串中,然后DrawText出來就可以了。
實現代碼如下:
staticwchar_tstrInfo[ 256] = { 0}; swprintf_s(strInfo, -1, L"模型坐標: (%.2f,%.2f, %.2f)",g_matWorld._41, g_matWorld._42, g_matWorld._43); g_pTextHelper->DrawText( NULL,strInfo, -1, &formatRect, DT_SINGLELINE| DT_NOCLIP | DT_LEFT,D3DCOLOR_RGBA( 135, 239, 136, 255));
還有一點,因為考慮到咱們的Direct3D_Render()函數中的代碼隨着講解的不斷深入,代碼越來越多,越來越雜,越來越亂。所以我們給他配了一個搭檔Direct3D_Update(),跟即時繪制沒有直接聯系但是需要即時調用的,如按鍵后的坐標的更改,按鍵后填充模式的更改等等相關的代碼,都放在Direct3D_Update()中了,這樣就給Direct3D_Render()繪制函數減了負,看起來更加清晰。
因為也是即時調用,所以Direct3D_Update()在消息循環中與Direct3D_Render()平起平坐了:
1 //消息循環過程 2 3 MSGmsg = { 0 }; //初始化msg 4 5 while(msg.message !=WM_QUIT ) //使用while循環 6 7 { 8 9 if(PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) ) //查看應用程序消息隊列,有消息時將隊列中的消息派發出去。 10 11 { 12 13 TranslateMessage(&msg ); //將虛擬鍵消息轉換為字符消息 14 15 DispatchMessage(&msg ); //該函數分發一個消息給窗口程序。 16 17 } 18 19 else 20 21 { 22 23 Direct3D_Update(hwnd); //調用更新函數,進行畫面的更新 24 25 Direct3D_Render(hwnd); //調用渲染函數,進行畫面的渲染 26 27 } 28 29 }
最后一點,DirectInput使用五步曲的第四步,即獲取按鍵狀態並進行響應就是在Direct3D_Update中實現的:
1 void Direct3D_Update( HWND hwnd) 2 3 { 4 5 // 獲取鍵盤消息並給予設置相應的填充模式 6 7 if (g_pKeyStateBuffer[DIK_1] & 0x80) // 若數字鍵1被按下,進行實體填充 8 9 g_pd3dDevice->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID); 10 11 if (g_pKeyStateBuffer[DIK_2] & 0x80) // 若數字鍵2被按下,進行線框填充 12 13 g_pd3dDevice->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME); 14 15 16 17 18 19 // 讀取鼠標輸入 20 21 ::ZeroMemory(&g_diMouseState, sizeof(g_diMouseState)); 22 23 Device_Read(g_pMouseDevice, (LPVOID)&g_diMouseState, sizeof(g_diMouseState)); 24 25 26 27 28 29 // 讀取鍵盤輸入 30 31 ::ZeroMemory(g_pKeyStateBuffer, sizeof(g_pKeyStateBuffer)); 32 33 Device_Read(g_pKeyboardDevice, (LPVOID)g_pKeyStateBuffer, sizeof(g_pKeyStateBuffer)); 34 35 36 37 38 39 40 41 42 43 // 按住鼠標左鍵並拖動,為平移操作 44 45 static FLOAT fPosX = 0.0f, fPosY = 30.0f, fPosZ = 0.0f; 46 47 if (g_diMouseState.rgbButtons[ 0] & 0x80) 48 49 { 50 51 fPosX += g_diMouseState.lX * 0.08f; 52 53 fPosY += g_diMouseState.lY * -0.08f; 54 55 } 56 57 58 59 60 61 //鼠標滾輪,為觀察點收縮操作 62 63 fPosZ += g_diMouseState.lZ * 0.02f; 64 65 66 67 68 69 // 平移物體 70 71 if (g_pKeyStateBuffer[DIK_A] & 0x80) fPosX -= 0.005f; 72 73 if (g_pKeyStateBuffer[DIK_D] & 0x80) fPosX += 0.005f; 74 75 if (g_pKeyStateBuffer[DIK_W] & 0x80) fPosY += 0.005f; 76 77 if (g_pKeyStateBuffer[DIK_S] & 0x80) fPosY -= 0.005f; 78 79 80 81 82 83 84 85 86 87 D3DXMatrixTranslation(&g_matWorld, fPosX, fPosY, fPosZ); 88 89 90 91 92 93 94 95 96 97 // 按住鼠標右鍵並拖動,為旋轉操作 98 99 static float fAngleX = 0.15f, fAngleY = -( float)D3DX_PI ; 100 101 if (g_diMouseState.rgbButtons[ 1] & 0x80) 102 103 { 104 105 fAngleX += g_diMouseState.lY * -0.01f; 106 107 fAngleY += g_diMouseState.lX * -0.01f; 108 109 } 110 111 // 旋轉物體 112 113 if (g_pKeyStateBuffer[DIK_UP] & 0x80) fAngleX += 0.005f; 114 115 if (g_pKeyStateBuffer[DIK_DOWN] & 0x80) fAngleX -= 0.005f; 116 117 if (g_pKeyStateBuffer[DIK_LEFT] & 0x80) fAngleY -= 0.005f; 118 119 if (g_pKeyStateBuffer[DIK_RIGHT] & 0x80) fAngleY += 0.005f; 120 121 122 123 124 125 126 127 128 129 D3DXMATRIX Rx, Ry; 130 131 D3DXMatrixRotationX(&Rx, fAngleX); 132 133 D3DXMatrixRotationY(&Ry, fAngleY); 134 135 136 137 138 139 g_matWorld = Rx * Ry * g_matWorld; 140 141 g_pd3dDevice->SetTransform(D3DTS_WORLD, &g_matWorld); 142 143 Matrix_Set(); 144 145 }
嗯,講解完成,下面我們就貼出完整的詳細注釋的demo的源代碼:
View Code1 //***************************************************************************************** 2 3 // 4 5 //【Visual C++】游戲開發筆記系列配套源碼 四十二 淺墨DirectX教程之十 游戲輸入控制利器 : DirectInput專場 6 7 // VS2010版 8 9 // 2012年 1月27日 Create by 淺墨 10 11 //圖標及圖片素材: 《仙劍奇俠傳五前傳》 瑕 12 13 //此刻心情:既然選擇了遠方,便只顧風雨兼程 14 15 // 16 17 //***************************************************************************************** 18 19 20 21 22 23 //***************************************************************************************** 24 25 // Desc: 宏定義部分 26 27 //***************************************************************************************** 28 29 #define SCREEN_WIDTH 800 //為窗口寬度定義的宏,以方便在此處修改窗口寬度 30 31 #define SCREEN_HEIGHT 600 //為窗口高度定義的宏,以方便在此處修改窗口高度 32 33 #define WINDOW_TITLE _T("【Visual C++游戲開發筆記】博文配套demo之四十二 淺墨DirectX教程之十 游戲輸入控制利器 : DirectInput專場") //為窗口標題定義的宏 34 35 #define DIRECTINPUT_VERSION 0x0800 //指定DirectInput版本,防止DIRECTINPUT_VERSION undefined警告 36 37 #define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } } //自定義一個SAFE_RELEASE()宏,便於COM資源的釋放 38 39 #define SAFE_DELETE(p) { if(p) { delete (p); (p)=NULL; } } 40 41 42 43 44 45 //***************************************************************************************** 46 47 // Desc: 頭文件定義部分 48 49 //***************************************************************************************** 50 51 #include <d3d9.h> 52 53 #include <d3dx9.h> 54 55 #include <tchar.h> 56 57 #include <time.h> 58 59 #include <dinput.h> // 使用DirectInput必須包含的頭文件,注意這里沒有8 60 61 62 63 64 65 66 67 68 69 //***************************************************************************************** 70 71 // Desc: 庫文件定義部分 72 73 //***************************************************************************************** 74 75 #pragma comment(lib,"d3d9.lib") 76 77 #pragma comment(lib,"d3dx9.lib") 78 79 #pragma comment(lib, "dinput8.lib") // 使用DirectInput必須包含的頭文件,注意這里有8 80 81 #pragma comment(lib,"dxguid.lib") 82 83 #pragma comment(lib, "winmm.lib") 84 85 86 87 88 89 90 91 92 93 94 95 //***************************************************************************************** 96 97 // Desc: 全局變量聲明部分 98 99 //***************************************************************************************** 100 101 LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; //Direct3D設備對象 102 103 LPD3DXFONT g_pTextFPS = NULL; //字體COM接口 104 105 LPD3DXFONT g_pTextAdaperName = NULL; // 顯卡信息的2D文本 106 107 LPD3DXFONT g_pTextHelper = NULL; // 幫助信息的2D文本 108 109 LPD3DXFONT g_pTextInfor = NULL; // 繪制信息的2D文本 110 111 float g_FPS = 0.0f; //一個浮點型的變量,代表幀速率 112 113 wchar_t g_strFPS[ 50]={ 0}; //包含幀速率的字符數組 114 115 wchar_t g_strAdapterName[ 60]={ 0}; //包含顯卡名稱的字符數組 116 117 118 119 LPDIRECTINPUT8 g_pDirectInput = NULL; // 120 121 LPDIRECTINPUTDEVICE8 g_pMouseDevice = NULL; 122 123 DIMOUSESTATE g_diMouseState = { 0}; 124 125 LPDIRECTINPUTDEVICE8 g_pKeyboardDevice = NULL; 126 127 char g_pKeyStateBuffer[ 256] = { 0}; 128 129 D3DXMATRIX g_matWorld; //世界矩陣 130 131 132 133 LPD3DXMESH g_pMesh = NULL; // 網格的對象 134 135 D3DMATERIAL9* g_pMaterials = NULL; // 網格的材質信息 136 137 LPDIRECT3DTEXTURE9* g_pTextures = NULL; // 網格的紋理信息 138 139 DWORD g_dwNumMtrls = 0; // 材質的數目 140 141 142 143 //***************************************************************************************** 144 145 // Desc: 全局函數聲明部分 146 147 //***************************************************************************************** 148 149 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ); 150 151 HRESULT Direct3D_Init(HWND hwnd,HINSTANCE hInstance); 152 153 HRESULT Objects_Init(); 154 155 void Direct3D_Render( HWND hwnd); 156 157 void Direct3D_Update( HWND hwnd); 158 159 void Direct3D_CleanUp( ); 160 161 float Get_FPS(); 162 163 void Matrix_Set(); 164 165 BOOL Device_Read(IDirectInputDevice8 *pDIDevice, void* pBuffer, long lSize) ; 166 167 168 169 170 171 //***************************************************************************************** 172 173 // Name: WinMain( ) 174 175 // Desc: Windows應用程序入口函數 176 177 //***************************************************************************************** 178 179 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd) 180 181 { 182 183 184 185 //開始設計一個完整的窗口類 186 187 WNDCLASSEX wndClass = { 0 }; //用WINDCLASSEX定義了一個窗口類,即用wndClass實例化了WINDCLASSEX,用於之后窗口的各項初始化 188 189 wndClass.cbSize = sizeof( WNDCLASSEX ) ; //設置結構體的字節數大小 190 191 wndClass.style = CS_HREDRAW | CS_VREDRAW; //設置窗口的樣式 192 193 wndClass.lpfnWndProc = WndProc; //設置指向窗口過程函數的指針 194 195 wndClass.cbClsExtra = 0; 196 197 wndClass.cbWndExtra = 0; 198 199 wndClass.hInstance = hInstance; //指定包含窗口過程的程序的實例句柄。 200 201 wndClass.hIcon=(HICON)::LoadImage( NULL,_T( "icon.ico"),IMAGE_ICON, 0, 0,LR_DEFAULTSIZE|LR_LOADFROMFILE); //從全局的::LoadImage函數從本地加載自定義ico圖標 202 203 wndClass.hCursor = LoadCursor( NULL, IDC_ARROW ); //指定窗口類的光標句柄。 204 205 wndClass.hbrBackground=(HBRUSH)GetStockObject(GRAY_BRUSH); //為hbrBackground成員指定一個灰色畫刷句柄 206 207 wndClass.lpszMenuName = NULL; //用一個以空終止的字符串,指定菜單資源的名字。 208 209 wndClass.lpszClassName = _T( "ForTheDreamOfGameDevelop"); //用一個以空終止的字符串,指定窗口類的名字。 210 211 212 213 if( !RegisterClassEx( &wndClass ) ) //設計完窗口后,需要對窗口類進行注冊,這樣才能創建該類型的窗口 214 215 return -1; 216 217 218 219 HWND hwnd = CreateWindow( _T( "ForTheDreamOfGameDevelop"),WINDOW_TITLE, //喜聞樂見的創建窗口函數CreateWindow 220 221 WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, SCREEN_WIDTH, 222 223 SCREEN_HEIGHT, NULL, NULL, hInstance, NULL ); 224 225 226 227 228 229 //Direct3D資源的初始化,調用失敗用messagebox予以顯示 230 231 if (!(S_OK==Direct3D_Init (hwnd,hInstance))) 232 233 { 234 235 MessageBox(hwnd, _T( "Direct3D初始化失敗~!"), _T( "淺墨的消息窗口"), 0); //使用MessageBox函數,創建一個消息窗口 236 237 } 238 239 240 241 242 243 244 245 MoveWindow(hwnd, 200, 50,SCREEN_WIDTH,SCREEN_HEIGHT, true); //調整窗口顯示時的位置,窗口左上角位於屏幕坐標(200,50)處 246 247 ShowWindow( hwnd, nShowCmd ); //調用Win32函數ShowWindow來顯示窗口 248 249 UpdateWindow(hwnd); //對窗口進行更新,就像我們買了新房子要裝修一樣 250 251 252 253 254 255 //消息循環過程 256 257 MSG msg = { 0 }; //初始化msg 258 259 while( msg.message != WM_QUIT ) //使用while循環 260 261 { 262 263 if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) ) //查看應用程序消息隊列,有消息時將隊列中的消息派發出去。 264 265 { 266 267 TranslateMessage( &msg ); //將虛擬鍵消息轉換為字符消息 268 269 DispatchMessage( &msg ); //該函數分發一個消息給窗口程序。 270 271 } 272 273 else 274 275 { 276 277 Direct3D_Update(hwnd); //調用更新函數,進行畫面的更新 278 279 Direct3D_Render(hwnd); //調用渲染函數,進行畫面的渲染 280 281 } 282 283 } 284 285 286 287 UnregisterClass(_T( "ForTheDreamOfGameDevelop"), wndClass.hInstance); 288 289 return 0; 290 291 } 292 293 294 295 296 297 298 299 //***************************************************************************************** 300 301 // Name: WndProc() 302 303 // Desc: 對窗口消息進行處理 304 305 //***************************************************************************************** 306 307 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) //窗口過程函數WndProc 308 309 { 310 311 switch( message ) //switch語句開始 312 313 { 314 315 case WM_PAINT: // 客戶區重繪消息 316 317 Direct3D_Render(hwnd); //調用Direct3D_Render函數,進行畫面的繪制 318 319 ValidateRect(hwnd, NULL); // 更新客戶區的顯示 320 321 break; //跳出該switch語句 322 323 324 325 case WM_KEYDOWN: // 鍵盤按下消息 326 327 if (wParam == VK_ESCAPE) // ESC鍵 328 329 DestroyWindow(hwnd); // 銷毀窗口, 並發送一條WM_DESTROY消息 330 331 break; 332 333 case WM_DESTROY: //窗口銷毀消息 334 335 Direct3D_CleanUp(); //調用Direct3D_CleanUp函數,清理COM接口對象 336 337 PostQuitMessage( 0 ); //向系統表明有個線程有終止請求。用來響應WM_DESTROY消息 338 339 break; //跳出該switch語句 340 341 342 343 default: //若上述case條件都不符合,則執行該default語句 344 345 return DefWindowProc( hwnd, message, wParam, lParam ); //調用缺省的窗口過程來為應用程序沒有處理的窗口消息提供缺省的處理。 346 347 } 348 349 350 351 return 0; //正常退出 352 353 } 354 355 356 357 358 359 //***************************************************************************************** 360 361 // Name: Direct3D_Init( ) 362 363 // Desc: 初始化Direct3D 364 365 // Point:【Direct3D初始化四步曲】 366 367 // 1.初始化四步曲之一,創建Direct3D接口對象 368 369 // 2.初始化四步曲之二,獲取硬件設備信息 370 371 // 3.初始化四步曲之三,填充結構體 372 373 // 4.初始化四步曲之四,創建Direct3D設備接口 374 375 //***************************************************************************************** 376 377 378 379 HRESULT Direct3D_Init(HWND hwnd,HINSTANCE hInstance) 380 381 { 382 383 384 385 //-------------------------------------------------------------------------------------- 386 387 // 【Direct3D初始化四步曲之一,創接口】:創建Direct3D接口對象, 以便用該Direct3D對象創建Direct3D設備對象 388 389 //-------------------------------------------------------------------------------------- 390 391 LPDIRECT3D9 pD3D = NULL; //Direct3D接口對象的創建 392 393 if( NULL == ( pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) ) //初始化Direct3D接口對象,並進行DirectX版本協商 394 395 return E_FAIL; 396 397 398 399 //-------------------------------------------------------------------------------------- 400 401 // 【Direct3D初始化四步曲之二,取信息】:獲取硬件設備信息 402 403 //-------------------------------------------------------------------------------------- 404 405 D3DCAPS9 caps; int vp = 0; 406 407 if( FAILED( pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps ) ) ) 408 409 { 410 411 return E_FAIL; 412 413 } 414 415 if( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT ) 416 417 vp = D3DCREATE_HARDWARE_VERTEXPROCESSING; //支持硬件頂點運算,我們就采用硬件頂點運算,妥妥的 418 419 else 420 421 vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; //不支持硬件頂點運算,無奈只好采用軟件頂點運算 422 423 424 425 //-------------------------------------------------------------------------------------- 426 427 // 【Direct3D初始化四步曲之三,填內容】:填充D3DPRESENT_PARAMETERS結構體 428 429 //-------------------------------------------------------------------------------------- 430 431 D3DPRESENT_PARAMETERS d3dpp; 432 433 ZeroMemory(&d3dpp, sizeof(d3dpp)); 434 435 d3dpp.BackBufferWidth = SCREEN_WIDTH; 436 437 d3dpp.BackBufferHeight = SCREEN_HEIGHT; 438 439 d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8; 440 441 d3dpp.BackBufferCount = 2; 442 443 d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE; 444 445 d3dpp.MultiSampleQuality = 0; 446 447 d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; 448 449 d3dpp.hDeviceWindow = hwnd; 450 451 d3dpp.Windowed = true; 452 453 d3dpp.EnableAutoDepthStencil = true; 454 455 d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8; 456 457 d3dpp.Flags = 0; 458 459 d3dpp.FullScreen_RefreshRateInHz = 0; 460 461 d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; 462 463 464 465 //-------------------------------------------------------------------------------------- 466 467 // 【Direct3D初始化四步曲之四,創設備】:創建Direct3D設備接口 468 469 //-------------------------------------------------------------------------------------- 470 471 if(FAILED(pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, 472 473 hwnd, vp, &d3dpp, &g_pd3dDevice))) 474 475 return E_FAIL; 476 477 478 479 480 481 //獲取顯卡信息到g_strAdapterName中,並在顯卡名稱之前加上“當前顯卡型號:”字符串 482 483 wchar_t TempName[ 60]= L"當前顯卡型號:"; //定義一個臨時字符串,且方便了把"當前顯卡型號:"字符串引入我們的目的字符串中 484 485 D3DADAPTER_IDENTIFIER9 Adapter; //定義一個D3DADAPTER_IDENTIFIER9結構體,用於存儲顯卡信息 486 487 pD3D->GetAdapterIdentifier( 0, 0,&Adapter); //調用GetAdapterIdentifier,獲取顯卡信息 488 489 int len = MultiByteToWideChar(CP_ACP, 0, Adapter.Description, -1, NULL, 0); //顯卡名稱現在已經在Adapter.Description中了,但是其為char類型,我們要將其轉為wchar_t類型 490 491 MultiByteToWideChar(CP_ACP, 0, Adapter.Description, -1, g_strAdapterName, len); //這步操作完成后,g_strAdapterName中就為當前我們的顯卡類型名的wchar_t型字符串了 492 493 wcscat_s(TempName,g_strAdapterName); //把當前我們的顯卡名加到“當前顯卡型號:”字符串后面,結果存在TempName中 494 495 wcscpy_s(g_strAdapterName,TempName); //把TempName中的結果拷貝到全局變量g_strAdapterName中,大功告成~ 496 497 498 499 500 501 502 503 //-------------------------------------------------------------------------------------- 504 505 // 【DirectInput使用五步曲的前三步】:創設備,設格式,拿權力。在為鼠標設備初始化 506 507 //-------------------------------------------------------------------------------------- 508 509 // 創建DirectInput接口和設備 510 511 DirectInput8Create(hInstance, 0x0800, IID_IDirectInput8, ( void**)&g_pDirectInput, NULL); 512 513 g_pDirectInput->CreateDevice(GUID_SysKeyboard, &g_pMouseDevice, NULL); 514 515 516 517 // 設置數據格式和協作級別 518 519 g_pDirectInput->CreateDevice(GUID_SysMouse, &g_pMouseDevice, NULL); 520 521 g_pMouseDevice->SetDataFormat(&c_dfDIMouse); 522 523 524 525 //獲取設備控制權 526 527 g_pMouseDevice->Acquire(); 528 529 530 531 //-------------------------------------------------------------------------------------- 532 533 // 【DirectInput使用五步曲的前三步】:創設備,設格式,拿權力。在為鍵盤設備初始化 534 535 //-------------------------------------------------------------------------------------- 536 537 // 創建DirectInput接口和設備 538 539 DirectInput8Create(hInstance, 0x0800, IID_IDirectInput8, ( void**)&g_pDirectInput, NULL); 540 541 g_pDirectInput->CreateDevice(GUID_SysKeyboard, &g_pKeyboardDevice, NULL); 542 543 544 545 // 設置數據格式和協作級別 546 547 g_pKeyboardDevice->SetDataFormat(&c_dfDIKeyboard); 548 549 g_pKeyboardDevice->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE); 550 551 552 553 //獲取設備控制權 554 555 g_pKeyboardDevice->Acquire(); 556 557 558 559 560 561 if(!(S_OK==Objects_Init())) return E_FAIL; 562 563 564 565 SAFE_RELEASE(pD3D) //LPDIRECT3D9接口對象的使命完成,我們將其釋放掉 566 567 568 569 return S_OK; 570 571 } 572 573 574 575 576 577 HRESULT Objects_Init() 578 579 { 580 581 //創建字體 582 583 D3DXCreateFont(g_pd3dDevice, 36, 0, 0, 1000, false, DEFAULT_CHARSET, 584 585 OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, _T( "Calibri"), &g_pTextFPS); 586 587 D3DXCreateFont(g_pd3dDevice, 20, 0, 1000, 0, false, DEFAULT_CHARSET, 588 589 OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"華文中宋", &g_pTextAdaperName); 590 591 D3DXCreateFont(g_pd3dDevice, 23, 0, 1000, 0, false, DEFAULT_CHARSET, 592 593 OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"微軟雅黑", &g_pTextHelper); 594 595 D3DXCreateFont(g_pd3dDevice, 26, 0, 1000, 0, false, DEFAULT_CHARSET, 596 597 OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"黑體", &g_pTextInfor); 598 599 600 601 // 從X文件中加載網格數據 602 603 LPD3DXBUFFER pAdjBuffer = NULL; 604 605 LPD3DXBUFFER pMtrlBuffer = NULL; 606 607 D3DXLoadMeshFromX( L"loli.x", D3DXMESH_MANAGED, g_pd3dDevice, 608 609 &pAdjBuffer, &pMtrlBuffer, NULL, &g_dwNumMtrls, &g_pMesh); 610 611 612 613 // 讀取材質和紋理數據 614 615 D3DXMATERIAL *pMtrls = (D3DXMATERIAL*)pMtrlBuffer->GetBufferPointer(); 616 617 g_pMaterials = new D3DMATERIAL9[g_dwNumMtrls]; 618 619 g_pTextures = new LPDIRECT3DTEXTURE9[g_dwNumMtrls]; 620 621 622 623 for (DWORD i= 0; i<g_dwNumMtrls; i++) 624 625 { 626 627 g_pMaterials[i] = pMtrls[i].MatD3D; 628 629 g_pMaterials[i].Ambient = g_pMaterials[i].Diffuse; 630 631 g_pTextures[i] = NULL; 632 633 D3DXCreateTextureFromFileA(g_pd3dDevice, pMtrls[i].pTextureFilename, &g_pTextures[i]); 634 635 } 636 637 pAdjBuffer->Release(); 638 639 pMtrlBuffer->Release(); 640 641 642 643 644 645 // 設置渲染狀態 646 647 g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW); //開啟背面消隱 648 649 g_pd3dDevice->SetRenderState(D3DRS_AMBIENT, D3DXCOLOR( 1.0f, 1.0f, 1.0f, 1.0f)); //設置環境光 650 651 652 653 g_pd3dDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); //設置為線性紋理過濾 654 655 g_pd3dDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); 656 657 658 659 660 661 return S_OK; 662 663 } 664 665 666 667 668 669 //***************************************************************************************** 670 671 // Name:Matrix_Set() 672 673 // Desc: 設置世界矩陣 674 675 // Point:【Direct3D四大變換】 676 677 // 1.【四大變換之一】:世界變換矩陣的設置 678 679 // 2.【四大變換之二】:取景變換矩陣的設置 680 681 // 3.【四大變換之三】:投影變換矩陣的設置 682 683 // 4.【四大變換之四】:視口變換的設置 684 685 //***************************************************************************************** 686 687 void Matrix_Set() 688 689 { 690 691 //-------------------------------------------------------------------------------------- 692 693 //【四大變換之一】:世界變換矩陣的設置 694 695 //-------------------------------------------------------------------------------------- 696 697 698 699 700 701 //-------------------------------------------------------------------------------------- 702 703 //【四大變換之二】:取景變換矩陣的設置 704 705 //-------------------------------------------------------------------------------------- 706 707 D3DXMATRIX matView; //定義一個矩陣 708 709 D3DXVECTOR3 vEye(0.0f, 0.0f, -250.0f); //攝像機的位置 710 711 D3DXVECTOR3 vAt(0.0f, 0.0f, 0.0f); //觀察點的位置 712 713 D3DXVECTOR3 vUp(0.0f, 1.0f, 0.0f); //向上的向量 714 715 D3DXMatrixLookAtLH(&matView, &vEye, &vAt, &vUp); //計算出取景變換矩陣 716 717 g_pd3dDevice->SetTransform(D3DTS_VIEW, &matView); //應用取景變換矩陣 718 719 720 721 //-------------------------------------------------------------------------------------- 722 723 //【四大變換之三】:投影變換矩陣的設置 724 725 //-------------------------------------------------------------------------------------- 726 727 D3DXMATRIX matProj; //定義一個矩陣 728 729 D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI / 4.0f,( float)(( double)SCREEN_WIDTH/SCREEN_HEIGHT), 1.0f, 1000.0f); //計算投影變換矩陣 730 731 g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &matProj); //設置投影變換矩陣 732 733 734 735 //-------------------------------------------------------------------------------------- 736 737 //【四大變換之四】:視口變換的設置 738 739 //-------------------------------------------------------------------------------------- 740 741 D3DVIEWPORT9 vp; //實例化一個D3DVIEWPORT9結構體,然后做填空題給各個參數賦值就可以了 742 743 vp.X = 0; //表示視口相對於窗口的X坐標 744 745 vp.Y = 0; //視口相對對窗口的Y坐標 746 747 vp.Width = SCREEN_WIDTH; //視口的寬度 748 749 vp.Height = SCREEN_HEIGHT; //視口的高度 750 751 vp.MinZ = 0.0f; //視口在深度緩存中的最小深度值 752 753 vp.MaxZ = 1.0f; //視口在深度緩存中的最大深度值 754 755 g_pd3dDevice->SetViewport(&vp); //視口的設置 756 757 758 759 } 760 761 762 763 764 765 void Direct3D_Update( HWND hwnd) 766 767 { 768 769 // 獲取鍵盤消息並給予設置相應的填充模式 770 771 if (g_pKeyStateBuffer[DIK_1] & 0x80) // 若數字鍵1被按下,進行實體填充 772 773 g_pd3dDevice->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID); 774 775 if (g_pKeyStateBuffer[DIK_2] & 0x80) // 若數字鍵2被按下,進行線框填充 776 777 g_pd3dDevice->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME); 778 779 780 781 // 讀取鼠標輸入 782 783 ::ZeroMemory(&g_diMouseState, sizeof(g_diMouseState)); 784 785 Device_Read(g_pMouseDevice, (LPVOID)&g_diMouseState, sizeof(g_diMouseState)); 786 787 788 789 // 讀取鍵盤輸入 790 791 ::ZeroMemory(g_pKeyStateBuffer, sizeof(g_pKeyStateBuffer)); 792 793 Device_Read(g_pKeyboardDevice, (LPVOID)g_pKeyStateBuffer, sizeof(g_pKeyStateBuffer)); 794 795 796 797 798 799 // 按住鼠標左鍵並拖動,為平移操作 800 801 static FLOAT fPosX = 0.0f, fPosY = 30.0f, fPosZ = 0.0f; 802 803 if (g_diMouseState.rgbButtons[ 0] & 0x80) 804 805 { 806 807 fPosX += g_diMouseState.lX * 0.08f; 808 809 fPosY += g_diMouseState.lY * -0.08f; 810 811 } 812 813 814 815 //鼠標滾輪,為觀察點收縮操作 816 817 fPosZ += g_diMouseState.lZ * 0.02f; 818 819 820 821 // 平移物體 822 823 if (g_pKeyStateBuffer[DIK_A] & 0x80) fPosX -= 0.005f; 824 825 if (g_pKeyStateBuffer[DIK_D] & 0x80) fPosX += 0.005f; 826 827 if (g_pKeyStateBuffer[DIK_W] & 0x80) fPosY += 0.005f; 828 829 if (g_pKeyStateBuffer[DIK_S] & 0x80) fPosY -= 0.005f; 830 831 832 833 834 835 D3DXMatrixTranslation(&g_matWorld, fPosX, fPosY, fPosZ); 836 837 838 839 840 841 // 按住鼠標右鍵並拖動,為旋轉操作 842 843 static float fAngleX = 0.15f, fAngleY = -( float)D3DX_PI ; 844 845 if (g_diMouseState.rgbButtons[ 1] & 0x80) 846 847 { 848 849 fAngleX += g_diMouseState.lY * -0.01f; 850 851 fAngleY += g_diMouseState.lX * -0.01f; 852 853 } 854 855 // 旋轉物體 856 857 if (g_pKeyStateBuffer[DIK_UP] & 0x80) fAngleX += 0.005f; 858 859 if (g_pKeyStateBuffer[DIK_DOWN] & 0x80) fAngleX -= 0.005f; 860 861 if (g_pKeyStateBuffer[DIK_LEFT] & 0x80) fAngleY -= 0.005f; 862 863 if (g_pKeyStateBuffer[DIK_RIGHT] & 0x80) fAngleY += 0.005f; 864 865 866 867 868 869 D3DXMATRIX Rx, Ry; 870 871 D3DXMatrixRotationX(&Rx, fAngleX); 872 873 D3DXMatrixRotationY(&Ry, fAngleY); 874 875 876 877 g_matWorld = Rx * Ry * g_matWorld; 878 879 g_pd3dDevice->SetTransform(D3DTS_WORLD, &g_matWorld); 880 881 Matrix_Set(); 882 883 } 884 885 886 887 888 889 890 891 //***************************************************************************************** 892 893 // Name: Direct3D_Render() 894 895 // Desc: 進行圖形的渲染操作 896 897 // Point:【Direct3D渲染五步曲】 898 899 // 1.渲染五步曲之一,清屏操作 900 901 // 2.渲染五步曲之二,開始繪制 902 903 // 3.渲染五步曲之三,正式繪制 904 905 // 4.渲染五步曲之四,結束繪制 906 907 // 5.渲染五步曲之五,翻轉顯示 908 909 //***************************************************************************************** 910 911 912 913 void Direct3D_Render(HWND hwnd) 914 915 { 916 917 918 919 //-------------------------------------------------------------------------------------- 920 921 // 【Direct3D渲染五步曲之一】:清屏操作 922 923 //-------------------------------------------------------------------------------------- 924 925 g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB( 0, 0, 0), 1.0f, 0); 926 927 928 929 //定義一個矩形,用於獲取主窗口矩形 930 931 RECT formatRect; 932 933 GetClientRect(hwnd, &formatRect); 934 935 936 937 //-------------------------------------------------------------------------------------- 938 939 // 【Direct3D渲染五步曲之二】:開始繪制 940 941 //-------------------------------------------------------------------------------------- 942 943 g_pd3dDevice->BeginScene(); // 開始繪制 944 945 946 947 //-------------------------------------------------------------------------------------- 948 949 // 【Direct3D渲染五步曲之三】:正式繪制,利用頂點緩存繪制圖形 950 951 //-------------------------------------------------------------------------------------- 952 953 954 955 // 繪制網格 956 957 for (DWORD i = 0; i < g_dwNumMtrls; i++) 958 959 { 960 961 g_pd3dDevice->SetMaterial(&g_pMaterials[i]); 962 963 g_pd3dDevice->SetTexture( 0, g_pTextures[i]); 964 965 g_pMesh->DrawSubset(i); 966 967 } 968 969 970 971 //在窗口右上角處,顯示每秒幀數 972 973 int charCount = swprintf_s(g_strFPS, 20, _T( "FPS:%0.3f"), Get_FPS() ); 974 975 g_pTextFPS->DrawText( NULL, g_strFPS, charCount , &formatRect, DT_TOP | DT_RIGHT, D3DCOLOR_RGBA( 0, 239, 136, 255)); 976 977 978 979 //顯示顯卡類型名