10
\$\begingroup\$

I'm writing an isometric 2D game and I'm having difficulty figuring precisely on which tile the cursor is. Here's a drawing:

where xs and ys are screen coordinates (pixels), xt and yt are tile coordinates, W and H are tile width and tile height in pixels, respectively. My notation for coordinates is (y, x) which may be confusing, sorry about that.

The best I could figure out so far is this:

int xtemp = xs / (W / 2);
int ytemp = ys / (H / 2);
int xt = (xs - ys) / 2;
int yt = ytemp + xt;

This seems almost correct but is giving me a very imprecise result, making it hard to select certain tiles, or sometimes it selects a tile next to the one I'm trying to click on. I don't understand why and I'd like if someone could help me understand the logic behind this.

Thanks!

\$\endgroup\$

3 Answers 3

2
\$\begingroup\$

For accurate measure, we could consider following:

Lets first consider how to transform coordinates from isometric space, determined by i and j vectors (as in isometricMap[i,j]) or as the yt and xt on the screen, to screen space, determined by x and y of the screen. Lets assume your screen space is aligned at origin with isometric space for simplicity's sake.

One way to do the transform is to do a rotation first, then scale the y or x-axis. To get the necessary values to match your yt and xt I can't quite come up with on the spot here. You may create a matrix to do this or not and then use reverse matrix, but the reverse operation is basically what you want.

Scale the value in reverse and then rotate backwards to get the values and round downwards.

There are other ways to this I guess, but this seems most proper to me right now.

\$\endgroup\$
2
  • \$\begingroup\$ argh. I've been revising this post so many times and i think i cant quite get my point across as neatly as I would like anyway. I need sleep. \$\endgroup\$
    – Toni
    Commented Apr 25, 2012 at 20:41
  • 1
    \$\begingroup\$ Thanks, matrices is definitely the best solution here. I have something almost working now! \$\endgroup\$
    – Asik
    Commented Apr 26, 2012 at 4:52
4
\$\begingroup\$

I had this same problem for a game that I was writing. I imagine that this problem will differ based on how exactly you implemented you isometric system, but I'll explain how I solved the problem.

I first started with my tile_to_screen function. (I assume that's how you are placing the tiles in the right location in the first place.) This function has an equation to calculate screen_x and screen_y. Mine looked like this (python):

def map_to_screen(self, point):
    x = (SCREEN_WIDTH + (point.y - point.x) * TILE_WIDTH) / 2
    y = (SCREEN_HEIGHT + (point.y + point.x) * TILE_HEIGHT) / 2
    return (x, y)

I took those two equations and made them into a system of linear equations. Solve this system of equations in any method you choose. (I used a rref method. Also, some graphing calculators can solve this problem.)

The final equations looked like this:

# constants for quick calculating (only process once)
DOUBLED_TILE_AREA = 2 * TILE_HEIGHT * TILE_WIDTH
S2M_CONST_X = -SCREEN_HEIGHT * TILE_WIDTH + SCREEN_WIDTH * TILE_HEIGHT
S2M_CONST_Y = -SCREEN_HEIGHT * TILE_WIDTH - SCREEN_WIDTH * TILE_HEIGHT

def screen_to_map(self, point):
    # the "+ TILE_HEIGHT/2" adjusts for the render offset since I
    # anchor my sprites from the center of the tile
    point = (point.x * TILE_HEIGHT, (point.y + TILE_HEIGHT/2) * TILE_WIDTH)
    x = (2 * (point.y - point.x) + self.S2M_CONST_X) / self.DOUBLED_TILE_AREA
    y = (2 * (point.x + point.y) + self.S2M_CONST_Y) / self.DOUBLED_TILE_AREA
    return (x, y)

As you can see, it's not simple like the initial equation. But it does work nicely for the game I created. Thank goodness for linear algebra!

Update

After writing a simple Point class with various operators, I simplified this answer to the following:

# constants for quickly calculating screen_to_iso
TILE_AREA = TILE_HEIGHT * TILE_WIDTH
S2I_CONST_X = -SCREEN_CENTER.y * TILE_WIDTH + SCREEN_CENTER.x * TILE_HEIGHT
S2I_CONST_Y = -SCREEN_CENTER.y * TILE_WIDTH - SCREEN_CENTER.x * TILE_HEIGHT

def screen_to_iso(p):
    ''' Converts a screen point (px) into a level point (tile) '''
    # the "y + TILE_HEIGHT/2" is because we anchor tiles by center, not bottom
    p = Point(p.x * TILE_HEIGHT, (p.y + TILE_HEIGHT/2) * TILE_WIDTH)
    return Point(int((p.y - p.x + S2I_CONST_X) / TILE_AREA),
                 int((p.y + p.x + S2I_CONST_Y) / TILE_AREA))

def iso_to_screen(p):
    ''' Converts a level point (tile) into a screen point (px) '''
    return SCREEN_CENTER + Point((p.y - p.x) * TILE_WIDTH / 2,
                                 (p.y + p.x) * TILE_HEIGHT / 2)
\$\endgroup\$
1
  • \$\begingroup\$ Yes, a system of two linear equations should also work. Considering we have two vectors that are not parallel, you should be able to get any point on the plane by using the unit vectors of yt and xt. Although I think your implementation is a bit constricted looking and i'm not going to bother validate it. \$\endgroup\$
    – Toni
    Commented Apr 25, 2012 at 20:44
2
\$\begingroup\$

You're using a good coordinate system. Things get much trickier if you use staggered columns.

One way to think about this problem is that you have a function to turn (xt, yt) into (xs, ys). I'll follow Thane's answer and call it map_to_screen.

You want the inverse of this function. We can call it screen_to_map. Function inverses have these properties:

map_to_screen(screen_to_map(xs, ys)) == (xs, ys)
screen_to_map(map_to_screen(xt, yt)) == (xt, yt)

Those two are good things to unit test once you have both functions written. How do you write the inverse? Not all functions have inverses but in this case:

  1. If you wrote it as a rotation followed by a translation, then the inverse is the inverse translation (negative dx,dy) followed by the inverse rotation (negative angle).
  2. If you wrote it as a matrix multiply, then the inverse is the matrix inverse multiply.
  3. If you wrote it as algebraic equations defining (xs, ys) in terms of (xt, yt), then the inverse is found by solving those equations for (xt, yt) given (xs, ys).

Be sure to test that the inverse + original function give back the answer you started with. Thane's passes both tests, if you take out the + TILE_HEIGHT/2 render offset. When I solved the algebra, I came up with:

x = (2*xs - SCREEN_WIDTH) / TILE_WIDTH
y = (2*ys - SCREEN_HEIGHT) / TILE_HEIGHT
yt =  (y + x) / 2
xt =  (y - x) / 2

which I believe is the same as Thane's screen_to_map.

The function will turn the mouse coordinates into floats; use floor to convert them into integer tile coordinates.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Thanks! I ended up using a transformation matrix, so that writing the inverse is trivial, i.e. it's just Matrix.Invert(). Plus it leads to a more declarative style of coding (Matrix.Translate() * Matrix.Scale() * Matrix.Rotate() rather than a bunch of equations). Maybe it's slightly slower though, but that shouldn't be an issue. \$\endgroup\$
    – Asik
    Commented Apr 27, 2012 at 3:41

You must log in to answer this question.

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