Several months ago, I talked about the distinction between world space and screen space. As a recap, these are fundamental concepts that separate our game or simulation state from our drawn or rendered state. What gets drawn to the screen is not necessarily how things are laid out in the game’s actual (world) state. Check out the previous articles for more information.
The concept of tile picking involves a user hovering their mouse or some other input device over a tile in the game map. Usually the user is doing this in order to interact with the tile such as moving a unit to a location, placing an item on the tile, or inspecting the metadata of the tile. Fortunately for the developer, the process of picking is independent of the projection other than some simple math to do a conversion of coordinates.
Imagine that you are playing SimCity 2000, and you want to create a stretch of road from one location to another. The process involves the user hovering the starting tile, clicking the mouse, dragging the road to the end tile, and releasing the mouse. Tiles were picked out of the game map based on the mouse’s coordinates during game updates. Which space do we pick the tile from? Do we have to calculate if the mouse is contained within the projected tile or within the game’s world space coordinates?
If the projection used in the game is orthogonal (think NES/SNES Zelda games), then the tile picking really is just a matter of answering the question, “Which tile contains the current mouse coordinate?” Answering the question is simple:
mousePosition = GetCurrentMousePosition(); worldX = floor(mousePosition.X / tileWidth); worldY = floor(mousePosition.Y / tileHeight); pickedTile = tiles[worldX][worldY];
To explain briefly, we get the position of the mouse (I use SDL which has a function to get the mouse position). We then convert the coordinates from screen space into world space. Finally we get the tile that is stored in our tile collection at the world space coordinates. We floor the coordinate conversion because landing in the middle of a tile will result in a partial index.
Reference the grid to the left. For the sake of discussion, let us assume that each grid contains 16 x 16 tiles. The top left most pixel is located in world space [0,0] and at screen space (0,0). The top right most pixel is located in world space [5, 0] and at screen space (80,0).
If the user moves the mouse to the screen space coordinate (25, 50), then the math to calculate the picked tile is as follows (using the aforementioned technique).
mousePosition = (25, 50); worldX = floor(25 / 16) = 1; worldY = floor(50 / 16) = 3; pickedTile = tiles;
Performing that calculation, we get a result of the tile highlighted in blue. It is then up to you to do whatever you need with that tile. You can highlight it by changing its color, pass it on to some actor to recalculate its path, or place an object into the map at that tile’s position. Note that some bounds checking may need to be performed if your map has boundaries. If you try to get a tile from your tile collection at an index that does not exist, you will surely run into problems.
The good news is that the strategy does not have to change with your projection differences. Only some minor math tweaks need to be made. Recall that the reason for this is because your world space representation does not change as your projection changes. They are independent! See below for the changes to the tile picking math.
mousePosition = GetCurrentMousePosition(); // convert our isometric screen coordinate into orthogonal world coordinate worldPositionX = (2 * mousePositionY + mousePositionX) / 2; worldPositionY = (2 * mousePositionY - mousePositionX) / 2; worldX = floor(worldPositionX / tileWidth); worldY = floor(worldPositionY / tileHeight); pickedTile = tiles[worldX][worldY];
Notice that the only difference here is the translation from isometric coordinates to orthogonal coordinates. We do this because the tiles are not positioned on a nice orthogonal grid like our previous example. They are offset slightly in an isometric manner. Could you imagine if we tried to store our game state the same as our rendered state? Trying to pick a tile by calculating if an isometrically projected tile contains our mouse coordinates would be much harder than just translating into orthogonal and doing some simple division.