Tracking your mouse in games and editors

It is quite common to hide your mouse while the game is on the game window. Another common technique is restricting the mouse movement somehow inside the game window. Both of those techniques help with getting rid of the cursor while the player is playing the game

Where is my mouse?

It must be one of the most common issues in games/emulators/whatnot. The player needs to minimize the window to take care of some other task on her computer but the mouse remains hidden and/or trapped within an invisible rectangle, preventing the user to complete her action or even see what she is doing. This is a very frustrating problem and surprisingly, a lot of games get this wrong.

Before I saw this problem in code, I assumed that this issue is taken care of automagically, however this is not the case. Let's consider the ShowCursor windows API function for a moment: Internally, the system is a reference counter: Each call of the function with a True argument will increase the counter and each call with a False argument will decrement the counter. This points to a certain direction in how to design our code:

The cursor visibility is actually a resource and we can acquire and release it using RAII techniques.

Let's consider the following scenario:

An RPG game comes with some option panels that can be spawned and closed independently - imagine something like the old Morrowind UI. As soon as we enter the game, we call ShowCursor(false); and our mouse is hidden. When any of the option panels is opened, we acquire the mouse visibility resource on the constructor of the panel, calling ShowCursor(true); and release it on destruction of the panel by calling ShowCursor(false). This technique will keep the reference counter of the mouse visibility system positive and the user will keep seeing a mouse cursor as long as at least one panel is visible.

I want my mouse and I want it now

RAII and reference counting solve this simple visibility issue, but what happens if the user wants to show or hide the mouse immediately, regardless of the state of the reference counter?

Consider the following examples:
  •  The mouse cursor is visible, and the user starts interacting with a certain widget. For better feeedback, we want to hide the cursor while we interact with this type of widget.
  • User tabs out of our game. We have to show the mouse regardless of the state of the reference variable.
In these kinds of situations, reference counting does not help. What is worse, is that ShowCursor, internally always handles the boolean we pass as a hint for reference counting. We need to override this internal counting and force a state for the cursor. How do we do that without messing with the state of the cursor?

Life is all about priorities

In the end, we need to evaluate a set of rules. One visibility rule is "show cursor if my reference counter is greater than zero". Another visibility rule is "always hide the cursor while I am interacting with this widget" and yet another rule is "Always, always show the cursor when our window goes out of focus". These rules may actually try to enforce a different state for the cursor at the same time and this is when you must use a set priorities for each rule. Usually, the window focus rule has the greatest priority of all. As soon as we go out of focus, the mouse must be shown. Other priorities should be decided by the use cases in the game.

To avoid the issue of ShowCursor treating our boolean as a hint and tweaking its own reference counter, we will only call it if our set of rules is evaluated to a value different than the last one.

Essentially, all this can be wrapped in a nice CursorVisibility class. We store reference counting as well as any flags containing the state of the rules in this class. Every time a rule changes or reference value is increased ir decreased, we need to evaluate the set of rules. This looks like that in the code (note, all class member variables are prefixed with m_):
1:  void CursorVisibility::evaluateVisibility()  
2:  {
3:    bool result = !m_bisWindowFocused || (m_referenceCounter > 0 && !m_binteractWidget);
4:    if (result != m_lastResult)  
5:    {  
6:      ShowCursor(result);  
7:      m_lastResult = result;  
8:    }  
9:  }  
10:  void CursorVisibility::setRuleVariable(bool flag)  
11:  {  
12:    // set rule variable (in this example window focus) and evaluate visibility  
13:    m_bisWindowFocused = flag;  
14:    evaluateVisibility();  
15:  }  

Of course mouse restriction can be handled with the same design. Remember that focusing in and out of your game window should evaluate to unrestricted and visible mouse in your evaluation function! The window focus in and focus out events can act as triggers for setting the rules in a RAII way.

Conclusion

I hope this write-up clears some things up and helps game and application developers deal with similar issues. So, stop having those horrible issues in your application (I'm looking at you PCSX2) and fix those bugs!

Thanks for reading!

Comments

Popular posts from this blog

Logarithmic scales for UI

Improving the performance of UI list updates

Contexted drop in Rust