同时检测多个按键和平滑按键处理
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 的窗口处于活动状态,从而解决非活动状态的按键处理问题。
添加评论
取消回复