How to handle mouse input events (from the OS)

Started by
8 comments, last by Polarist 11 years, 2 months ago

So it's been a while since the last time I created an abstraction for the mouse, so I have a few questions about good practices to mouse handling.

The way it used to be done "back in the day" was to, each iteration of the game loop, do something like so:

1. Calculate the mouse position delta by tracking its distance from the center of the window (e.g. mouse.position-window.center).

2. Force the mouse.position back to window.center.

I'm not sure all the reasons why it was handled this way, but this solution at least allows both the ability to move the mouse in any direction indefinitely and it nearly eliminates the possibility of the mouse moving outside of the window. One negative side effect of this approach is that anytime you switch from first-person-mode to a menu requiring absolute mouse position, the cursor starts up at in the center position (0.5, 0.5).

But in modern games, it appears that the mouse's input is handled differently, as if it's "locked in place" when needing relative position (first-person mode) and preserves its position when switching back to "absolute position" (menu mode). (For instance, in WoW, you can hold down the right mouse button to rotate the camera, but once you let go, the mouse cursor continues from where it was before you had clicked.)

So the first question is, what's the best way to implement this behavior? Is this "faked" by saving the position and resetting to that point, or is it locked in place using an OS call? If the latter, what are the relevant OS calls (for win32, x11, and/or os x)?

Or maybe, is input handling preferably handled a different way altogether?

Another question is, how do you lock a mouse within the bounds of a window? I know you can at least force the position of the mouse cursor, but is there an OS call that achieves this effect (again, for win32, x11, and/or os x)

Thanks for any guidance!

Advertisement
Found the Win32 solution for: Another question is, how do you lock a mouse within the bounds of a window?
// If window is active, ensure mouse stays within bounds of the game
if(g_hWnd == GetForegroundWindow()){
	RECT clientRect;
	// Get client rect in "window space"
	GetClientRect( g_hWnd, &clientRect );
	// Convert client rect to "screen space"
	POINT pt = { clientRect.left, clientRect.top };
	POINT pt2 = { clientRect.right, clientRect.bottom };
	ClientToScreen(g_hWnd, &pt);
	ClientToScreen(g_hWnd, &pt2);
	SetRect(&clientRect, pt.x, pt.y, pt2.x, pt2.y);
	// Lock cursor (mouse) to screen-space client rect
	ClipCursor( &clientRect );
}
Though this solution causes the WM_MOUSEMOVE events to stop firing if you are locked to the side. Perhaps instead of messages, I need to use RawInput to track movement?

There is a difference between the OS cursor and the game's cursor, at least in some games. The OS cursor is fast and responsive usually even when the game's frame rate drops to, say, 5fps. Some games just use the OS cursor but set a custom cursor (.cur file) image to make it look distinct. Other games completely hide the OS cursor and show a game cursor which is just a textured screen-aligned quad. But eventually the logic is similar in regards to the functionality you are after, so I'll assume you mean the OS cursor.

Don't use raw input. From your description, basically you have two modes of operation: camera navigation, and pointing. In camera navigation mode, we only care about dx and dy, the amount by which the user has moved the mouse. In pointing mode, we only care about the cursor's coordinates. So the logic becomes like this:


POINT g_ptPointerPos; // The pointer's position, used in pointing mode.
void SetMousingMode( MOUSING_MODE mode )
{
	m_Mode = mode;
	if( mode == MODE_NAV )
	{
		ShowCursor( FALSE ); // Actually, you may need to say while( ShowCursor(FALSE)>0 ); - see ShowCursor() API docs.
		SetCursorPos( ptWindowCenter );
	}
	else if( mode == MODE_POINT )
	{
		ShowCursor( TRUE );
		SetCursorPos( g_ptPointerPos );
	}
	else if( mode == MODE_INACTIVE )
	{
		// This is used when the game's window is deactivated.
		// It's important not to interfere with the mouse when the window
		// is not active, otherwise you'll piss off your users.
		ShowCursor( TRUE );
	}
}

void OnMouseMove( POINT cursorPos )
{
	if( m_Mode == MODE_NAV )
	{
		INT dx = cursorPos.x - ptWindowCenter.x;
		INT dy = cursorPos.y - ptWindowCenter.y;
		DoNavigation( dx, dy );
		SetCursorPos( ptWindowCenter );
	}
	else if( m_Mode == MODE_POINT )
	{
		g_ptPointerPos = cursorPos;
		DoPointing( cursorPos );
	}
	else if( m_Mode == MODE_INACTIVE )
	{
		// nothing to do here.
	}
}

You need to clip the cursor to the bounds of the window only in pointing mode. I hope this helps.

Hey, thanks for the response.

Don't use raw input.

What I really want to understand is "why?" It'd be great if you could shed some light on the reasoning behind that statement.

I actually had a similar system to your code prior to asking this question. And at this point I have the Raw Input solution, too. But I'm still deciding between the two.

I was actually leaning towards the Raw Input solution as it seemed more robust, but I'm seeing the "center" implementation more frequently in tutorials and such. After some Googling, I know at least the Source (TF2, HL2, etc) engine exposes Raw Input, but OIS appears to use the centering solution that you've suggested. Doom3 appears to use DirectInput, which is a (deprecated) wrapper around Raw Input. I haven't checked any other implementations, though.

Also: And I haven't tested this, but from what I hear, Raw Input allows you to make use of higher sensitivity in "gaming" mice.

Well, the reason I suggested to avoid raw input was actually just simplicity - processing raw input messages is more complicated than the standard mouse messages. And yes, it seems you can't reap the full benefits of having a high-definition mouse using standard messages (link). A short excerpt from the linked article:

The disadvantage is that WM_INPUT has no ballistics applied to its data, so if you want to drive a cursor with this data, extra effort will be required to make the cursor behave like it does in Windows.

Nevertheless, there seems to be a few players who really would appreciate full support for their (expensive?) high-definition mice. There could be other advantages/disadvantages to either method, but I would suggest you go with the simplest thing that works now and then get back to this issue after all higher-priority features have been implemented in your game/engine. I'm also interested in hearing what other people are doing with their games/engines as well.

Thanks again for the response.

I would suggest you go with the simplest thing that works now

I already have both implemented and working for Win32, at this point. I was mostly curious as to the pros and cons between them. Switching between them is a matter of flipping a boolean.

the reason I suggested to avoid raw input was actually just simplicity

That's the thing, WM_MOUSEMOVE is not any simpler than WM_INPUT (Raw Input). The difference is that WM_MOUSEMOVE draws its mouse information from the OS representation of the mouse cursor, while WM_INPUT draws information from the mouse device itself. At least for my project, either one would simply send a MouseChangedEvent to my engine's event handler. Neither one is harder, they are just different.

Anyway, I figured out a clean solution for everything I originally wanted (at least in Win32), and that is a hybrid solution between the two. The cursor operates in two separate states:

1. First-person mode: Locks the mouse position in place (NOT in the window's center), and uses Raw Input to track position deltas. This disables mouse acceleration, which is generally considered desirable for gaming. It's not a priority now, but later on, I might add a setting to enable mouse acceleration in this mode. (Not sure why anyone would want that, but it seems to be a standard feature.)
2. Menu mode: Uses plain windows mouse events to preserve the users mouse settings from the OS.

This allows my engine to support more complex control schemes such as the ones you'd see in World of Warcraft or Starcraft II.

If anyone is interested in knowing more, I'd be happy to do a deeper write-up.

I found this post helpful:

Designing a Robust Input Handling System for Games

EDIT: Sorry. Misread the topic. Thought it was about generic input handling, not mouse-only handling.

I found this post helpful:
Designing a Robust Input Handling System for Games

Thanks for the link! Even if it wasn't exactly what I asked, it was a good read and pretty helpful.

My system is quite similar to the one described, but it's nice to hear what works well for people after the fact. I didn't however do the XML configuration file approach, but that's a good idea for user-friendly configuration. I might have to come back and do that.

I have an interest in this topic too. I only ever implemented the stick-cursor-in-center-of-screen solution, since I used a third-party library to handle all window-related events.

This system seems to work fine for the most part, and I pass along x,y,dx,dy to all my mouse handling functions without problem.

There's two concerns with this system that I have:

1) FPS drops - a large fps drop means the game loop lags, means it won't center the mouse on screen, giving it the ability to exit the window. Even worse if a click is done while outside the window. Locking the mouse to the window seems like a good solution (as posted by Polarist for windows). Although the question I have here is, what happens if your program crashes? Is the mouse cursor automatically unlocked from the area on screen?

Edit: Having a look at the MS site, there's an example of how to do locked cursor:


RECT rcClip;           // new area for ClipCursor
RECT rcOldClip;        // previous area for ClipCursor

// Record the area in which the cursor can move. 

GetClipCursor(&rcOldClip); 

// Get the dimensions of the application's window. 

GetWindowRect(hwnd, &rcClip); 

// Confine the cursor to the application's window. 

ClipCursor(&rcClip); 

   // 
   // Process input from the confined cursor. 
   // 

// Restore the cursor to its previous area. 

ClipCursor(&rcOldClip);  

It seems like you need to manually release the cursor area. I'm still not sure what happens if a crash occurs while processing the input from the cursor. I also wonder if this is at all compatible with using a third party library - i.e. if you can 'process' the input from the confined cursor using a library that receives cursor events.

2) In rpg style games you control the camera rotation by holding and dragging the right mouse button. Supposedly, this would put the mouse in nav-mode, centering it on screen. But that is an issue if you have any other action that uses the mouse position for anything else - since now all of a sudden the mouse gets teleported to the center of the screen. An example of this is ground-targeting in rpgs, attempted while rotating the camera. I guess one solution is to never use raw mouse coords, but to always obtain them from whatever mouse managing class you have in the game, which can return the 'false' mouse position as though its being held in place where the right click occurred.

Given these problems though, I'm also interested - is there a hardware way to lock the mouse cursor in place, while still receiving some sort of dx/dy events?

And as Polarist asked - why would you not use raw input? Is there no advantage to using it? Or is it simply because it's more complex to code?

@Milcho The solution you posted locks the cursor to the window, while the solution I posted above locks the cursor to the "client area." The difference is that the window allows you to move your cursor to the titlebar and sides of the window, which means that players will be able to drag the mouse "out of the game" and interact with the window elements (such as dragging the window or resizing the window), which in my implementation would be unwieldy.

When the game crashes, I read somewhere that older versions of windows (perhaps pre-Vista) would indeed keep the mouse locked to the clip rect. The suggested alternative was to simulate the ClipCursor behavior with manual SetCursor calls. I haven't tested any of this personally though.

Given these problems though, I'm also interested - is there a hardware way to lock the mouse cursor in place, while still receiving some sort of dx/dy events?
And as Polarist asked - why would you not use raw input? Is there no advantage to using it? Or is it simply because it's more complex to code?

Raw input (WM_INPUT) allows you to separate the input from the mouse from the pointer representation in the OS (WM_MOUSEMOVE). Thus you can do things like locking the mouse cursor in place (e.g. ClipCursor on a 1x1 Rect) while still receiving move events. I found that using raw input was no more complex than windows messages, in both cases, you simply get passed a dx and dy. The only logically different piece was checking for "absolute position" devices such as touchpads, and even there you simply need a subtraction before you have a dx and dy.

So while complexity is substantially the same, it is important to note the differences in the data. WM_MOUSEMOVE will be affected by mouse settings in the OS. If the user changes his mouse acceleration or mouse sensitivity, the dx and dy will be scaled accordingly. Raw Input won't reflect those changes.

So to answer your question, there are advantages to it and it's not substantially harder to code.

I settled for a hybrid solution to support the RPG controls you described, using WM_MOUSEMOVE when the pointer is visible and using WM_INPUT when needing to rotate the camera. This appears to be the most robust solution. (Unless there are alternatives I'm not aware of.)

This topic is closed to new replies.

Advertisement