程序员人生 网站导航

Windows编程 DirectInput 鼠标和键盘的输入

栏目:php教程时间:2016-09-27 09:00:43

版本: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节了。



------分隔线----------------------------
------分隔线----------------------------

最新技术推荐