Windows編程 DirectInput 鼠標和鍵盤的輸入
來源:程序員人生 發布時間:2016-09-27 09:00:43 閱讀次數:2904次
版本:VS2015 語言:C++
書的第8章是1些數學的知識,和1個圖形庫的創建。數學知識是有必要看1看的,我這里就不做多的介紹了,圖形庫的話反正你現在的win7+系統上也運行不了,看看就好。由于雖然這本書(《Windows游戲編程大師技能》)非常的經典,但是代碼都是比較老的,很多都已過時了不能運行,所以我們要明確我們的目的,學好基礎知識,編寫1下程序練練手,熟習熟習Direct的流程和原理,至于正真的想要應用的話,憑著這些知識學習最新的dx,或直接上引擎,研究引擎中的代碼。
好了,說了這么多,其實這本書就是為了入門。
今天講的是第9章的內容,主要實現使用鍵盤和鼠標控制。
首先是基礎的代碼:
#define KEYDOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0) //判斷當前的按鍵是不是被按下
#define DDRAW_INIT_STRUCT(ddsd) { memset(&ddsd, 0, sizeof(ddsd)); ddsd.dwSize = sizeof(ddsd); }
#define _RGB32BIT(a, r, g, b) ((b) + (g << 8) + (r << 16) + (a << 24))
HWND main_window_handle = NULL; //當前窗口
LPDIRECTDRAW7 lpdd = NULL; //Direct7對象,下稱d7
LPDIRECTDRAWSURFACE7 lpddsprimary = NULL; //主顯示表面指針
LPDIRECTDRAWSURFACE7 lpddsback = NULL; //后備顯示表面
DDSURFACEDESC2 ddsd; //主顯示表面的描寫
int SCREEN_WIDTH = 640; //顯示寬度
int SCREEN_HEIGHT = 480; //顯示高度
int SCREEN_BPP = 32; //色深,現在的機子只能設置為32位,書上可能還是8位的
int CharPosX = 200; //當前顯示人物的x坐標
int CharPosY = 200; //當前任務顯示的y坐標
// 彈出消息
void popMessage(LPWSTR str)
{
MessageBox(main_window_handle, str, TEXT("提示"), MB_OK);
}
// 32位像素上色
void Plot_Pixel_Fast32_2(int x, int y, int red, int green, int blue, int alpha, UINT* video_buffer, int lpitch)
{
video_buffer[x + y * (lpitch >> 2)] = (UINT)(_RGB32BIT(alpha, red, green, blue)); //使用宏直接寫,有點區分的是lpitch需要除以4,由于lpitch算的是橫向的字節數,而我們把主界面的內存弄成UINT型,是32位、4個字節的,上1節中是我理解的不夠深入
}
// 游戲初始化
int Game_Init(void* params = NULL)
{
// 基礎設置
if (FAILED(DirectDrawCreateEx(NULL, (void**)&lpdd, IID_IDirectDraw7, NULL))) //獲得d7對象
return 0;
if (FAILED(lpdd->SetCooperativeLevel(main_window_handle,
DDSCL_FULLSCREEN | DDSCL_ALLOWMODEX | DDSCL_EXCLUSIVE | DDSCL_ALLOWREBOOT
))) //跟windows協作等級設置為全屏,這是最經常使用的參數
return 0;
if (FAILED(lpdd->SetDisplayMode(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, 0, 0))) //設置顯示模式,如果設置為8位會直接出錯
return 0;
// 開始創建顯示主界面
memset(&ddsd, 0, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT; //表明ddsCaps是個有效成員,并且具有后備的緩沖
ddsd.dwBackBufferCount = 2; //表明有1個緩沖
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | //表明該界面是主界面
DDSCAPS_COMPLEX | //表明具有緩沖鏈
DDSCAPS_FLIP; //表明是反正結構的1部份,上面的參數相當因而有緩沖,而這個參數表明可以切換緩沖
if (FAILED(lpdd->CreateSurface(&ddsd, &lpddsprimary, NULL))) //根據界面描寫創建主界面
{
popMessage(TEXT("主表面創建出錯"));
return 0;
}
// 開始創建后備界面
ddsd.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER; //表明該界面是后備界面
if (FAILED(lpddsprimary->GetAttachedSurface(&ddsd.ddsCaps, &lpddsback))) //通過主界面創建出備用表面
{
popMessage(TEXT("創建備用表面出錯了"));
return 0;
}
return 1;
}
// 游戲結束
int Game_Shutdown(void* params = NULL)
{
//// 釋放初始化時創建的對象
if (NULL != lpddsprimary)
{
lpddsprimary->Release();
lpddsprimary = NULL;
}
if (NULL != lpdd) //d7對象不為空的情況下釋放
{
lpdd->Release();
lpdd = NULL;
}
return 1;
}
// 游戲主循環
int Game_Main(void* params = NULL)
{
// 判斷是不是要退出
if (KEYDOWN(VK_ESCAPE))
PostMessage(main_window_handle, WM_CLOSE, 0, 0);
// 初始化主界面描寫
DDRAW_INIT_STRUCT(ddsd);
if (FAILED(lpddsback->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL))) //有備用表面時用備用表面加鎖
{
popMessage(TEXT("LOCK 出錯了"));
}
// 白色的背景
UINT *video_buffer = (UINT*)ddsd.lpSurface;
for (int x = 0; x < 640; ++x)
for (int y = 0; y < 480; ++y)
Plot_Pixel_Fast32_2(x, y, 255, 255, 255, 128, video_buffer, ddsd.lPitch);
//畫人物
//UCHAR *video_buffer = (UCHAR*)ddsd.lpSurface;
for (int x = 0+CharPosX; x < 64+CharPosX; ++x)
for (int y = 0 + CharPosY; y < 64 + CharPosY; ++y)
{
if (x<0 || x>SCREEN_WIDTH - 1 || y<0 || y>SCREEN_HEIGHT⑴) //超越屏幕邊沿的時候不畫
continue;
Plot_Pixel_Fast32_2(x, y, 0, 255, 0, 128, video_buffer, ddsd.lPitch);
}
if (FAILED(lpddsback->Unlock(NULL))) //解鎖
{
popMessage(TEXT("UNLOCK 出錯了"));
}
while (FAILED(lpddsprimary->Flip(NULL, DDFLIP_WAIT))); //切換界面,這邊的while不是很懂,應當每次只會調用1次
return 1;
}
// 消息處理函數
LRESULT CALLBACK WindowProc(HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM IParam)
{
switch (msg)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
DefWindowProc(hwnd, msg, wParam, IParam); //自動處理其他的消息
break;
}
return (1);
}
// 主函數,程序入口
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
// 創建窗口類
WNDCLASSEX wndclass;
wndclass.cbSize = sizeof(WNDCLASSEX);
wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC | CS_DBLCLKS; //窗口的樣式:改變寬度刷新、改變高度刷新、分配裝備描寫表、雙擊信息
wndclass.lpfnWndProc = WindowProc; //回調函數
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); //任務欄上的圖標
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); //光標的讀取
wndclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); //窗口背景
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = TEXT("MyManyTimesWindow"); //窗口的名字
wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION); //利用上的圖標
if (!RegisterClassEx(&wndclass))
return 0;
// 創建窗口,上面的窗口類是1個模版,可以根據上面的模版創建多個窗口,但請注意第2個參數
HWND hwnd = CreateWindowEx(NULL,//WS_EX_TOPMOST, //窗口特性,注釋里設置為永久在最上方顯示
TEXT("MyManyTimesWindow"), //窗口名稱,1定要和窗口類的lpszClassName對應
TEXT("我與DDraw已很屢次了"), //標題
WS_POPUP | WS_VISIBLE, //無邊框樣式配合下面的尺寸實現全屏顯示
0, 0, //左上角坐標
SCREEN_WIDTH, SCREEN_HEIGHT,
NULL, //父窗口句柄,如果是桌面則為NULL
NULL, //菜單窗口句柄
hInstance, //利用程序實例
NULL //高級特性
);
if (!hwnd) //創建失敗返回
return 0;
main_window_handle = hwnd;
ShowWindow(hwnd, nCmdShow); //顯示窗口
UpdateWindow(hwnd); //刷新窗口
MSG msg; //消息緩存
srand(GetTickCount()); //隨機1個種子
if (0 == Game_Init()) //游戲初始化
return 0;
// 進入主循環
while (true)
{
DWORD start_time = GetTickCount(); //獲得當前時間
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) //有消息事件,注意最后1個參數,如果設置為PM_NOREMOVE的話不會燒毀消息隊列中的消息
{
if (msg.message == WM_QUIT)
break;
TranslateMessage(&msg); //轉譯消息
DispatchMessage(&msg); //將消息發送給WindowProc函數處理
}
else //沒有消息
{
//游戲主循環
Game_Main();
// 延時期碼,鎖定30幀
while ((GetTickCount() - start_time) < 33);
}
}
Game_Shutdown(); //游戲結束
return msg.wParam;
}
注意導入庫文件。嘛,都是之前的知識,復制粘貼過來就好了,效果:
顯示了1個綠色的方型,這就是我們要操控的勇士了。來吧,接下來要到達的效果就是使用wasd,控制左右移動,首先我們加上速度的全局變量(放在角色位置變量的下放):
int CharSpdX = 0; //當前顯示人物x方向的速度
int CharSpdY = 0; //當前顯示人物y方向的速度
然后導入文件input.lib和input8.lib,并包括dinput.h頭文件。
在文件的全局處加上宏和變量:
#define DIKEYDOWN(data, n) (data[n] & 0x80)
HINSTANCE h_instance = NULL; //當前利用程序的句柄,玩家自己在main函數中設置1下
LPDIRECTINPUT8 lpdi; //輸入對象
LPDIRECTINPUTDEVICE8W lpdikey = NULL; //鍵盤裝備
UCHAR keyboard_state[256]; //鍵盤當前的狀態
初始化函數中添加獲得輸入對象和輸入裝備等的代碼,毛病處理去掉了,玩家自己添加1下:
// 開始創建輸入對象
FAILED(DirectInput8Create(h_instance, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&lpdi, NULL));
FAILED(lpdi->CreateDevice(GUID_SysKeyboard, &lpdikey, NULL)); //創建鍵盤裝備FAILED(lpdikey->SetCooperativeLevel(main_window_handle, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE)); //設置協作等級,鍵盤和鼠標設置為這里的可以后臺接受和非獨占,而游戲手柄則要設置為獨占
FAILED(lpdikey->SetDataFormat(&c_dfDIKeyboard))); //設置鍵盤的數據格式FAILED(lpdikey->Acquire()); //獲得鍵盤
然后在游戲Shutdown燒毀各個對象:
if (lpdikey) //釋放鍵盤相干對象
lpdikey->Unacquire();
if (lpdikey)
lpdikey->Release();
if (lpdi)
lpdi->Release();
最后是游戲主循環,哈哈,激動人心的時候到了:
// 獲得鍵盤狀態
if (lpdikey->GetDeviceState(sizeof(UCHAR[256]), (LPVOID)keyboard_state))
{
popMessage(TEXT("獲得鍵盤狀態出錯了"));
}
// 處理按鍵
if (DIKEYDOWN(keyboard_state, DIK_W))
CharSpdY = ⑶;
else if (DIKEYDOWN(keyboard_state, DIK_S))
CharSpdY = 3;
else
CharSpdY = 0;
if (DIKEYDOWN(keyboard_state, DIK_D))
CharSpdX = 3;
else if (DIKEYDOWN(keyboard_state, DIK_A))
CharSpdX = ⑶;
else
CharSpdX = 0;
// 調劑人物的位置
CharPosX += CharSpdX;
CharPosY += CharSpdY;
這段代碼放在刷新白色背景的上面,然后可以試試效果了。我們的勇者是否是可以上下左右移動了?嗯,太棒了!
下面是鼠標信息獲得的方法。
嗯,首先要說明1下,DirectX中的鼠標跟Windows里的鼠標其實沒有甚么關系,Windows鼠標就是你白色的指針,它就是在屏幕中的某個位置,而dx中鼠標裝備的意思是真實裝備操控時所產生的信息。不是很理解的話,我會在程序中說到這個問題。
上代碼(初始化和釋放就不說了,跟鍵盤的方法差不多,換成mouse相干的就OK了):
//全局變量
LPDIRECTINPUTDEVICE8W lpdimouse = NULL; //鼠標裝備
DIMOUSESTATE2 mouse_state; //鼠標的狀態
int MousePosX = 0; //dx計算出來的鼠標位置
int MousePosY = 0;
// 這里是游戲主循環里的內容,請放在獲得鍵盤狀態的下面
// 處理鼠標
if (FAILED(lpdimouse->GetDeviceState(sizeof(DIMOUSESTATE), (LPVOID)&mouse_state)))
{
popMessage(TEXT("獲得鼠標狀態出錯了"));
}
bool isMouseProccess = false;
MousePosX += mouse_state.lX; //計算當前鼠標的位置,取得的參數是當前鼠標位置與上1幀位置的差值
MousePosY += mouse_state.lY;
SetCursorPos(MousePosX, MousePosY); //設置Windows中鼠標的位置,如果不設置的話,可能會出現計算出來的位置與當前顯示位置不匹配的情況,1定要記得Windows鼠標的位置和dx中鼠標的位置是隔離的
if (DIKEYDOWN(mouse_state.rgbButtons, 0)) //當鼠標按下的時候,人物瞬移到對應位置
{
isMouseProccess = true;
CharPosX = MousePosX - 32;
CharPosY = MousePosY - 32;
}
好了,現在按下鼠標,角色就跟這鼠標移動了,效果是否是很棒啊,哈哈。
至于手柄,現在手上也沒有手柄,所以暫時就算了,這類其他的裝備也就是比鼠標鍵盤麻煩了點,思路還是1樣的。
下1節會講聲音相干的內容,而講完聲音,dx的內面貌似就是已完了,如果有好玩的話寫1點后面章節的知識,1般般的話就算了,下1節就是最后1節了。
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈