“鼠标拖动游戏窗口,消息循环会卡住,导致游戏不能更新不能渲染”,解决办法在这里

开发端游的同学都知道,鼠标按在窗口标题栏上进行拖动,游戏就会卡住不能更新不能渲染。

还有另外一种情况也会导致游戏卡住,就是弹出窗口的系统菜单时,例如下图:



“弹出系统菜单导致游戏卡住”,这种情况比较容易解决,我先讨论这种情况。

有两种操作会弹出系统菜单:

1,鼠标左击标题栏左侧的图标

2,鼠标右击标题栏

当用户发生上面两种操作时,我们拦截掉Windows消息,不要通知给默认的消息处理函数即可。

LRESULT CALLBACK WindowMsgProcess(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
	case WM_SYSCOMMAND:
		{
			switch (wParam & 0xfff0)
			{
			case SC_KEYMENU:
			case SC_MOUSEMENU:
				//拦截第一种操作,不要通知给默认的消息处理函数
				return 0;
			}
		}
		break;
	case WM_NCRBUTTONDOWN:
		{
			//拦截第二种操作,不要通知给默认的消息处理函数
			return 0;
		}
		break;
	}
	//执行默认的消息处理函数
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

接下来讨论“鼠标拖动标题栏导致游戏卡住”。

当鼠标在标题栏上按住不放时,系统会发送 WM_SYSCOMMAND 消息,携带的参数为 SC_MOVE ,当DefWindowProc收到 SC_MOVE 后,会发送 WM_ENTERSIZEMOVE ,这个时候整个消息循环就会卡止,直到DefWindowProc处理完成返回。

解决办法也是拦截相应的Windows消息,不要通知给默认的消息处理函数。仅仅拦截消息还不够,用户的目的是移动窗口,我们要写代码跟随鼠标的移动来移动窗口;否则的话,用户无论怎样拖动标题栏,窗口都一动不动。

定义下面几个全局变量,用于我们的“鼠标拖动窗口标题栏,不要阻塞消息循环”逻辑。

一旦 g_bNcLButtonDown 的值为true,表示要跟随鼠标的移动来移动窗口。

bool g_bNcLButtonDown = false;
POINT g_kLastMousePos;
POINT g_kCurMousePos;
RECT g_kCurWindowRect;

响应Windows消息,来更改 g_bNcLButtonDown 的值

LRESULT CALLBACK WindowMsgProcess(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
	case WM_SYSCOMMAND:
		{
			switch (wParam & 0xfff0)
			{
			case SC_MOVE:
				{
					if (g_bNcLButtonDown == false)
					{
						//鼠标开始拖动标题栏,我们的逻辑开始执行
						g_bNcLButtonDown = true;
						::GetCursorPos(&g_kLastMousePos);
						::GetWindowRect(hWnd, &g_kCurWindowRect);
					}
					//拦截,不要通知给默认的消息处理函数
					return 0;
				}
				break;
			}
		}
		break;
	case WM_NCLBUTTONUP:
		{
			//在窗口的非客户区(主要是标题栏),鼠标抬起了,表示不再拖动窗口,我们的逻辑停止执行
			//不要拦截,交给DefWindowProc去处理。
			g_bNcLButtonDown = false;
		} break;
	}
	//执行默认的消息处理函数
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

在游戏循环里面,做“跟随鼠标的移动来移动窗口”的逻辑

	//“鼠标拖动游戏窗口,不要卡住游戏循环”逻辑。
	if (g_bNcLButtonDown_NoBlockLoop)
	{
		//有时候客户端会卡顿,一旦不能收到窗口消息,g_bNcLButtonDown_NoBlockLoop可能会一直为true,导致bug。
		//解决办法是,这里总是判断鼠标左键是否按下。从系统层面获取鼠标左键是否按下。
		if (GetAsyncKeyState(VK_LBUTTON) & 0x8000)
		{
			::GetCursorPos(&g_kCurMousePos_NoBlockLoop);
			const int nDeltaX = g_kCurMousePos_NoBlockLoop.x - g_kLastMousePos_NoBlockLoop.x;
			const int nDeltaY = g_kCurMousePos_NoBlockLoop.y - g_kLastMousePos_NoBlockLoop.y;
			if (nDeltaX < -1 || nDeltaX > 1 || nDeltaY < -1 || nDeltaY > 1)
			{
				g_kCurWindowRect_NoBlockLoop.left += nDeltaX;
				g_kCurWindowRect_NoBlockLoop.top += nDeltaY;
				g_kLastMousePos_NoBlockLoop = g_kCurMousePos_NoBlockLoop;
				//执行移动操作的时候不要更改窗口size。
				::SetWindowPos(g_hWnd_NoBlockLoop, HWND_NOTOPMOST, g_kCurWindowRect_NoBlockLoop.left, g_kCurWindowRect_NoBlockLoop.top, g_kCurWindowRect_NoBlockLoop.right, g_kCurWindowRect_NoBlockLoop.bottom, SWP_NOSIZE);
			}
		}
		else
		{
			g_bNcLButtonDown_NoBlockLoop = false;
		}
	}


至此,大功告成。

-----------------------------------------------------------------------------

回复留言

回复supercolin:我最近在做“渲染模块和更新模块分拆成两个线程”的工作,感悟到你的说法不对。鼠标拖动游戏窗口,消息循环就会卡住,虽然渲染线程不会卡住,但是渲染数据都是旧的,渲染出来的画面都是过时的画面;当鼠标不再拖动,更新模块会执行一个时间跨度比较大的更新,渲染出来的画面会“瞬移”,“抖动”一下。





评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值