XNA Finding mouse position with 2D camera

Go To StackoverFlow.com

4

I have tried every way I found online to get the mouse position relative to the camera, but nothing will work. The selection tile always draws far away from the mouse. Also how would I only change the tile I am clicking on and not every tile with the same texture

Camera Class

public class Camera : Game1
{
    protected float _zoom;
    public Matrix _transform;
    public Vector2 _pos;
    protected float _rotation;

    public Camera()
    {
        _zoom = 1.0f;
        _rotation = 0.0f;
        _pos = Vector2.Zero;
    }

    public float Zoom
    {
        get { return _zoom; }
        set { _zoom = value; if (_zoom < 0.1f) _zoom = 0.1f; } // Negative zoom will flip image
    }

    public float Rotation
    {
        get { return _rotation; }
        set { _rotation = value; }
    }


    public void Move(Vector2 amount)
    {
        _pos += amount;
    }

    public Vector2 Pos
    {
        get { return _pos; }
        set { _pos = value; }
    }

    public Matrix get_transformation()
    {
        _transform = 
                     Matrix.CreateTranslation(new Vector3(-_pos.X, -_pos.Y, 0)) *
                     Matrix.CreateRotationZ(Rotation) *
                     Matrix.CreateScale(_zoom) *
                     Matrix.CreateTranslation(new Vector3(1024 * 0.5f, 768 * 0.5f, 0));
        return _transform;
    }

    public void Update()
    {
        Input();
    }

    protected virtual void Input()
    {
        KeyboardState _keyState;
        _keyState = Keyboard.GetState();

        if (_keyState.IsKeyDown(Keys.A))
        {
            _pos.X -= 5f;
        }
        if (_keyState.IsKeyDown(Keys.D))
        {
            _pos.X += 5f;
        }
        if (_keyState.IsKeyDown(Keys.W))
        {
            _pos.Y -= 5f;
        }
        if (_keyState.IsKeyDown(Keys.S))
        {
            _pos.Y += 5f;
        }
    }
}

Tile Class

class TileGeneration
{
    public Block[] tiles = new Block[3];
    public int width, height;
    public int[,] index;
    public Texture2D grass, dirt, selection;
    bool selected;
    MouseState MS;
    Vector2 mousePos;

    Camera camera;

    public TileGeneration()
    {

    }

    public void Load(ContentManager content, GraphicsDevice g)
    {
        grass = content.Load<Texture2D>(@"Tiles/grass");
        dirt = content.Load<Texture2D>(@"Tiles/dirt");
        selection = content.Load<Texture2D>(@"Tiles/selection");

        tiles[0] = new Block { Type = BlockType.Grass, Position = Vector2.Zero, texture = grass};
        tiles[1] = new Block { Type = BlockType.Dirt, Position = Vector2.Zero, texture = dirt};

        width = 50;
        height = 50;

        index = new int[width, height];

        camera = new Camera();

        Random rand = new Random();
        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                index[x,y] = rand.Next(0,2);
            }
        }
    }

    public void Update()
    {
        MS = Mouse.GetState();
        Matrix inverseViewMatrix = Matrix.Invert(camera.get_transformation());
        Vector2 mousePosition = new Vector2(Mouse.GetState().X, Mouse.GetState().Y);
        Vector2 worldMousePosition = Vector2.Transform(mousePosition, inverseViewMatrix);
        mousePos = worldMousePosition;
        Console.WriteLine(mousePos);

        if (MS.LeftButton == ButtonState.Pressed)
        {
            Console.WriteLine("Selected");
            selected = true;
        }
    }

    public void Draw(SpriteBatch spriteBatch)
    {
        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
              spriteBatch.Draw(tiles[index[x,y]].texture, new Rectangle(x * 64, y * 64, 64, 64), 
                    Color.White);
              if (selected && IsMouseInsideTile(x, y))
              {
                  if (tiles[index[x,y]].texture == grass)
                      tiles[index[x,y]].texture = dirt;
              }
              if(IsMouseInsideTile(x, y))
                  spriteBatch.Draw(selection, new Rectangle(x * 64, y * 64, 64, 64), Color.White);
            }
        }    
    }

    public bool IsMouseInsideTile(int x, int y)
    {
        return (mousePos.X >= x * 64 && mousePos.X <= (x + 1) * 64 &&
            mousePos.Y >= y * 64 && mousePos.Y <= (y + 1) * 64);
    }

Game1 Draw

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

        spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, null, null, null, null, 
            camera.get_transformation());

        tile.Draw(this.spriteBatch);
        player.Draw(this.spriteBatch);
        spriteBatch.End();

        base.Draw(gameTime);
    }

enter image description here

2012-04-03 20:57
by Corey
The code looks right to me, so I don't know if I can help. But I will say that you should use your MouseState MS instead of querying state again in your Update method - A-Type 2012-04-03 22:13
Thanks, that was a mistake I didn't notic - Corey 2012-04-03 22:43
Not entirely related, but your code would be more logical and more correctable if your camera wasn't an implementation of the game. Rather your camera should be a GameComponent that you activate in the Game1 (add it to components list) and then use statically. Same thing for all managers. In fact in most of my projects, Game1 is EMPTY, everything is done in external (often singleton) managers - NoName 2012-04-04 02:35


3

There may be a better way, but:

// absoluteMouseX will be the value from your MouseState, and camera will be an instance of your class
// You may need to convert your rotation to radians.

float relativeMouseX = absoluteMouseX + camera.Pos.X;

float relativeMouseY = absoluteMouseY + camera.Pos.Y;
2012-04-03 22:23
by annonymously
Yeah, the second method is what I have now and it is not working. I tried the first way and it is still doesn't seem to be workin - Corey 2012-04-03 22:41
@Corey Can you describe exactly what happens? The selection is far away from the cursor, but what's wrong with it? Is it rotating the wrong way, or not following the scale? It's hard to diagnose your problem without seeing it in actio - annonymously 2012-04-04 00:35
Ok I edited a picture in at the bottom of my question to explain better. Hope it is helpful, if not let me know what I can do to better explai - Corey 2012-04-04 01:32
I've edited my answer, you can try it now. Also, it might be helpful if you narrow the search by just trying to make the panning work, without rotation or zooming. If you temporarily disable the rotation and scale, we can find out if the position is being translated correctly - annonymously 2012-04-04 02:34
That isn't working either. I don't need either of those so if it would be easier to remove them that is fine by m - Corey 2012-04-04 02:47
OK, so it's definitely something wrong with the translatio - annonymously 2012-04-04 02:50
What should I do to test it - Corey 2012-04-04 02:53
Just remove all of the rotation and scale related code, and then see what happens. If it still doesn't work, what is the relationship between the actual mouse position and the selected one - annonymously 2012-04-04 02:56
Ok, I removed those and it is still off. I'm not sure. I am kinda confused about the whole thing now. Everything I seen online they would just invert the transform and get the mouse position relative to the camera by doing vector2.transform, but that doesn't work. I just want the selection to draw exactly where my mouse pointer i - Corey 2012-04-04 03:10
I think what he wants is for you to set a breakpoint in your loop and record the camera position, absolute mouse position, and relative mouse position values for inspection - A-Type 2012-04-04 03:21
Absolute mouse and relative mouse are both the same no matter wha - Corey 2012-04-04 03:29
So the camera isn't moving? If it's not moving then you just have to subtract the top-left margins around the map from the absolute mouse position - annonymously 2012-04-04 03:34
The camera is moving though. That's the weird thing. I have absoluteMouse.X + camera.Pos.X; for relativex and y, but it seems like it is not adding the camera positio - Corey 2012-04-04 03:40
Are you sure calling you're calling TileGeneration.Update()? I can't think of anything else that would yield these results - annonymously 2012-04-04 03:44
Yeah, it reads my mouse click from tileGeneration. Idk what's the problem I am stumped as wel - Corey 2012-04-04 03:48
This is usually the point where I would try to do a project clean and compile on a different machine, in case it's visual studio that's screwing u - annonymously 2012-04-04 03:52
Alright, thanks for trying to help me out. I will have to try that out tomorro - Corey 2012-04-04 03:55


1

I don't have XNA right now to test it, but I recall having problems with this.

A snippet of code I have which may help:

        Vector2 mouse = new Vector2(ms.X, ms.Y);
        Matrix transform = Matrix.Invert(camera.ViewMatrix);

        Vector2.Transform(ref mouse, ref transform, out mouse);

        selectedRow = (int)(mouse.Y / Tile.SIZE);
        if (selectedRow < 0) selectedRow = 0;
        else if (selectedRow >= rows) selectedRow = rows - 1;

        selectedCol = (int)(mouse.X / Tile.SIZE);
        if (selectedCol < 0) selectedCol = 0;
        else if (selectedCol >= cols) selectedCol = cols - 1;

Two things:
1. Notice how the row depends on the Y component of the Mouse, and the column on the X component.
2. Notice that it's considerably faster to directly get which tile the Mouse lays on rather than asking every single tile if it has the Mouse "inside".

2012-04-05 18:48
by Fermin Silva
++ Inverting the transform you used for your camera, is the key to this. You're reusing the same transform in reverse so it's hard to mess it up - Tory Netherton 2013-06-25 22:50