One of the annoying things with XNA, happens when handling mouse clicks. If you’ve ever worked on an XNA windows game (note: this only really applies to a windows game) in windowed mode and tried switching applications, you may have noticed that XNA still captures the clicks even when the window is not active! It also reports the mouse position when it’s outside of the window which can cause more issues in and of itself. Unfortunately, the fix for the first problem doesn’t work if you’re using multiple forms in the same application.
So, we have three issues:
- XNA captures clicks while the game is not active (i.e. from other applications)
- and it reports the mouse position when it’s outside of the viewable area
- and the fix for the first item above doesn’t work when you have multiple forms in your application.
Fortunately, there’s an easy way to resolve these! “Great!”, you say, “now how do I do it?”. Click on, my friend.
Ok, let’s start out in our Update statement with a basic mouse click check:
1 2 3 4 5 6 7 8 9 10 |
MouseState _previousMouseState; public void Update(GameTime gameTime) { MouseState ms = Mouse.GetState(); if (ms.LeftButton == ButtonState.Pressed && _previousMouseState.LeftButton == ButtonState.Released) { //Mouse was clicked } _previousMouseState = ms; } |
Ok, so we’ve got it checking to make sure that during the last Update call, the the left mouse button was in a state of Released and now it’s in a state of Pressed. That’s great, but this allows you to click in other applications or even off-screen and the code inside our if statement will still be executed!
Active or not Active? That is the question.
First, let’s tackle the issue of the game capturing clicks while it’s not active (when another application has focus). There is an easy solution to this: use the Microsoft.Xna.Framework.Game’s IsActive property. This property is true when the game application has focus and is false otherwise. Our main game class should be derived from Game, so all we have to do is add “&& this.IsActive” into our if statement like so:
1 2 3 4 5 |
if (ms.LeftButton == ButtonState.Pressed && _previousMouseState.LeftButton == ButtonState.Released && this.IsActive) { //Mouse was clicked and the form is active } |
There is one caveat with this method and that is that when you run the application “IsActive” is set to true no matter what. So, if you run the game and quickly switch to another application, when the game finished loading it will think it’s active (IsActive == true). Be aware of this since it could cause unexpected behaviour.
A Click Too Far
Ok, so now we’ve (more or less) established that the click happened while our game was active, but we still have the problem that if our game is in window mode, clicks can be captured off of the drawing area. All we have to do here, is make sure that the mouse’s X/Y coordinates are within the drawing surface by checking them against the graphics buffer’s (essentially, the window’s) width and height. You can do this like so:
1 2 3 4 5 6 7 |
if (ms.LeftButton == ButtonState.Pressed && _previousMouseState.LeftButton == ButtonState.Released && this.IsActive && ms.X >= 0 && ms.X < graphics.PreferredBackBufferWidth && ms.Y >= 0 && ms.Y < graphics.PreferredBackBufferHeight) { //Mouse was clicked and the form is active } |
So, we check to make sure that the mouse X/Y coordinates are above 0 and that they’re below the backbuffer width and height, respectively.
Which one is which?
For most games, this should be enough. However, in my case, I am making a map editor which uses a windows Form for the controls and the Game window for drawing the tile engine. I noticed while working on it on my netbook, which doesn’t have a lot of screen real-estate, that when I would click on the controls form while it was over the game window, it would click through. This got really annoying because when I’d switch a tile in the controls form, it would draw the tile in the game form.
So, after some searching for different ideas, I managed to figure out a way to do this pretty easily using System.Windows.Forms.Form’s ActiveForm property. This property “Gets the currently active form for this application.” (MSDN) Using this, we can just add a quick check to see if the game form is active within the application. I do this by checking to see if the active form’s Text property is equal to the game’s Window.Title property because it’s generic enough for this tutorial, but you can use something else such as checking the object Types. I should also mention that obviously doing it this way requires your game’s Window.Title to be different from your other forms’ Text property values. Ok, on to the code:
1 2 3 4 5 6 7 8 9 |
if (ms.LeftButton == ButtonState.Pressed && _previousMouseState.LeftButton == ButtonState.Released && this.IsActive && ms.X >= 0 && ms.X < graphics.PreferredBackBufferWidth && ms.Y >= 0 && ms.Y < graphics.PreferredBackBufferHeight && System.Windows.Forms.Form.ActiveForm != null && System.Windows.Forms.Form.ActiveForm.Text.Equals(this.Window.Title)) { //Mouse was clicked and the form is active } |
Just a note: the reason I fill out the namespaces rather than adding a using statement is because if you add using System.Windows.Forms, you’ll get conflicts with other variables because the namespaces have overlapping class names. It’s just easier to do it this way.
Conclusion
So, that’s how I check to see if clicks are being handled properly. It takes into account whether the form is active, whether the mouse is within the screen and even whether the form itself is active within the application. I hope this helped you. Please drop a comment if you have any questions or remarks!
Mephy says:
It would be simplier to make a pause screen for when the game is not active
(on beginning of Update()):
if (!IsActive) { CallPauseScreen(); // which will turn off all updates}
And for clicking outside the windows, draw a rectangle for the mouse and intersect it on the viewport:
Rectangle viewport = new Rectangle(GraphicsDevice.Viewport.X, GraphicsDevice.Viewport.Y, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height);Rectangle mouse = new Rectangle(mouseCoord.X, mouseCoord.Y, 1, 1);if (!viewport.Intersects(mouse)) { CallPauseScreen(); // which will active whenever the mouse leaves the screen}
Richard Marskell says:
Good point. Both of those would work for a typical game. In fact, for the Viewport check you could simplify it to this:
if (!GraphicsDevice.Viewport.Bounds.Contains(new Point(ms.X, ms.Y))) { CallPauseScreen();}
On a side note: I was using this code for a map editor, so there was no pause screen and there were other tool windows (i.e. other Win Forms) that could be activated within the same application. I found that if my extra forms were over the main game screen, clicks on the tools form would also occur on the main game screen; which usually meant that I would inadvertently edit the map when switching tools! That’s why I have the extra active window checks.
~Richard
Frederik Gelder says:
Great this works! its really a great help when u use an editor and it doesnt deselect your item when you click on the properties in the other window 😀
Richard Marskell says:
I know what you’re saying. I’m glad it helped you! 🙂 Out of curiosity, if it’s not top secret, what project are you working on?
~Richard
Cüneyt Yetişenler says:
Thank you very much for sharing this. I have had the same problems you mentioned before but thanks to you my mouse clicks work as they should.
Richard Marskell says:
Happy to help! 🙂