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