設計一個PC游戲,鍵盤鼠標的輸入是絕不能少。Windows也提供了諸如
WM_LBUTTONDOWN、WM_RBUTTONUP等鼠標消息以及WM_KEYDOWN、WM_KEYUP等鍵盤輸入消息。但是
DirectInput中仍然提供了對鼠標鍵盤的支持,其原因就是DirectInput提供一個更直接更快捷的對輸入設備的訪問方法。就象我們在DOS
下直接接管鍵盤中斷,而不是去用什么討厭的INT16來處理鍵盤輸入一樣(用INT16來處理鍵盤輸入其弊端在《金庸群俠傳》中顯得尤為明顯,人物在走路
之前總要頓那么一下,就是這一下讓我覺得非常之不爽!其原因我想我也不用羅嗦了)。
當然Windows的鍵盤消息比之INT16當然有了長足的進步(因為它提供了一
個WM_KEYUP消息),但是在某些方面仍顯不足。因為Windows的消息機制是一個緩沖(buffer)機制,未被處理的鍵盤鼠標消息都放在緩沖區(qū)
里等待下一次處理,這樣對于一些應用軟件是非常重要的,但是對游戲來說(特別是一些動作游戲,包括體育游戲)就顯得有蛇足之嫌了。舉個例子,在足球游戲
里,你去搶截對手的球——搶球和射門、鏟斷和長傳(大腳)總是設成同一個鍵,這好像是個公認的標準了——但這時剛好對手的球脫腳了,球直接就到了你的腳
下,這時你本來想帶球繞過他的,可是你的搶球鍵已經按過了,由于這個該死的緩沖機制,先要處理一下這個搶球鍵(也就是射門鍵),于是你的動作就變成了一次
盲目的后場遠射(等同于大腳解圍)了。控制不了自己的動作,做球員做到這個份兒上真是夠失敗的了。這里就是緩沖機制不適用的地方了。
而DirectInput提供了緩沖和立即兩種訪問輸入設備的方式,對于立即方
式,正好就是解決上面弊病的方法。DirectInput里關于鍵盤的初始化部分,已經在很早以前的一篇文章里給出來了。雖是針對于DirectX7的,
但關于DirectInput部分在DX8和DX7里差別不大,把LPDIRECTINPUT7換成LPDIRECTINPUT8、
LPDIRECTINPUTDEVICE7換成LPDIRECTINPUTDEVICE8就OK了,此外還有一點點需要改動的就是DirectInput
對象的創(chuàng)建,DX8里用的是下面這個函數:
DirectInput8Create(hInst, DIRECTINPUT_VERSION,
IID_IDirectInput8,(LPVOID *)&lpDI, NULL);
里面具體的參數大家看也看得出來,我就不多說了。
下面說一下鼠標,鼠標的緩沖機制還是滿重要的,鼠標的移動就建立在滾動計數累積的
基礎上的。鼠標是每隔8ms采樣一次(反正USB鼠標是這樣,我估計一般鼠標也是一樣),要是只獲取當前狀態(tài)的話,那這個鼠標移動起來就太慢了(應該不會
有人在應用程序里每隔8ms就調用一次鼠標的狀態(tài)獲取函數吧)。之所以采用DirectInput,不是因為緩沖這個原因,而是因為一個我個人的喜好因
素。一般的游戲在卷屏時是判斷鼠標的位置是否在屏幕邊緣,如果是就向這一方向卷屏。我個人不是很喜歡這種做法,可能因為我手比較笨,玩游戲是經常莫名其妙
地畫面就移走了,這讓我覺得很成問題,為什么光標指到屏幕邊上就要卷屏?所以我希望鼠標的移動才是卷屏的依據,這在Windows的消息機制里就做不到
了。因為在Windows的消息機制里,當鼠標一到屏幕邊上時再向外移動,應用程序是收不到WM_MOUSEMOVE消息的。但在DirectInput
里就可以由我自己來實現(xiàn),DirectInput接收到的只是鼠標的滾動計數,它可沒有什么光標位置的限制。
下面就給出DirectInput鼠標對象的初始化代碼,只能這么一步步的來,沒什么好說的。
//=================================
LPDIRECTINPUT8 pDI;
LPDIRECTINPUTDEVICE8 lpMouse;
// 存放鼠標光標的Surface
LPDIRECT3DSURFACE8 lpDSCursor;
HANDLE hMouseEvent;
//是有符號型,所以可以判斷光標是否移出屏幕來決定是否卷屏
short MouseX = FULLSCREEN_WIDTH/2,MouseY =FULLSCREEN_HEIGHT/2;
bool InitInput()
{
HRESULT hres;
hres = DirectInput8Create(hInst, DIRECTINPUT_VERSION, IID_IDirectInput8, (LPVOID *)&lpDI, NULL);
if(FAILED(hres))
return FALSE;
hres = lpDI->CreateDevice(GUID_SysMouse, &lpMouse,NULL);
if(FAILED(hres))
return FALSE;
hres = lpMouse->SetDataFormat(&c_dfDIMouse);
if(FAILED(hres))
return FALSE;
hres = lpMouse->SetCooperativeLevel(hMainWnd,DISCL_EXCLUSIVE | DISCL_FOREGROUND);
if(FAILED(hres))
return FALSE;
hMouseEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if(!hMouseEvent)
return FALSE;
hres = lpMouse->SetEventNotification(hMouseEvent);
if(FAILED(hres))
return FALSE;
DIPROPDWORD dipdw;
dipdw.diph.dwSize = sizeof(DIPROPDWORD);
dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
dipdw.diph.dwObj = 0;
dipdw.diph.dwHow = DIPH_DEVICE;
dipdw.dwData = MOUSE_SAMPLEBUFFER; // 預定義為16
hres = lpMouse->SetProperty(DIPROP_BUFFERSIZE,&dipdw.diph);
if(FAILED(hres))
return FALSE;
lpMouse->Acquire();
return TRUE;
}
現(xiàn)在是我們完全接管了鼠標,那么無可非議的,鼠標光標的顯示任務也落到了我們頭上,不過在D3D8入門里我提到了,光標的顯示可以由D3D8支持。下面我們就來創(chuàng)建一個光標:
D3DLOCKED_RECT dlr;
//光標的Surface只能是A8R8G8B8格式的,占了一個alpha字節(jié)又不支持半透明,真是shit
hres = lpDevice->CreateImageSurface(32, 32, D3DFMT_A8R8G8B8,&lpDSCursor);
if(FAILED(hres))
return FALSE;
hres = lpDSCursor->LockRect(&dlr, NULL, 0);
if(FAILED(hres))
return FALSE;
// 往Surface里寫數據呀,不用我說了吧
………………
hres = lpDSCursor->UnlockRect();
hres = lpDevice->SetCursorProperties(0, 0, lpDSCursor);
if(FAILED(hres))
return FALSE;
lpDevice->ShowCursor(TRUE);
接下來就是鼠標數據的存取了,這里我只處理了鼠標的移動。
void MouseEvent()
{
DIDEVICEOBJECTDATA od;
HRESULT hres;
DWORD count;
short x = 0, y = 0;
while(1)
{
count = 1;
hres =lpMouse->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), &od,&count, 0);
if(hres == DIERR_INPUTLOST)
{
lpMouse->Acquire();
return;
}
if(FAILED(hres) || !count)
break;
switch(od.dwOfs)
{
case DIMOFS_X:
x += (short)od.dwData;
break;
case DIMOFS_Y:
y += (short)od.dwData;
break;
//物理設備上左鍵或右鍵按下/釋放,如有左右鍵交換可是要自己判斷的
case DIMOFS_BUTTON0:
case DIMOFS_BUTTON1:
if(od.dwData & 0x80)
// 鍵按下
…………
else
// 鍵釋放
…………
}
}
if(x || y)
{
MouseX += x;
MouseY += y;
// 決定光標的位置以及是否卷屏等等
…………
lpDevice->SetCursorPosition(MouseX, MouseY,D3DCURSOR_IMMEDIATE_UPDATE);
}
}
好了,現(xiàn)在算是完了。但是我個人覺得有一點小小的缺憾,大家如果試一下就會發(fā)現(xiàn),鼠標移動的總比Windows下慢一些,這是為什么?我在Windows的鼠標設置里看到一個加速選項,覺得可能是由于這個原因。那就模擬一下了。(以下只列出上面函數的改動部分)
short xaccel,yaccel;
xaccel = yaccel = 1;
while(1)
{
…………
switch(od.dwOfs)
{
case DIMOFS_X:
x += (short)od.dwData*xaccel;
xaccel++;
break;
case DIMOFS_Y:
y += (short)od.dwData*yaccel;
yaccel++;
break;
…………
}
………
}
經過這樣改動后,鼠標再移動起來果然順暢了很多。
此外,DIDEVICEOBJECTDATA結構中還有一個時間標記,用這個可以
判斷鼠標的雙擊,現(xiàn)在我們的鼠標模擬已經初具雛形。自己接管鼠標后,就可以定義方便自己的消息比如什么拖動啦(在Windows下判斷拖動就是煩,自己定
義一個)、三擊啦什么的,好處是不言而喻的,當然也帶來了壞處——就是編寫的代碼就多了,不過這就是游戲程序員的職責呀。