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

版本:VS2015 语言:C++

 

书的第八章是一些数学的知识,以及一个图形库的创建。数学知识是有必要看一看的,我这里就不做多的介绍了,图形库的话反正你现在的win7+系统上也运行不了,看看就好。因为虽然这本书(《Windows游戏编程大师技巧》)非常的经典,但是代码都是比较老的,很多都已经过时了不能运行,所以我们要明确我们的目的,学好基础知识,编写一下程序练练手,熟悉熟悉Direct的流程以及原理,至于正真的想要运用的话,凭着这些知识学习最新的dx,或者直接上引擎,研究引擎中的代码。

 

好了,说了这么多,其实这本书就是为了入门。

 

今天讲的是第九章的内容,主要实现使用键盘和鼠标控制。

 

首先是基础的代码:

#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个字节的,上一节中是我理解的不够深刻
}


// 游戏初始化
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;	//表明有一个缓冲
	ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | //表明该界面是主界面
							DDSCAPS_COMPLEX | //表明拥有缓冲链
							DDSCAPS_FLIP;	//表明是反正结构的一部分,上面的参数相当于是有缓冲,而这个参数表明可以切换缓冲

	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-1)	//超出屏幕边缘的时候不画
				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不是很懂,应该每次只会调用一次

	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;

	// 创建窗口,上面的窗口类是一个模版,可以根据上面的模版创建多个窗口,但请注意第二个参数
	HWND hwnd = CreateWindowEx(NULL,//WS_EX_TOPMOST,	//窗口特性,注释里设置为永远在最上方显示
		TEXT("MyManyTimesWindow"),	//窗口名称,一定要和窗口类的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());	//随机一个种子

	if (0 == Game_Init())	//游戏初始化
		return 0;

	// 进入主循环
	while (true)
	{
		DWORD start_time = GetTickCount();	//获取当前时间

		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))	//有消息事件,注意最后一个参数,如果设置为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;
}


注意导入库文件。嘛,都是以前的知识,复制粘贴过来就行了,效果:

Windows编程 DirectInput 鼠标和键盘的输入_第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函数中设置一下
LPDIRECTINPUT8 lpdi;	//输入对象
LPDIRECTINPUTDEVICE8W lpdikey = NULL;	//键盘设备
UCHAR keyboard_state[256];	//键盘当前的状态

初始化函数中添加获取输入对象和输入设备等的代码,错误处理去掉了,玩家自己添加一下:

// 开始创建输入对象
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 = -3;
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 = -3;
else
	CharSpdX = 0;

// 调整人物的位置
CharPosX += CharSpdX;
CharPosY += CharSpdY;

这段代码放在刷新白色背景的上面,然后可以试试效果了。我们的勇者是不是可以上下左右移动了?嗯,太棒了!

 

下面是鼠标信息获取的方法。

 

嗯,首先要说明一下,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;	//计算当前鼠标的位置,获得的参数是当前鼠标位置与上一帧位置的差值
MousePosY += mouse_state.lY;
SetCursorPos(MousePosX, MousePosY);	//设置Windows中鼠标的位置,如果不设置的话,可能会出现计算出来的位置与当前显示位置不匹配的情况,一定要记得Windows鼠标的位置和dx中鼠标的位置是隔离的	
if (DIKEYDOWN(mouse_state.rgbButtons, 0))	//当鼠标按下的时候,人物瞬移到对应位置
{
	isMouseProccess = true;
	CharPosX = MousePosX - 32;
	CharPosY = MousePosY - 32;
}

好了,现在按下鼠标,角色就跟这鼠标移动了,效果是不是很棒啊,哈哈。

 

至于手柄,现在手上也没有手柄,所以暂时就算了,这类其他的设备也就是比鼠标键盘麻烦了点,思路还是一样的。

 

下一节会讲声音相关的内容,而讲完声音,dx的内容貌似就是已经完了,如果有好玩的话写一点后面章节的知识,一般般的话就算了,下一节就是最后一节了。



你可能感兴趣的:(DirectX,OpenGL层)