键盘按键按下会触发按键消息
每秒大概32个
)EGE中,产生的键盘消息用下面的 key_msg 结构体保存,包含是什么按键,是按下还是松开,是否同时按下了辅助键的信息。
按键消息结构体
typedef struct key_msg {
unsigned int msg; //消息类型
unsigned int key; //按键码
unsigned int flags; //辅助键标志
}key_msg;
结构体key_msg 有三个成员,分别是按键消息类型,按键码和辅助键标志。
用来判断按键是按下还是松开
msg 表示消息类型,有以下几个枚举值
key_msg_down 键盘按下消息。
key_msg_up 键盘弹起消息。
key_msg_char 键盘字符输入消息。(测试时没检测到有这种类型的值)
定义如下:
typedef enum key_msg_e {
key_msg_down = 1,
key_msg_up = 2,
key_msg_char = 4,
}key_msg_e;
使用方法:
假设keyMsg是key_msg类型的变量
keyMsg.msg == key_msg_down
keyMsg.msg == key_msg_up
来试试按键消息类型的检测, 多个按键按下都是可以的
不用纠结代码意思,做次按键短按,长按,松开的实验
#include
int main()
{
initgraph(640, 480, 0);
setcaption("按键消息类型测试");
setbkcolor(WHITE);
setcolor(BLACK);
int keyUpCount = 0, keyDownCount = 0, keyCharCount = 0;
for (; is_run(); delay_fps(60)) {
key_msg keyMsg = { 0 };
while (kbmsg()) {
keyMsg = getkey();
switch(keyMsg.msg) {
case key_msg_up: keyUpCount++; break;
case key_msg_down: keyDownCount++; break;
case key_msg_char: keyCharCount++; break;
}
}
cleardevice();
xyprintf(100, 300, "down 计数 = %d up 计数 = %d char 计数 = %d",
keyUpCount, keyDownCount, keyCharCount);
}
return 0;
}
可以看出,按键长按按键消息是这样的:
(1的时候长按,6的时候松开, 9的时候再长按,15的时候再松开)
消息类型 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
up | ∙ \ \bullet ∙ | ∙ \ \bullet ∙ | ||||||||||||||
down | ∙ \ \bullet ∙ | ∙ \ \bullet ∙ | ∙ \ \bullet ∙ | ∙ \ \bullet ∙ | ∙ \ \bullet ∙ | ∙ \ \bullet ∙ | ∙ \ \bullet ∙ | ∙ \ \bullet ∙ | ∙ \ \bullet ∙ |
用来 判断是来自哪个按键的消息
如果是按下和弹起的消息,key则表示按键虚拟键码,否则为gbk编码字符消息。
平时用来判断是否是键盘A键的消息, 使用 if (keyMsg.key == ‘A’) 来判断即可。
关于虚拟键码,可以自己查看定义,这是windowsAPI定义的一些宏,对应键盘上的各种按键,一般以 VK_ 开头,如
ESC键: VK_ESCAPE
上方向键:VK_UP
右方向键:VK_RIGHT
虚拟键码数不足256个, 每个键码的值用一个字节即可表示
可以在VS中输入VK_UP, 然后按快捷键F12转到定义,就可以查看各种虚拟键码。
字母键的虚拟键码就等于它的大写字母的ASCII码值。
如果是想判断是否是上方向键的消息, if (keyMsg.key == VK_UP)
EGE中也自己定义的一些枚举值,与虚拟键码是相等的, 可以使用ege中的定义, 以 key_ 开头
typedef enum key_code_e {
//鼠标左右中三键
key_mouse_l = 0x01,
key_mouse_r = 0x02,
key_mouse_m = 0x04,
//退格,Tab,回车键
key_back = 0x08,
key_tab = 0x09,
key_enter = 0x0d,
//辅助键
key_shift = 0x10,
key_control = 0x11,
key_menu = 0x12,
key_pause = 0x13,
//大写锁定,esc键,空格键
key_capslock = 0x14,
key_esc = 0x1b,
key_space = 0x20,
//上一页,下一页,行首,行尾
key_pageup = 0x21,
key_pagedown = 0x22,
key_home = 0x23,
key_end = 0x24,
//方向键
key_left = 0x25,
key_up = 0x26,
key_right = 0x27,
key_down = 0x28,
key_print = 0x2a,
key_snapshot = 0x2c,
//插入,删除键
key_insert = 0x2d,
key_delete = 0x2e,
//大键盘数字键
key_0 = 0x30,
key_1 = 0x31,
key_2 = 0x32,
key_3 = 0x33,
key_4 = 0x34,
key_5 = 0x35,
key_6 = 0x36,
key_7 = 0x37,
key_8 = 0x38,
key_9 = 0x39,
//字母键中的A键和Z键
key_A = 0x41,
key_Z = 0x5a,
//windows键
key_win_l = 0x5b,
key_win_r = 0x5c,
key_sleep = 0x5f,
//小键盘的数字键,就是九个数字围成九宫格那个
key_num0 = 0x60,
key_num1 = 0x61,
key_num2 = 0x62,
key_num3 = 0x63,
key_num4 = 0x64,
key_num5 = 0x65,
key_num6 = 0x66,
key_num7 = 0x67,
key_num8 = 0x68,
key_num9 = 0x69,
//小键盘的符号键
//EGE缺少定义,故拿虚拟键码来补充
//VK_MULTIPLY 0x6A *
//VK_ADD 0x6B +
//VK_SEPARATOR 0x6C
//VK_SUBTRACT 0x6D -
//VK_DECIMAL 0x6E .
//VK_DIVIDE 0x6F /
//这个是键盘上方的12个功能键
key_f1 = 0x70,
key_f2 = 0x71,
key_f3 = 0x72,
key_f4 = 0x73,
key_f5 = 0x74,
key_f6 = 0x75,
key_f7 = 0x76,
key_f8 = 0x77,
key_f9 = 0x78,
key_f10 = 0x79,
key_f11 = 0x7a,
key_f12 = 0x7b,
//小键盘数字锁
key_numlock = 0x90,
key_scrolllock = 0x91,
//可能左右两边都有一个
key_shift_l = 0xa0,
key_shift_r = 0xa1,
key_control_l = 0xa2,
key_control_r = 0xa3,
key_menu_l = 0xa4,
key_menu_r = 0xa5,
//大键盘上的符号键
key_semicolon = 0xba, ; 分号
key_plus = 0xbb, + 加号
key_comma = 0xbc, , 逗号
key_minus = 0xbd, - 减号
key_period = 0xbe, . 句号
key_slash = 0xbf, / 右斜杠
key_tilde = 0xc0, ` 波浪符(下面的点)
key_lbrace = 0xdb, [ 左方
key_backslash = 0xdc, \ 反斜杠
key_rbrace = 0xdd, ] 右方
key_quote = 0xde, ' 引号
key_ime_process = 0xe5,
}key_code_e;
从上面挑选一些比较常用的键码
方向键:上下左右
key_up, key_down, key_left, key_right
字母键:
大写字母的ASCII值, 如S键, 'S’
大键盘的数字键:
key_数字
键盘右边的小键盘数字键
key_num数字
ESC键
key_esc
空格键
key_space
按键参数,可能为以下值的组合(位或的方式), 如果没有那就是0了
typedef enum key_flag_e {
key_flag_shift = 0x100,
key_flag_ctrl = 0x200,
}key_flag_e;
如果你只是想判断是哪个按键按下的或者获取输入的字符,这里有种简单的方式,那就是 kbhit() 和 getch() 的组合
最常用的getch(),这时候程序会暂停,等待用户按下按键,返回值是按键输入的字符的ASCII值或者是功能键的码值。(这个码值不是虚拟键码,如果能用ASCII表示,那就是等于ASCII码,如果不能表示,那么码值大于255
)
int ch = getch();
(特别注意)
getch() 返回的值需要用两个字节表示,部分键返回值大于0xFF, 所以不能用char存储,否则很可能被截断,应该用 int。
需要注意:
如果不想暂停,可以使用 kbhit() 检测是否有字符输入,如果没有就跳过,有就读取字符,这样就不影响程序运行了。
//判断是否有按键字符输入,有就读取字符。
if (kbhit()) {
int ch = getch();
...
}
但是,一般都是用while一次读取出全部的消息
//判断是否有按键字符输入,有就读取字符。
while (kbhit()) {
int ch = getch();
...
}
&emsp因为用 if 的话,如果你帧率比较小,并且按键长按产生了大量的按键消息的话,处理就没那么快了。键盘消息就会堆积起来,有种滞后的感觉。当然,如果你帧率较快,那么单键长按就什么问题。(比较快是多快? 帧率大于34)
下面来看看滞后的情况,设置成每秒20帧, 按键长按,然后松开,就会看到。
修改 delay_fps() 中的数来改变帧率,看看滞后的情况变化
#include
int main()
{
initgraph(640, 480, 0);
setcaption("按键消息处理滞后");
setbkcolor(WHITE);
setcolor(BLACK);
int keyCount = 0;
for (; is_run(); delay_fps(20)) {
key_msg keyMsg = { 0 };
if (kbhit()) {
int key = getch();
keyCount++;
}
cleardevice();
xyprintf(100, 300, "按键计数 = %d ",
keyCount);
}
return 0;
}
所以一般是用while来处理,这样就能很快将键盘消息清空,而不用管帧率是多少
来看看,使用了while来处理,按键长按,即使是10FPS也没事。
绘图的部分不要放在while循环中,因为绘图是需要 控制帧率 的,在里面循环是非常快的,绘图放在里面while循环里面,相当于产生多少消息就绘制多少次,会占用过多的CPU
像按键计数这种不需要控制帧率的是可以放在while循环中
字符输入检测示例:
#include
int main()
{
initgraph(640, 480, 0);
setcaption("按键消息处理");
setbkcolor(WHITE);
setcolor(BLACK);
int keyCount = 0;
for (; is_run(); delay_fps(10)) {
cleardevice();
//按键处理
while (kbhit()) {
int key = getch();
keyCount++;
}
xyprintf(100, 300, "按键计数 = %d ",
keyCount);
}
return 0;
}
按键按下 getch() 到底返回什么,可以通过实验的方式
下面是一段测试代码,按下什么键,就会显示 getch() 返回的码值
如,通过实验,方向键左上右下分别是293, 294, 295, 296
#define SHOW_CONSOLE
#include
#include
int main()
{
initgraph(640, 480,0);
setbkcolor(WHITE);
for (; is_run(); delay_fps(60)) {
while (kbhit()) {
int key = getch();
printf("%c, %d, %#x\n", key, key, key);
}
}
closegraph();
return 0;
}
这个是通过获取鼠标消息结构体的方式,结构体包含了更多的信息。
kbmsg() 和 getkey() 的组合, kbmsg() 判断有无按键消息,getkey() 则是获取按键消息。
getkey() 也会暂停程序,等待用户按下按键。getkey() 返回一个key_msg 结构体。
用法如下所示:
key_msg keyMsg = {0};
while (kbmsg()) {
keyMsg = getkey();
这里做简单的按键消息处理,但绘图的代码不应该放这里
}
绘图代码应该放在这里
鼠标左键双击和 点击后拖动会产生按键消息,被getkey()获取。
得到 key_msg 后,就可以读取
按键消息检测示例:
#include
int main()
{
initgraph(640, 480, 0);
setcaption("按键消息处理");
setbkcolor(WHITE);
setcolor(BLACK);
int keyCount = 0;
for (; is_run(); delay_fps(10)) {
cleardevice();
key_msg keyMsg = {0};
//按键消息处理
while (kbmsg()) {
keyMsg = getkey();
if (keyMsg.key == 'A' && keyMsg.msg == key_msg_down)
keyCount++;
}
xyprintf(100, 300, "A键 按下计数 = %d ",
keyCount);
}
return 0;
}
判断某个按键是否按下。这个不需要处理按键消息,直接获取按键状态。
按下返回1,没按下返回0, 相当于 true 和 false
由于键盘电路问题,当某些按键同时按下时,会有无法识别出按键按下松开的现象。例如S键,D键和K键这三个按键,任意两个按下后,另一个按键是检测不出来按没按下的,这是电路问题。
int keystate(int key);
其中key是虚拟键码, 如果虚拟键码是字母键或数字键(大键盘)的,就是它的字符值(大写)。
注意,字母键的表示使用大写,如检测A键, 是 keystate(‘A’) , 而不是keystate(‘a’)
小键盘上的数字键则用其它宏表示, 如数字3, VK_NUMPAD3。也可以使用上面EGE自己定义的 key_code_e 枚举值
也可以用来获取鼠标按键状态,如鼠标左键码 key_mouse_l
if (keystate(VK_ESCAPE)) {
// ESC键按下了
}
或者
if (keystate(key_esc) {
}
检测A键
if (keystate('A') {
}
当然,检测按键也可以通过处理按键消息的方式,只不过比较麻烦
if (keyMsg.key == 键码 && keyMsg.msg == key_msg_down)
if (keyMsg.key == 键码 && keyMsg.msg == key_msg_up)
检测按键的状态可以直接由 keystate() 检测,比较方便,但如果你想通过处理按键消息的方式来获得按键状态也是可以的
为按键设置一个状态变量,初始为up状态。在按键消息处理时,只要检测到 该按键的down 消息,就把状态设为down 状态,若检测到该按键的 up消息,则把该按键的状态设置为 up.
下面是示例(非完整程序),只适合单键按下,不适合多键同时按键的情况。
//按键状态
enum KeyState
{
KEY_UP = 0,
KEY_DOWN = 1
};
//记录按键的状态
KeyState keyState = KEY_UP;
for (; is_run(); delay_fps(60)) {
key_msg kMsg = { 0 };
while (kbmsg()) {
kMsg = getkey();
keyState = (kMsg.msg == key_msg_down) ? KEY_DOWN : KEY_UP;
}
在这里由keyState的值就可得出按键的状态,
当然,只能是对一个按键来说
}
//按键状态
enum KeyState
{
KEY_UP = 0,
KEY_DOWN = 1
};
//记录26个字母键按键的状态, KEY_UP等于0,只给出第一个初始值,那剩下的都是初始化为0,,正好是KEY_UP
KeyState keyStates[26] = {KEY_UP};
for (; is_run(); delay_fps(60)) {
key_msg kMsg = { 0 };
while (kbmsg()) {
kMsg = getkey();
int keyId = kMsg.key;
KeyState state = (kMsg.msg == key_msg_down) ? KEY_DOWN : KEY_UP;
//如果是字母按键(键码都是用大写表示)
if ('A' <= keyId && keyId <= 'Z')
keyStates[keyId - 'A'] = state;
}
//这里可以由keyStates得出各个按键的状态
}
键盘由于键盘主板电路问题,并不能识别很多个同时按下的情况,当已经有很多个按键同时按下时,如果有其它的一些按键按下,电路就有可能不能将其识别出来, 比如S, D和K,我键盘上按下其中两个,剩下的一个按下是检测不出来的
#include
enum KeyState
{
KEY_UP,
KEY_DOWN
};
const int SCREEN_WIDTH = 600, SCREEN_HEIGHT = 300;
int main()
{
initgraph(SCREEN_WIDTH, SCREEN_HEIGHT, 0);
setcaption("字母按键状态显示");
setbkcolor(WHITE);
setcolor(BLACK);
setfillcolor(EGERGB(0XFF, 0x80, 0XFF));
//透明文字背景,不然带色块的
setfont(20, 0, "仿宋");
setbkmode(TRANSPARENT);
KeyState keyStates[26] = { KEY_UP };
for (; is_run(); delay_fps(60)) {
key_msg kMsg = { 0 };
while (kbmsg()) {
kMsg = getkey();
int keyId = kMsg.key;
KeyState state = (kMsg.msg == key_msg_down) ? KEY_DOWN : KEY_UP;
//如果是字母按键
if ('A' <= keyId && keyId <= 'Z')
keyStates[keyId - 'A'] = state;
}
/*上面是做位置计算的,下面是根据位置绘图的部分*/
//清屏
cleardevice();
//画按钮状态示意图
for (int i = 0; i < 26; i++) {
int x = 100 + i % 10 * 40;
int y = 100 + i / 10 * 40;
//按下时画色块
if (keyStates[i] == KEY_DOWN) {
bar(x, y, x + 38, y + 38);
}
//对应位置写字母
xyprintf(x + 10, y + 10, "%c", 'A' + i);
}
}
return 0;
}
上面检测单个按键消息的判断只能单独判断一个键盘消息,但是因为实际上按住不放的时候会发出多个按下的消息。
将上面检测是否是按键按下消息的代码(即 kMsg.msg == key_msg_down ),用于人物移动判断的话,长按按键,人物会突然移动很多,就像上面按键消息计数程序一样,长按按键时,计数会快速增长。
下面是按下松开时键盘发出的 keymsg 消息:
1的时候长按,6的时候松开, 9的时候再长按,15的时候再松开
消息类型 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
up | ∙ \ \bullet ∙ | ∙ \ \bullet ∙ | ||||||||||||||
down | ∙ \ \bullet ∙ | ∙ \ \bullet ∙ | ∙ \ \bullet ∙ | ∙ \ \bullet ∙ | ∙ \ \bullet ∙ | ∙ \ \bullet ∙ | ∙ \ \bullet ∙ | ∙ \ \bullet ∙ | ∙ \ \bullet ∙ |
所以我们可以看出,按键长按时, 过一小段时间后,会快速发出多条键盘按键按下 (down) 的消息,如果你用 kMsg.msg == key_msg_down 判断的话,长按一次按键,会检测到多次按键按下的消息。但检测到按键 松开(up) 的消息时,那就是松开的时刻,因为松开时只会发送一条消息。
所以将上面检测是否是按键按下消息的代码(即 kMsg.msg == key_msg_down ),用于人物移动判断的话,长按按键,人物会突然移动很多
。
如果我们只需要按键按下一次人物移动一格,长按也不会继续移动时,可以只检测按键按下和松开的时刻。记录下按键的状态,这样按键按下和松开时状态会变化, 只对状态变化的时刻做出反应 。这样的话,长按时,即使检测到按键按下的消息,但状态没有发生变化,因此不是按键刚按下的时候。
if (lastKeyState == KEY_UP && curKeyState == KEY_DOWN)
if (lastKeyState == KEY_DOWN && curKeyState == KEY_UP)
#include
//按键状态
enum KeyState
{
KEY_UP,
KEY_DOWN
};
int main()
{
initgraph(640, 480, 0);
setcaption("按键按下松开计数");
setbkcolor(WHITE);
setcolor(BLACK);
//按键按下松开计数
int keyUpCount = 0, keyDownCount = 0;
//记录按键上一次的状态和现在的状态
int lastKeyState = KEY_UP;
int curKeyState = KEY_UP;
for (; is_run(); delay_fps(60)) {
key_msg kMsg = { 0 };
//这个不能省
lastKeyState = curKeyState;
while (kbmsg())
{
kMsg = getkey();
if (kMsg.msg == key_msg_up)
curKeyState = KEY_UP;
else if (kMsg.msg == key_msg_down)
curKeyState = KEY_DOWN;
}
//按键按下
if (lastKeyState == KEY_UP && curKeyState == KEY_DOWN)
keyDownCount++;
else if (lastKeyState == KEY_DOWN && curKeyState == KEY_UP)
keyUpCount++;
cleardevice();
xyprintf(200, 200, "当前按键状态:%s", (curKeyState == KEY_UP) ? "松开" : "按下");
xyprintf(200, 300, "down 计数 = %d", keyDownCount);
xyprintf(200, 330, "up 计数 = %d", keyUpCount);
}
return 0;
}
说明一下,这代码不适用于多按键同时按下的情况。多个按键同时按下并且长按时,如果有一个按键松开,状态显示会突然变成松开,然后又变回按下。
如果需要处理多键按下的情况,可以为每个按键分别定义两个状态变量,
只要消息处理时,根据 keyMsg.key 判断出是哪个按键,就设置对应按键的状态就行
如果需要判断的按键只有几个,比如,判断常用的A, W, D, S四个方向键,则可以按照如下来写(非完整程序
)
#define KEY_NUM 4
//这里设置好各个键的状态变量在数组中的下标
const int KEY_A_INDEX = 0, KEY_W_INDEX = 1, KEY_D_INDEX = 2, KEY_S_INDEX = 3;
//按键按下计数
int keyDownCount[KEY_NUM] = { 0 };
//记录按键上一次的状态和现在的状态
int lastKeyState[KEY_NUM] = { KEY_UP };
int curKeyState[KEY_NUM] = { KEY_UP };
for (; is_run(); delay_fps(60)) {
key_msg kMsg = { 0 };
for (int i = 0; i < KEY_NUM; i++) {
lastKeyState[i] = curKeyState[i];
}
while (kbmsg())
{
kMsg = getkey();
int key = -1;
//根据kMsg.key 判断哪个键按下
switch (kMsg.key) {
case 'A': key = KEY_A_INDEX; break;
case 'W': key = KEY_W_INDEX; break;
case 'D': key = KEY_D_INDEX; break;
case 'S': key = KEY_S_INDEX; break;
}
//如果是四个键其中之一,那么key就会不是-1
if (key >= 0) {
if (kMsg.msg == key_msg_up)
curKeyState[key] = KEY_UP;
else if (kMsg.msg == key_msg_down)
curKeyState[key] = KEY_DOWN;
//不需控制帧率的按键处理等可以放在这里
keyDownCount[key]++;
}
}
//这里通过状态来判断按下情况, 这里只检测A键
if (lastKeyState[KEY_A_INDEX] == KEY_UP && curKeyState[KEY_A_INDEX] == KEY_DOWN) {
这里作A键按下时的绘图处理等
}
else if (lastKeyState[KEY_A_INDEX] == KEY_DOWN && curKeyState[KEY_A_INDEX] == KEY_UP) {
这里作A键松开时的绘图处理等
}
//如果想遍历的话
for (int i = 0; i < KEY_NUM; i++) {
//如果按下
if (lastKeyState[i] == KEY_UP && curKeyState[i] == KEY_DOWN) {
}
}
因为键码只用一个字节就可以表示,即小于256,所以可以创建大小为256的状态变量数组。由键码直接对应下标。
对于 lastKeyState == curKeyState, 由于涉及过多的按键,状态变量的改变只会在某个按键按下或松开时,所以可以用一个数组,保存本次循环中有哪些键发出消息(即保存键码),只对这些按键赋值即可,这样可以减少一些操作。 数组大小可以设置为256(虽然不可能有那么多按键在一帧里同时按下)
。当然,也可以直接将256个状态变量遍历赋值,会多一些操作量,不过应该也没啥问题。
因为按键长按时会发出很多消息,所以不应该直接将本次的按键保存,而应该遍历之前保存的,看有没有这个按键的记录,没有就保存,按下按键的数目 + 1, 有的话就不用记录了在一帧里,同时按下的按键数也就几个,所以这个操作也不是很多。
记得在帧循环开始将按下按键数置为 0
#define KEY_NUM 256
//记录按键上一次的状态和现在的状态
int lastKeyState[KEY_NUM] = { KEY_UP };
int curKeyState[KEY_NUM] = { KEY_UP };
//记录按下的按键
int pressKeyStack[256];
int pressKeyCount = 0;
for (; is_run(); delay_fps(60)) {
key_msg kMsg = { 0 };
//上次状态转换
for (int i = 0; i < pressKeyCount; i++) {
lastKeyState[pressKeyStack[i]] = curKeyState[pressKeyStack[i]];
}
pressKeyCount = 0;
while (kbmsg())
{
kMsg = getkey();
if (kMsg.msg == key_msg_up)
curKeyState[kMsg.key] = KEY_UP;
else if (kMsg.msg == key_msg_down)
curKeyState[kMsg.key] = KEY_DOWN;
//下面是对按下的按键进行记录
//对按下的按键进行遍历,看有没有记录
int i = 0;
for (i = 0; i < pressKeyCount; i++) {
if (pressKeyStack[i] == kMsg.key)
break;
}
//i == pressKeyCount 说明没有记录
if (i == pressKeyCount)
pressKeyStack[pressKeyCount++] = kMsg.key;
}
//这里通过状态来判断按下情况, 判断A键
if (lastKeyState['A'] == KEY_UP && curKeyState['A'] == KEY_DOWN) {
}
else if (lastKeyState['A'] == KEY_DOWN && curKeyState['A'] == KEY_UP) {
}
//判断空格键
if (lastKeyState[key_space] == KEY_UP && curKeyState[key_space] == KEY_DOWN) {
}
else if (lastKeyState[key_space] == KEY_DOWN && curKeyState[key_space] == KEY_UP) {
}
}
可以看到上面 keyMsg 是定义在帧循环里面的,这样每次循环,都会初始化一次。如果你定义在外面,在里面进行鼠标处理之前也要进行一次初始化,除非你想要处理上一次遗留下来的消息。因为如果当前没有键盘消息,keyMsg就不会赋值,如果没有初始化,那就是上一次检测留下来的值,这样如果直接对keyMsg进行检测的话就会造成误判。
几种初始化方式
for ( ; is_run(); delay_fps()) {
key_msg keyMsg = {0};
}
key_msg keyMsg = {0};
for ( ; is_run(); delay_fps()) {
keyMsg = key_msg(0);
}
下面是不进行初始化的示例,由于是直接对 keyMsg 进行检测,不初始化会造成误判。
#include
int main()
{
initgraph(640, 480, 0);
setbkcolor(WHITE);
setcolor(BLACK);
//按键按下松开计数
int keyCount = 0;
xyprintf(200, 200, "按键下计数:%d", keyCount);
key_msg keyMsg = { 0 };
for (; is_run(); delay_fps(60)) {
while (kbmsg()) {
keyMsg = getkey();
}
if (keyMsg.msg == key_msg_down) {
keyCount++;
xyprintf(200, 200, "按键下计数:%d", keyCount);
}
}
return 0;
}
下面是进行初始化后的,比较一下
#include
int main()
{
initgraph(640, 480, 0);
setbkcolor(WHITE);
setcolor(BLACK);
//按键按下松开计数
int keyCount = 0;
xyprintf(200, 200, "按键下计数:%d", keyCount);
key_msg keyMsg = { 0 };
for (; is_run(); delay_fps(60)) {
keyMsg = key_msg{ 0 }; //初始化
while (kbmsg()) {
keyMsg = getkey();
}
if (keyMsg.msg == key_msg_down) {
keyCount++;
xyprintf(200, 200, "按键下计数:%d", keyCount);
}
}
return 0;
}
当你觉得缓存区中的键盘消息已经没有用时,可以将这些消息清空。这样下一次就能直接处理到最新的键盘消息了。
清空键盘消息缓存区的函数为
flushkey();
使用后将清空键盘消息缓存区,
这里用小球来表示演示
会有突然移动很多的问题
短按和长按分别试试
#include
void drawGrid();
void moveCheck(int key, int& dx, int& dy);
enum KeyState
{
KEY_UP,
KEY_DOWN
};
const int SCREEN_WIDTH = 600, SCREEN_HEIGHT = 600;
const int radius = 20;
int main()
{
const int COL = SCREEN_WIDTH / (2 * radius);
const int ROW = SCREEN_HEIGHT / (2 * radius);
initgraph(SCREEN_WIDTH, SCREEN_HEIGHT, INIT_RENDERMANUAL);
setcaption("人物格子移动");
//为了美观,使用抗锯齿
ege_enable_aa(true);
setbkcolor(EGEACOLOR(0XFF, WHITE));
setcolor(BLACK);
setfillcolor(EGEARGB(0XFF, 0XFF, 0, 0XFF));
//透明文字背景,不然带色块的
setbkmode(TRANSPARENT);
int xCircle = COL / 2, yCircle = ROW / 2;
for (; is_run(); delay_fps(60)) {
key_msg kMsg = { 0 };
while (kbmsg())
{
kMsg = getkey();
}
int dx = 0, dy = 0;
//按键按下时
if (kMsg.msg == key_msg_down) {
//位置移动监测
moveCheck(kMsg.key, dx, dy);
//计算下一个位置在哪
int xNext = xCircle + dx, yNext = yCircle + dy;
//对下一个位置做边界检测,在边界内的才移动,
if (0 <= xNext && xNext < COL
&& 0 <= yNext && yNext < ROW) {
//如果里面有围墙,那么就需要再次判断要走到的位置是否是围墙
xCircle += dx;
yCircle += dy;
}
}
/*上面是做位置计算的,下面是根据位置绘图的部分*/
//清屏
cleardevice();
//画格子
drawGrid();
//高级绘图函数 画填充椭圆,可抗锯齿,这里的四个参数是(left, top, width, height)
ege_fillellipse(xCircle * 2 * radius, yCircle * 2 * radius, 2 * radius, 2 * radius);
xyprintf(5, 5, "当前位置:(%d, %d)", xCircle, yCircle);
}
return 0;
}
void drawGrid()
{
for (int i = 0; i < SCREEN_WIDTH; i += 2 * radius) {
line(i, 0, i, SCREEN_HEIGHT);
}
for (int j = 0; j < SCREEN_HEIGHT; j += 2 * radius) {
line(0, j, SCREEN_WIDTH, j);
}
}
void moveCheck(int key, int& dx, int& dy)
{
switch (key) {
case 'A': case key_left: //左
dx = -1; dy = 0;
break;
case 'W': case key_up: //上
dx = 0; dy = -1;
break;
case 'S': case key_down: //下
dx = 0; dy = 1;
break;
case 'D': case key_right: //右
dx = 1; dy = 0;
break;
default: //其他键不移动
dx = dy = 0;
break;
}
}
按下一次只会移动一格,长按也不会多移动
#include
void drawGrid();
void moveCheck(int key, int& dx, int& dy);
enum KeyState
{
KEY_UP,
KEY_DOWN
};
const int SCREEN_WIDTH = 600, SCREEN_HEIGHT = 600;
const int radius = 20;
int main()
{
const int COL = SCREEN_WIDTH / (2 * radius);
const int ROW = SCREEN_HEIGHT / (2 * radius);
initgraph(SCREEN_WIDTH, SCREEN_HEIGHT, INIT_RENDERMANUAL);
setcaption("人物格子移动");
//为了美观,使用抗锯齿
ege_enable_aa(true);
setbkcolor(EGEACOLOR(0XFF, WHITE));
setcolor(BLACK);
setfillcolor(EGEARGB(0XFF, 0XFF, 0, 0XFF));
//透明文字背景,不然带色块的
setbkmode(TRANSPARENT);
int xCircle = COL / 2, yCircle = ROW / 2;
KeyState lastKeyState = KEY_UP;
KeyState curKeyState = KEY_UP;
for (; is_run(); delay_fps(60)) {
key_msg kMsg = { 0 };
while (kbmsg())
{
kMsg = getkey();
if (kMsg.msg == key_msg_up)
curKeyState = KEY_UP;
else if (kMsg.msg == key_msg_down)
curKeyState = KEY_DOWN;
}
int dx = 0, dy = 0;
//有按键按键按下
if (lastKeyState == KEY_UP && curKeyState == KEY_DOWN) {
//根据按键做出移动判断
moveCheck(kMsg.key, dx, dy);
//判断一下位置有没有移动,如果是其它键按下的话,位置就没有移动,就不用做位置计算了
if (dx != 0 || dy != 0) {
//计算下一个位置在哪
int xNext = xCircle + dx, yNext = yCircle + dy;
//对下一个位置做边界检测,在边界内的才移动,
if (0 <= xNext && xNext < COL
&& 0 <= yNext && yNext < ROW) {
//如果里面有围墙,那么就需要再次判断要走到的位置是否是围墙
xCircle += dx;
yCircle += dy;
}
}
}
/*上面是做位置计算的,下面是根据位置绘图的部分*/
//清屏
cleardevice();
//画格子
drawGrid();
//高级绘图函数 画填充椭圆,可抗锯齿,这里的四个参数是(left, top, width, height)
ege_fillellipse(xCircle * 2 * radius, yCircle * 2 * radius, 2 * radius, 2 * radius);
xyprintf(5, 5, "当前位置:(%d, %d)", (xCircle - radius) / (2 * radius), (yCircle - radius) / (2 * radius));
//这个不能省
lastKeyState = curKeyState;
}
return 0;
}
void drawGrid()
{
for (int i = 0; i < SCREEN_WIDTH; i += 2 * radius) {
line(i, 0, i, SCREEN_HEIGHT);
}
for (int j = 0; j < SCREEN_HEIGHT; j += 2 * radius) {
line(0, j, SCREEN_WIDTH, j);
}
}
void moveCheck(int key, int& dx, int& dy)
{
switch (key) {
case 'A': case key_left: //左
dx = -1; dy = 0;
break;
case 'W': case key_up: //上
dx = 0; dy = -1;
break;
case 'S': case key_down: //下
dx = 0; dy = 1;
break;
case 'D': case key_right: //右
dx = 1; dy = 0;
break;
default: //其他键不移动
dx = dy = 0;
break;
}
}
普通的按键移动不均匀,长按时会动一下, 隔一段时间后再连续移动,不够平滑
而格子移动一次只能移动一格,显然不符合要求。
现在这里借助按键当前状态进行按键控制的平滑移动。每一帧都检测当前哪个按键处于按下状态,位置就往哪个方向增,而不用判断是否有按键按下。因为只根据当前状态,所以一个按键只用一个状态变量就够了。
检测按键状态使用 keystate()就可以了,自己使用鼠标消息来处理状态也可以,下面两个程序都将贴出
如果是四方向移动的,只判断出一个按下的方向键就够了,不用判断其它方向。如果是八方向移动的,就把所有按下的方向键的位置增量都加起来。这样,如果左和上同时按下时,会朝左上方向移动。
如果想平滑地拐弯,减速地停止,如同具有惯性,可以设置加速度, 而不是速度dx, dy 突然变化,可以慢慢变化,如本来向左时匀速 dx = -10, dy = 0, 按下上方向键,这时候每一帧 dx的减小一点,dy增加一点, 比如变成 dx = -8, dy = -2, 而不是突然变成dx = 0, dy = -10。
使用 keystate() 检测按键状态的方式
这种方式并不需要处理按键消息
#include
//窗口大小
const int SCREEN_WIDTH = 600, SCREEN_HEIGHT = 600;
const int radius = 40; //圆半径大小
int main()
{
initgraph(SCREEN_WIDTH, SCREEN_HEIGHT, INIT_RENDERMANUAL);
setcaption("人物格子移动");
//为了美观,使用抗锯齿
ege_enable_aa(true);
setbkcolor(EGEACOLOR(0XFF, WHITE));
setcolor(BLACK);
setfillcolor(EGEARGB(0XFF, 0XFF, 0, 0XFF));
//透明文字背景,不然带色块的
setbkmode(TRANSPARENT);
//浮点型,精确位置,
float xCircle = SCREEN_WIDTH / 2, yCircle = SCREEN_HEIGHT / 2;
//0, 1, 2, 3分别对应方向 左 上 右 下
int keys[4] = {'A', 'W', 'D', 'S'};
int directKeys[4] = { key_left, key_up, key_right, key_down };
//移动速度, 浮点型能任意控制速度快慢,
float speed = 2.0f;
float xSpeeds[4] = { -speed, 0, speed, 0 };
float ySpeeds[4] = { 0,-speed, 0, speed };
for (; is_run(); delay_fps(60)) {
//根据按键按下状态对位置增量进行累加,可八方向移动
float xNext = xCircle;
float yNext = yCircle;
for (int i = 0; i < 4; i++) {
if (keystate(keys[i]) || keystate(directKeys[i])) {
xNext += xSpeeds[i];
yNext += ySpeeds[i];
}
}
//如果移动了
if (xNext != xCircle || yNext != yCircle) {
//检测是否超出边界
if (radius <= xNext && xNext <= (SCREEN_WIDTH - radius)
&& radius <= yNext && yNext <= (SCREEN_HEIGHT - radius)) {
xCircle = xNext;
yCircle = yNext;
}
}
/*上面是做位置计算的,下面是根据位置绘图的部分*/
//清屏
cleardevice();
//高级绘图函数 画填充椭圆,可抗锯齿.这里的四个参数是(left, top, width, height)
ege_fillellipse(xCircle - radius, yCircle - radius, 2 * radius, 2 * radius);
xyprintf(5, 5, "当前位置:(%f, %f)", xCircle, yCircle);
}
return 0;
}
自己处理按键状态的方式
#include
enum KeyState
{
KEY_UP,
KEY_DOWN
};
//窗口大小
const int SCREEN_WIDTH = 600, SCREEN_HEIGHT = 600;
const int radius = 40; //圆半径大小
int main()
{
initgraph(SCREEN_WIDTH, SCREEN_HEIGHT, INIT_RENDERMANUAL);
setcaption("人物格子移动");
//为了美观,使用抗锯齿
ege_enable_aa(true);
setbkcolor(EGEACOLOR(0XFF, WHITE));
setcolor(BLACK);
setfillcolor(EGEARGB(0XFF, 0XFF, 0, 0XFF));
//透明文字背景,不然带色块的
setbkmode(TRANSPARENT);
//浮点型,精确位置,
float xCircle = SCREEN_WIDTH / 2, yCircle = SCREEN_HEIGHT / 2;
//0:左, 1:上, 2:右, 3:下
KeyState keysState[4] = { KEY_UP };
//移动速度, 浮点型能任意控制速度快慢,
float speed = 2.0f;
float xSpeeds[4] = {-speed, 0, speed, 0};
float ySpeeds[4] = {0,-speed, 0, speed };
for (; is_run(); delay_fps(60)) {
key_msg kMsg = { 0 };
while (kbmsg())
{
kMsg = getkey();
//如果是方向键就改变方向键状态
int keyNum = -1;
switch (kMsg.key) {
case 'A': case key_left: //左
keyNum = 0;
break;
case 'W': case key_up: //上
keyNum = 1;
break;
case 'D': case key_right: //右
keyNum = 2;
break;
case 'S': case key_down: //下
keyNum = 3;
break;
}
//修改对应按键的状态
if (keyNum >= 0) {
if (kMsg.msg == key_msg_up)
keysState[keyNum] = KEY_UP;
else
keysState[keyNum] = KEY_DOWN;
}
}
//根据按键按下状态对位置增量进行累加,可八方向移动
float xNext = xCircle;
float yNext = yCircle;
for (int i = 0; i < 4; i++) {
if (keysState[i] == KEY_DOWN) {
xNext += xSpeeds[i];
yNext += ySpeeds[i];
}
}
//如果移动了
if (xNext != xCircle || yNext != yCircle) {
//检测是否超出边界
if (radius <= xNext && xNext <= (SCREEN_WIDTH - radius)
&& radius <= yNext && yNext <= (SCREEN_HEIGHT - radius)) {
xCircle = xNext;
yCircle = yNext;
}
}
/*上面是做位置计算的,下面是根据位置绘图的部分*/
//清屏
cleardevice();
//高级绘图函数 画填充椭圆,可抗锯齿.这里的四个参数是(left, top, width, height)
ege_fillellipse(xCircle - radius, yCircle - radius, 2 * radius, 2 * radius);
xyprintf(5, 5, "当前位置:(%f, %f)", xCircle, yCircle);
}
return 0;
}
可以先定义一个方向变量 direction,初始值为-1。在按键处理时,当检测到方向键按下时,则把它赋值为对应的值。然后再检测 direction,如果不为-1,就代表刚才有方向键按下,这时就可以根据值来做不同的变化。
int direction = -1;
//按键消息处理
while (kbmsg()) {
key_msg keyMsg = getkey();
if (keyMsg.msg == key_msg_down) {
switch(keyMsg.key) {
case 'A': case key_left: direction = 0; break;
case 'W': case key_up: direction = 1; break;
case 'D': case key_right: direction = 2; break;
case 'S': case key_down: direction = 3; break;
}
}
}
//这里根据direction的值判断,如果不是-1,说明按下了方向键
if (direction != -1) {
//移动处理
}
因为四方向就是坐标 (x, y) 的变化, 可以根据四个方向不同的变化定义变化量dx, dy 数组。
下面下标 0~3 分别对应左, 上,右, 下方向的坐标偏移
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, -1, 0, 1};
这样,得到方向direction 后,则可以做移动
x += dx[direction];
y += dy[direction];
下面是按键控制四方向格子移动的实例(按下按键)
#include
#define NUM_GRID 10
#define GRID_WIDTH 50
const int SCR_WIDTH = GRID_WIDTH * NUM_GRID;
const int SCR_HEIGHT = SCR_WIDTH;
int main()
{
initgraph(SCR_WIDTH, SCR_HEIGHT, 0);
setbkcolor(WHITE);
setcolor(BLACK);
setfillcolor(EGERGB(0XFF, 0X80, 0XFF));
setfont(14, 0, "楷体");
setbkmode(TRANSPARENT);
//当前格子位置
int xGrid = NUM_GRID / 2, yGrid = xGrid;
//计算绘制位置,绘制
int x = xGrid * GRID_WIDTH, y = yGrid * GRID_WIDTH;
bar(x, y, x + GRID_WIDTH, y + GRID_WIDTH);
xyprintf(x, y, "(%d, %d)", xGrid, yGrid);
//方向坐标偏移量,0~3分别对应左上右下
int dx[4] = { -1, 0, 1, 0 };
int dy[4] = { 0, -1, 0, 1 };
for (; is_run(); delay_fps(60)) {
int direction = -1;
//按键消息处理
while (kbmsg()) {
key_msg keyMsg = getkey();
if (keyMsg.msg == key_msg_down) {
switch (keyMsg.key) {
case 'A': case key_left: direction = 0; break;
case 'W': case key_up: direction = 1; break;
case 'D': case key_right: direction = 2; break;
case 'S': case key_down: direction = 3; break;
}
}
}
if (direction != -1) {
//可以计算出下一个位置坐标
int xGridNext = xGrid + dx[direction], yGridNext = yGrid + dy[direction];
//判断移动是否会出边界,没出边界则移动
if (0 <= xGridNext && xGridNext < NUM_GRID && 0 <= yGridNext && yGridNext < NUM_GRID) {
cleardevice();
//移动
xGrid = xGridNext;
yGrid = yGridNext;
//计算绘制位置,绘制
x = xGrid * GRID_WIDTH, y = yGrid * GRID_WIDTH;
bar(x, y, x + GRID_WIDTH, y + GRID_WIDTH);
xyprintf(x, y, "(%d, %d)", xGrid, yGrid);
}
}
}
closegraph();
return 0;
}