The basics of isometric programming
When I started work on Hostile Takeover, I had only admired existing isometric games and never thought about programming one myself. So when I decided that isometric would be the graphical style I’d go for, I had to do a lot of reading up on the subject and wrap my head around this way of “faking” a 3D presentation with 2D graphics. I thought I might as well share some of the basics I’ve come to understand – maybe you’re gearing up for your own isometric game, but if not, I hope you still find this somewhat interesting.
There were two main issues I had to figure out to get the most basic stuff up and running: drawing the tile sprites in an isometric grid and calculating which tile the mouse cursor is over. As with pretty much everything in programming, the answers to both issues were some surprisingly simple mathematical formulas.
Drawing an isometric grid
An isometric tile is basically a square tile rotated 45 degress around the z-axis and tilted around the x-axis so it’s twice as wide as it is high:
When drawing a grid made up of non-isometric tiles, it’s pretty easy as all the tiles on the same row have the same y-value, but with an isometric grid all tiles are offset half a tile both vertically and horizontally:
Image borrowed from this article
This is where basic math came to the rescue. The algorithm I use for drawing tiles is:
FOR Y := 1 TO SizeY DO BEGIN FOR X := 1 TO SizeX DO BEGIN DrawX := ((X - Y) * 38) + 400; DrawY := (X + Y) * 19; END; END;
My tiles are 76 pixels wide and 38 pixels high, which is why I multiple with 38 (half of 76) and 19 (half of 38). I add 400 to the x-value to keep the left half of the grid from being drawn at negative x-coordinates (i.e. beyond the left edge of my computer screen). This gives me the x and y coordinates for blitting the tile sprite to the screen. Simple!
Calculating tile mouse over
When it comes to figuring out which tile the mouse is over (so that the game will know which tile the player character should move to when the player clicks the mouse button!), it was slightly harder to figure out, but, again, some simple math came to the rescue.
I already knew what the screen coordinates of the mouse cursor is, since I used that for actually drawing the mouse cursor. The screen coordinate is just which pixel on the screen the mouse cursor is currently at. My screen resolution is 1280×800, so the mouse cursor’s x-coordinate will be 1 – 1280 and the y-coordinate 1 – 800. I also already know where my tiles are on the screen, since I just previously figured out the algorithm for drawing them to the screen. Combining this, I can make an algorithm for translating the mouse cursor’s screen coordinates to an isometric tile coordinate:
ScreenX := ScreenX - 438; TileX := Trunc((ScreenY / 38) + (ScreenX / 76)); TileY := Trunc((ScreenY / 38) - (ScreenX / 76));
So if I know that my mouse is at, say, [410, 235] on the screen, calculating which tile the mouse is over is as easy as:
TileX = (235 / 38) + (-28 / 76) = 5 (rounded down)
TileY = (235 / 38) – (-28 / 76) = 6 (rounded down)
You’ll notice that I start with subtracting 438 from the mouse cursor’s x-coordinate. This is because I added 400 to the x-coordinate when drawing the tiles, so I have to subtract this again (as well as half a tile width) before calculating which tile the mouse is over.
Now, this is all well and good for a map that isn’t larger than the computer monitor’s resolution. What do you do if you want a map that’s larger than that and want to allow the player to scroll the map? Easy, you just add an offset x and y value to the algorithms:
FOR Y := 1 TO SizeY DO BEGIN FOR X := 1 TO SizeX DO BEGIN DrawX := (((X - Y) * 38) + 400) - OffsetX; DrawY := ((X + Y) * 19) - OffsetY; END; END;
ScreenX := (ScreenX - 438) + OffsetY; ScreenY := ScreenY + OffsetY; TileX := Trunc((ScreenY / 38) + (ScreenX / 76)); TileY := Trunc((ScreenY / 38) - (ScreenX / 76));
When the player scrolls the map horizontally, just increase or decrease the OffsetX value accordingly, and do the same with the OffsetY value when the player scrolls vertically.
And there you have it. The basics of drawing and interacting with an isometric map. I had to play around with the values a bit, though, so when I for example add 400 to the DrawX value, that number is just something I arrived at with trial and error. It would differ for different monitor resolutions and depending on where you want the default map position to be at (i.e. before the player does any scrolling).
If you want to read more about isometric programming theory, there’s a bunch of enlightening articles over at GameDev.Net.
Oh, and in case you’re wondering, the code snippets are Pascal. I program in FreePascal with SDL and OpenGL for cross-platform development.