Lock Mouse Position

Hi,

I have issues with mouse locking. I would like to lock the mouse to the current position when I right click. My approach was to set the position to the locked position at the end of every frame and to use relative mouse positions for rotating. But the rotation is not very smooth, i have the impression that I loose mouse updates with this approach. But I have no idea how to solve it in a different way.

How about making a custom cursor class.
It would draw the cursor on draw() and update the position on update(). Adding a simple property for when the cursor is allowed to move would fix your problem.

Would not help. When I run in window mode I would reach the end of the screen and then the rotation would stop. I hide the cursor while rotating, so the cursor is not an issue.

Hey Sebastian. I would recommend using Cursor.Clip, from System.Windows.Forms. Clip will limit mouse movement in the screen by given rectangle. Only problems is that if player decides to minimize your game, or press something like Ctrl+Esc it will remove the clip. So your best option would be to track when the application is in focus again and reassign clip. Also don’t forget to change clip when window is moved or resized.

To clip mouse use:
System.Drawing.Rectangle bounds = new System.Drawing.Rectangle(Window.ClientBounds.X + 5,
Wndow.ClientBounds.Y + 5,
Window.ClientBounds.Width - 5,
Wndow.ClientBounds.Height - 5);
System.Windows.Forms.Cursor.Clip = bounds;

Try adding to you main update function and see how it works (just don’t leave it there). I would not use this if you are in the main menu, because then there won’t be any way to move or resize the window properly.

It solves one part of the problem. But what I would also like to achieve is to still get relative mouse positions while the cursor is locked.

I press the right mouse button and would like to rotate my camera until the mouse would fall down from the desk, but it stops when the end of my screen/monitor is reached.

When I reset the mouse to the viewport center I loose updates (perhaps rounding issues or so).

My only solution I found so far is to reset the position every 30 frames only and ignore this frame.

       frames++;

        if (isPositionLocked)
        {
            mouseScreenPoint = capturedScreenPosition;

            if (frames % 30 == 0)
            {
                Mouse.SetPosition(capturedScreenPosition.X, capturedScreenPosition.Y);

                mouseNow = Mouse.GetState();
                mousePrev = mouseNow;
            }
        }

It is smooth in 99% of the time but sometimes, when this frame 30 is important, the rotation is jerky.

Use mouse delta. Every update track changed position from previous update and after that reset Cursor position to zero.

Vector2 mouse_delta = new Vector2(System.Windows.Forms.Cursor.Position.X - window_center.X, System.Windows.Forms.Cursor.Position.X - window_center.Y);

System.Windows.Forms.Cursor.Position = new System.Drawing.Point(window_center.X, window_center.Y);

I believe that should solve your problem.

How is this solution different from mine? The only difference is that you are windows forms directly.

Ok, it actually works. But why does it not work with MonoGame?

Works:

  mouseScreenPoint = capturedScreenPosition;
mouseScreenDelta =
    new Point(
        System.Windows.Forms.Cursor.Position.X - capturedScreenPosition.X,
        System.Windows.Forms.Cursor.Position.Y - capturedScreenPosition.Y);

System.Windows.Forms.Cursor.Position = new System.Drawing.Point(capturedScreenPosition.X, capturedScreenPosition.Y);

Does not work

mouseScreenPoint = capturedScreenPosition;
mouseScreenDelta =
    new Point(
        Mouse.GetState().X - capturedScreenPosition.X,
        Mouse.GetState().Y - capturedScreenPosition.Y);

Mouse.SetPosition(capturedScreenPosition.X, capturedScreenPosition.Y);

Well the difference is that my solution won’t ignore any frames, it is consistent that way. About not working with MonoGame I believe it has something to do about when it gets information about mouse position. If I understand correctly not working from you means not moving, if that so, then MonoGame sets Mouse position at the end of the updates. Honestly I have no idea :smile:

Yes, thats true, MonoGame updates the mouse only during the game loop. Too bad you cannot monkey patch it :wink:

But at least I made an input manager so I can fix it in one place :smiley:

I just did a quick test myself

protected override void Update(GameTime gameTime)
{
    delta = Mouse.GetState().Position.ToVector2() - center;
    Mouse.SetPosition(center);
    rotation += delta.X * sensitivity;
}

Rendering a sprite that rotates with the x-pos of my mouse work perfectly fine this way.

Which Platform do you use? I use WindowsDX ?

And which version? My Mouse.SetPosition has no override for Point.

@Finnhax Can you share your test? Perhaps it is my hardware.

I’ve created a minimal example for you:

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace RotationTest
{
    public class Game1 : Game
    {
        private GraphicsDeviceManager _graphics;
        private SpriteBatch _spriteBatch;

        private Texture2D _texture;
        private Point _texSize;

        private float _rotation;
        private float _sensitivity;

        private Point _center;

        
        public Game1()
        {
            _graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            IsMouseVisible = true;
        }

        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            base.Initialize();
        }

        protected override void LoadContent()
        {
            _spriteBatch = new SpriteBatch(GraphicsDevice);

            _texSize = new Point(100,100);
            _texture = CubeTexture(_texSize.X, _texSize.Y, Color.Red);

            _rotation = 0f;
            _sensitivity = 0.01f;

            _center = new Point(400, 300);
        }

        protected override void UnloadContent()
        {
        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();

            var delta = Mouse.GetState().Position - _center;
            Mouse.SetPosition(_center.X, _center.Y);
            _rotation += delta.X * _sensitivity;

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            _spriteBatch.Begin();
            _spriteBatch.Draw(_texture, _center.ToVector2(), null, Color.White, _rotation, _texSize.ToVector2() / 2f, 1f, SpriteEffects.None, 0);
            _spriteBatch.End();

            base.Draw(gameTime);
        }

        private Texture2D CubeTexture(int width, int height, Color color)
        {
            var texture = new Texture2D(_graphics.GraphicsDevice, width, height);
            var data = new Color[width * height];

            for (var i = 0; i < data.Length; i++)
                data[i] = color;

            texture.SetData(data);
            return texture;
        }
    }
}

I tested in with the DesktopGL-Template and it works perfectly fine.
Monogame-Version is 3.6

Awesome. I found the reason now.

If you set SynchronizeWithVerticalRetrace=false it does not work anymore.

I just tried that with my minimal example and everything’s still working fine.
My guess would be that because of potentially higher fps some part in your code is the problem. E.g. not multiplying by deltatime or something

I tried your example, when I set SynchronizeWithVerticalRetrace=false it does not work anymore on my machine.

I have PR for this. https://github.com/MonoGame/MonoGame/pull/6081

If you want to test it here is the patched MG SDK Installer.
https://1drv.ms/u/s!AgIeh29TX5UhkPppcjPqV4vKoiVjbg

Great, but why does it take so long to get something like this out?

¯_(ツ)_/¯


1 Like