7
\$\begingroup\$

I'm creating a game in XNA similar to Minecraft - Minecraft 2D

It looks like this

game

Here's how it works:

  • All blocks are generated once with fixed x,y coordinates and just re-drawn.

        //generate world
        for (int i = 0; i < 25; i++)
        {
            blocks.Add(new Block("top", i * 32, 32 * 7, Color.White));
            for (int j = 8; j < 20; j++)
            {
                blocks.Add(new Block("dirt", i * 32, 32 * j, Color.White));
            }
        }
    
  • When user clicks on a block, I just browse through all blocks and check whether block exists.

    if ((mouseCoordinate.X - (mouseCoordinate.X % 32)) == block.x && (mouseCoordinate.Y - (mouseCoordinate.Y % 32)) == block.y) {
          //destroy block
    }
    
  • Currently, without any camera movements, everything works. When I move my camera just about 32px to the right, for example, it's all broken. I can't do previous step anymore.

  • What I need: I need to get mouse position relative to the map, not to the game window. How can I get mouse position relative to the map? Basically, I want mouse position 0,0 on the point I have circled here

00

How do I calculate it?

EDIT: here's my camera class (yes, Matrix)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.Reflection;
namespace MojePrvniHra
{
    public class Camera2d
    {
    protected float _zoom; // Camera Zoom
    public Matrix _transform; // Matrix Transform
    public Vector2 _pos; // Camera Position
    protected float _rotation; // Camera Rotation

    public Camera2d()
    {
        _zoom = 1.0f;
        _rotation = 0.0f;
        _pos = Vector2.Zero;
    }// Sets and gets zoom
    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; }
    }

    // Auxiliary function to move the camera
    public void Move(Vector2 amount)
    {
        _pos += amount;
    }
    // Get set position
    public Vector2 Pos
    {
        get { return _pos; }
        set { _pos = value; }
    }

    public Matrix get_transformation(GraphicsDevice graphicsDevice)
    {
        _transform =
          Matrix.CreateTranslation(new Vector3(-_pos.X, -_pos.Y, 0)) *
                     Matrix.CreateRotationZ(_rotation) *
                     Matrix.CreateScale(new Vector3(Zoom, Zoom, 1)) *
                     Matrix.CreateTranslation(new Vector3(graphicsDevice.Viewport.Width * 0.5f, graphicsDevice.Viewport.Height * 0.5f, 0));
        return _transform;
    }

    }
}

and here's how I start my spriteBatch

       spriteBatch.Begin(SpriteSortMode.BackToFront,
                BlendState.AlphaBlend,
                null,
                null,
                null,
                null,
                cam.get_transformation(graphics.GraphicsDevice));
\$\endgroup\$
13
  • 6
    \$\begingroup\$ Answered your question below, but noticed something strange on another part of your question: "When user clicks on a block, I just browse through all blocks and check whether block exists". You iterate through all of the blocks just to find which one the user clicked? If that's the case, that's completely unnecessary. Just take the world position, divide it by the block size, and finally round the result down (Math.Floor) on each component. That will give you the clicked block directly. \$\endgroup\$ Commented Dec 31, 2011 at 1:59
  • \$\begingroup\$ @DavidGouveia: yes, I currently do. foreach(Block b in blocks) { //if statement posted }. I'm not sure, how do I get "World position". I bet it's something trivial, but I'm unsure I know what it currently is. Do you mean mouse position on world? \$\endgroup\$
    – Martin.
    Commented Dec 31, 2011 at 11:01
  • \$\begingroup\$ Yes I meant the mouse position in the world, i.e. the very same value this question was all about. \$\endgroup\$ Commented Dec 31, 2011 at 11:46
  • 1
    \$\begingroup\$ I explained that in my other comment... For instance, on the X axis it would be: int blockX = (int)(mouseWorld.X / tileWidth) and in the Y axis it would be: int blockY = (int)(mouseWorld.Y / tileHeight). \$\endgroup\$ Commented Dec 31, 2011 at 11:51
  • 1
    \$\begingroup\$ Well, then you really should. It's the only way you can efficiently index individual tiles from the map. Will also help up a lot with collision detection. \$\endgroup\$ Commented Dec 31, 2011 at 12:01

2 Answers 2

13
\$\begingroup\$

Solution

Is your camera simulated using a view matrix? If so, all you need to do is:

Vector2 worldPosition = Vector2.Transform(mousePosition, Matrix.Invert(viewMatrix));

And if you're not using a view matrix... you should :-)

Explanation

The view matrix transforms coordinates from world space into view space. The inverse of this matrix does the opposite - it transforms coordinates from view space back into world space.

Since your mouse coordinates are defined in view space, all you need to do is transform them with the inverse view matrix to convert them into world space.

\$\endgroup\$
10
  • \$\begingroup\$ Wow I didn't realize that Vector2 had a Transform method. That makes things much easier. \$\endgroup\$ Commented Dec 31, 2011 at 2:05
  • \$\begingroup\$ Thank you a lot for this solution! I might take a look at this solution when I'm more better in XNA. For now, I'm not able to modify the Matrix (camera) class to fit needs of this transform. And sorry for not accepting you \$\endgroup\$
    – Martin.
    Commented Dec 31, 2011 at 11:18
  • \$\begingroup\$ How are you implementing your camera then? \$\endgroup\$ Commented Dec 31, 2011 at 11:48
  • \$\begingroup\$ @DavidGouveia: Matrix.CreateTranslation passing to the last parameter of spritebatch.Begin() (see my question, I have edited it before some time and described how I implement the camera) \$\endgroup\$
    – Martin.
    Commented Dec 31, 2011 at 11:51
  • \$\begingroup\$ Looked at your camera. You don't need to modify anything, it already fits the needs... Use this: Vector2 worldPosition = Vector2.Transform(mousePosition, Matrix.Invert(camera.get_transformation(GraphicsDevice)));. That single line will work no matter how much you move, rotate or zoom the camera. \$\endgroup\$ Commented Dec 31, 2011 at 12:03
3
\$\begingroup\$

The concept here is unprojecting a mouse coordinate to eye-space. When dealing with graphics, there are several different coordinate systems used to represent objects. I'll explain all of them, but the way you have set your rendering up cancels the need for the coordinate spaces:

  • Everything is generally defined by vertices relative to (0, 0). This is called local space.
  • If you want to position an object in the world, you need to convert local space to world space. You do this with what is called a model matrix, which allows for movement, rotation, and scaling of the object. The way you've set up your rendering does this, or a simple version of this by just providing the X and Y location of the Block in the world.
  • If you want to move around the world, you need to convert world space to eye space. This is done with a view matrix. You can move, rotate, and scale the camera in any way you want. In your case, you're either using a view matrix or just storing the location of the camera and offsetting everything by that position.
  • DirectX uses a default coordinate system of -1 to 1 on the X and Y axes (0 to 1 on the Z, but that's not important). Everything that is rendered must exist within that range, and to convert to that coordinate system, you need one last transformation - the projection matrix. In this case, you've set up your projection matrix to convert pixel coordinates to the default coordinate system. In this case the projection matrix doesn't make a difference when unprojecting, as it'll just give you the same mouse coordinate back.

On to the actual solution, you've removed the need for the model and projection matrices, so you can set up a view matrix from your camera position if you don't already have one. From there you're just using Vector2.Transform as suggested by the previous answer.

If you want the most direct solution that you can implement very quickly and without matrices, you can just offset your mouse coordinates by the camera position before aligning it to the nearest block like this:

Vector2 cameraPos = /*The position of your camera*/;
Vector2 clickCoord = mouseCoordinate + cameraPos;
if ((clickCoord.X - (clickCoord.X % 32)) == block.x && (clickCoord.Y - (clickCoord.Y % 32)) == block.y) {
      //destroy block
}
\$\endgroup\$
8
  • \$\begingroup\$ Using the view matrix directly, it's still just one line of code, and has the definite advantage that it will still work even if he decides to add zoom and rotation to his camera. Or if the camera has an origin different than zero, you'd also need to take that into account, while with the matrix it's all automatic. So I wouldn't look any other way. \$\endgroup\$ Commented Dec 31, 2011 at 2:36
  • \$\begingroup\$ Yeah, that's why I suggested your solution first. It's what I do in all my projects, and it's what I would recommend, but at the same time I wanted to show a very direct solution that solves the issue and nothing more. \$\endgroup\$ Commented Dec 31, 2011 at 2:41
  • 1
    \$\begingroup\$ You're right. In fact, before I got used to working with transformations, I also did most things this way, but faced the consequences later on... Once zooming gets mixed in, you can still sort of figure it out with a few multiplications. But once you start needing rotation, it becomes a trigonometry mess. Add a variable origin and parallax on top of that and you can imagine what that simple expression would turn into. :-) That's the real beauty of matrices, how they can encapsulate all of this complexity in a single black box that "just works". \$\endgroup\$ Commented Dec 31, 2011 at 2:48
  • \$\begingroup\$ Currently I'm using Matrix.CreateTranslation for moving camera. Currently, without changing anything but just adding your solution, this happens (first point = second (bad) point): i.lulzimg.com/48f9fac1e3.png \$\endgroup\$
    – Martin.
    Commented Dec 31, 2011 at 10:56
  • \$\begingroup\$ I think the issue right now is that your camera's origin is at the center of the screen instead of the top left I assumed it would be (Center makes more sense anyways). All you have to do is adjust the clickCoord to be centered: Vector2 clickCoord = mouseCoordinate - halfWindowSize + cameraPos; I'm not too familiar with XNA, so I'll let you figure out how to get the window size. \$\endgroup\$ Commented Dec 31, 2011 at 11:05

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .