YangW

无为,无我,无欲,居下,清虚,自然

同时检测多个按键和平滑按键处理

getch() 函数,用于返回用户输入的字符。当连续按键时,该函数返回第一个字符和第二个字符之间,默认有 0.5 秒的延时,并且之后的连续字符,默认是每秒钟 15 次输入。这两个数值可以在控制面板中设置。

如果需要平滑的按键输入,或者同时按下多个按键,就不能用 getch() 了,需要使用另一个 Windows API 函数:GetAsyncKeyState()。该函数原型如下:

SHORT GetAsyncKeyState(
	int vKey		// virtual-key code
);

vKey 是要检测的按键的虚拟键码,常用的如 VK_UP、VK_DOWN 等,分别表示方向键的上、下等。需要注意:对于 26 个字母的键码,可以直接写 'A'、'B'……,而不要写 VK_A、VK_B。数字键也是,请直接写 '0'、'1'……。全部的 256 种虚拟键码,请参考 MSDN 中的 Virtual-Key Codes。

返回的 SHORT 值,如果最高位为 1,表示该键被按下;否则表示该键弹起。该函数的最低位还可以用来检测开关键(比如大小写锁定键)的状态。作为按键处理,还可以使用 GetKeyState、GetKeyboardState 等函数,详细请参考 MSDN 手册中的 Keyboard Input Functions 部分。

下面给一个简单的例子,该范例是用键盘的上下左右键移动一个圆,并且可以通过左 Shift 放大、左 Ctrl 缩小,几个按键可以同时灵活地控制圆。代码如下:

// 程序名称:同时检测多个按键及平滑按键输入的范例
// 编译环境:Visual C++ 6.0 / 2010,EasyX 惊蛰版
//
#include <graphics.h>

/////////////////////////////////////////////
// 定义常量、枚举量、结构体、全局变量
/////////////////////////////////////////////

#define	CMD_UP			1
#define	CMD_DOWN		2
#define	CMD_LEFT		4
#define	CMD_RIGHT		8
#define	CMD_ZOOMIN		16
#define	CMD_ZOOMOUT		32
#define	CMD_QUIT		64

// 声明圆的坐标和半径
int g_x, g_y, g_r;



/////////////////////////////////////////////
// 函数声明
/////////////////////////////////////////////

void Init();						// 初始化
void Quit();						// 退出
int  GetCommand();					// 获取控制命令
void DispatchCommand(int _cmd);		// 分发控制命令
void OnUp();						// 上移
void OnDown();						// 下移
void OnLeft();						// 左移
void OnRight();						// 右移
void OnZoomIn();					// 放大
void OnZoomOut();					// 缩小



/////////////////////////////////////////////
// 函数定义
/////////////////////////////////////////////

// 主函数
void main()
{
	Init();

	int c;
	do
	{
		c = GetCommand();
		DispatchCommand(c);
		Sleep(10);
	}while(!(c & CMD_QUIT));

	Quit();
}

// 初始化
void Init()
{
	// 设置绘图屏幕和绘图模式
	initgraph(640, 480);
	setwritemode(R2_XORPEN);

	// 设置圆的初始位置和大小
	g_x = 320;
	g_y = 240;
	g_r = 20;

	// 显示操作说明
	setfont(14, 0, _T("宋体"));
	outtextxy(20, 270, _T("操作说明"));
	outtextxy(20, 290, _T("上:上移"));
	outtextxy(20, 310, _T("下:下移"));
	outtextxy(20, 330, _T("左:左移"));
	outtextxy(20, 350, _T("右:右移"));
	outtextxy(20, 370, _T("左 Shift:放大"));
	outtextxy(20, 390, _T("左 Ctrl:缩小"));
	outtextxy(20, 410, _T("ESC:退出"));
	outtextxy(20, 450, _T("注:可以同时按多个键,但能同时按下的键的数量,受键盘硬件限制"));

	// 画圆
	circle(g_x, g_y, g_r);
}

// 退出
void Quit()
{
	closegraph();
}

// 获取控制命令
int GetCommand()
{
	int c = 0;

	if (GetAsyncKeyState(VK_LEFT) & 0x8000)		c |= CMD_LEFT;
	if (GetAsyncKeyState(VK_RIGHT) & 0x8000)	c |= CMD_RIGHT;
	if (GetAsyncKeyState(VK_UP) & 0x8000)		c |= CMD_UP;
	if (GetAsyncKeyState(VK_DOWN) & 0x8000)		c |= CMD_DOWN;
	if (GetAsyncKeyState(VK_LSHIFT) & 0x8000)	c |= CMD_ZOOMIN;
	if (GetAsyncKeyState(VK_LCONTROL) & 0x8000)	c |= CMD_ZOOMOUT;
	if (GetAsyncKeyState(VK_ESCAPE) & 0x8000)	c |= CMD_QUIT;

	return c;
}

// 分发控制命令
void DispatchCommand(int _cmd)
{
	if (_cmd & CMD_UP)			OnUp();
	if (_cmd & CMD_DOWN)		OnDown();
	if (_cmd & CMD_LEFT)		OnLeft();
	if (_cmd & CMD_RIGHT)		OnRight();
	if (_cmd & CMD_ZOOMIN)		OnZoomIn();
	if (_cmd & CMD_ZOOMOUT)		OnZoomOut();
}

// 上移
void OnUp()
{
	circle(g_x, g_y, g_r);
	if (g_y <= 0) g_y = 480; else g_y-=2;
	circle(g_x, g_y, g_r);
}

// 下移
void OnDown()
{
	circle(g_x, g_y, g_r);
	if (g_y >= 480) g_y = 0; else g_y+=2;
	circle(g_x, g_y, g_r);
}

// 左移
void OnLeft()
{
	circle(g_x, g_y, g_r);
	if (g_x <= 0) g_x = 640; else g_x-=2;
	circle(g_x, g_y, g_r);
}

// 右移
void OnRight()
{
	circle(g_x, g_y, g_r);
	if (g_x >= 640) g_x = 0; else g_x+=2;
	circle(g_x, g_y, g_r);
}

// 放大
void OnZoomIn()
{
	circle(g_x, g_y, g_r);
	if (g_r < 100) g_r++;
	circle(g_x, g_y, g_r);
}

// 缩小
void OnZoomOut()
{
	circle(g_x, g_y, g_r);
	if (g_r > 10) g_r--;
	circle(g_x, g_y, g_r);
}

再额外说一个小问题:由于 GetAsyncKeyState() 函数获取的按键状态是直接取自硬件,并非取自消息队列。所以,即便程序处非活动状态,GetAsyncKeyState() 仍然可以正确获取按键状态。所以会有这样一个问题:比如你写了一个打字练习的小游戏,在游戏中途切换到另一个应用去发邮件,你会看到发邮件录入文字时,你的打字练习小游戏仍然会接受键盘输入。很明显,这时候需要判当前应用是否处于活动状态。解决方法有多种,例如,通过 Windows API 函数 GetForegroundWindow() 获取到当前前景窗口的句柄,再和 EasyX 窗口的句柄对比,如果相同,就表示 EasyX 的窗口处于活动状态,从而解决非活动状态的按键处理问题。

分享到

添加评论