Krissi

半亩方塘一鉴开,天光云影共徘徊。

正确处理鼠标和按键消息(解决反应迟钝或消息丢失的问题) 铜牌收录

概述

本文讲解怎样处理鼠标消息。编程思路有很多种,本文只是提供一种思路,仅供参考。

使用 getmessage 获取消息比较简单,可以参考范例代码:https://docs.easyx.cn/mouse-operations

但是 getmessage 是阻塞函数,如果当前没有消息,就会一直等待下去,直到有消息产生。因此,getmessage 不太适合游戏处理。游戏里面获取消息比较常用 peekmessage 函数,该函数会立即返回,不论是否有消息。

常见的游戏主循环结构

通常写小游戏时,很多人会有一个主循环,类似这样:

while(true)
{
	获取用户控制();

	进行游戏运算();

	绘制游戏内容();

	Sleep(xx);
}

鼠标和键盘消息的获取

以鼠标操作为例,当添加鼠标操作时,有人会这样写(错误代码):

// 定义变量,保存鼠标消息
ExMessage msg;

// 游戏的主循环
while(true)
{
	if (peekmessage(&msg, EX_MOUSE))	// 如果获取到了消息就执行
	{
		switch(msg.message)				// 根据不同的鼠标消息,执行不同的代码
		{
			case xxxx: 进行游戏运算1(); break;
			case xxxx: 进行游戏运算2(); break;
		}
	}

	绘制游戏内容();

	Sleep(xx);							// 延时,降低 CPU 占用率
}

这个代码的问题是,由于 Sleep 的存在,导致鼠标消息的产生速度,超过游戏处理的速度。比如有 Sleep(20),那么主循环每秒钟最多循环 50 次,也就最多处理 50 次鼠标消息。但是鼠标移动时也会产生一系列的鼠标消息,这个速度远远超过每秒钟 50 次,这就导致鼠标消息的缓冲区溢出,会导致每次处理得鼠标消息都是旧的,并且无法接收新的鼠标消息。

于是,有人在 Sleep(xx) 之前,增加了函数 flushmessage(),将多余的鼠标消息清空。很明显的,这样会造成另一个问题:鼠标消息丢失。产生的效果就是操作不灵敏,一些点击操作无效。因为鼠标移动的消息数量占大多数,点击操作占少数,清空的鼠标消息很可能包括点击操作,所以点击操作最受影响。

作为一种解决方案,可以考虑这么做:

// 定义变量,保存鼠标消息
ExMessage msg;

// 游戏的主循环
while(true)
{
	while (peekmessage(&msg, EX_MOUSE))	// 如果获取到了消息就执行
	{
		switch(msg.message)				// 根据不同的鼠标消息,执行不同的代码
		{
			case xxxx: 进行游戏运算1(); break;
			case xxxx: 进行游戏运算2(); break;
		}
	}

	绘制游戏内容();

	Sleep(xx);							// 延时,降低 CPU 占用率
}

这样就可以确保每次循环都能处理完全部的鼠标消息,并且不会造成鼠标消息的丢失。在范例程序里面,很多网友的作品都有鼠标操作,同样可以做一个参考。

flushmessage 的使用场景

既然如此,那么关于 flushmessage 函数,在什么时候使用呢?

例如游戏“连连看”,这样一个功能:

准备游戏 3、2、1 倒计时();
执行游戏();

问题是:在倒计时的时候,用户有可能会狂点鼠标。那么这些鼠标操作,是会“记忆”的,等到“执行游戏”的时候,获取到的鼠标消息,都是倒计时期间的,因此会产生错误的操作。可以这么解决:

准备游戏 3、2、1 倒计时();
flushmessage();
执行游戏();

这样,就可以确保在“执行游戏()”的时候,不会受之前的鼠标操作所影响。

评论 (10) -

  • 那while的话是不是只能等鼠标不操作的话才能sleep一下呀
  • 不过如果用鼠标控制的游戏中光标之类的移动,这样还能行吗
  • 这个在绘制的东西比较多的时候深有体会。另外,判断鼠标点击按钮的时候,虽然很多人会想到写一个函数去判断鼠标点击时是否在按钮范围内,但当一个界面按钮比较多的时候,很多人就直接for循环判断处于哪个按钮了,这样会导致反应迟缓,如果按钮是有规律排序的话建议直接根据鼠标坐标加减乘除直接确定处于哪个按钮范围内。
  • 也就是说用if只能处理缓存区的一个鼠标信息,换成while就可以每次处理缓冲区里的所有鼠标信息吧
  • 请问添加鼠标操作的错误代码与解决方案的不同点在哪呢……感觉没有改动呀(捂脸

添加评论