





🌟 Special thanks to our amazing supporters:
✨ $10 Tier: [Geeks Love Detail]
🌈 $5 Tier: [Arch Toasty][Benedikt][David Martínez Martí]
Hello and welcome to another blog post about the development of Nebuchadnezzar. This time we will look at the core of almost every isometric game: the map. Specifically, well go over two different map coordinate systems and show you the one we use.
Typically, isometric games take place on a tile map. There are two basic coordinate systems for these maps: Staggered or Diamond. Games usually use one or the other. We use Staggered in Nebuchadnezzar. Although it looks complicated at first sight, we have several good reasons for choosing this system.
Lets take a look.
From the beginning of Nebuchadnezzars development, we knew we wanted the maps to have a rectangular shape because its much more immersive. If all tiles are in range (0 - [map width], 0 - [map height]) then the Staggered map system retains a rectangular shape where as a Diamond map would have a rhomboid shape, and therefore, wouldnt fit the whole screen.
If all tiles are in this range, then we can store them all in the matrix data structure. This provides fast access to arbitrary tiles. This is very advantageousmainly during rendering, when in each frame we need to iterate over many tiles.
Its connected with another big advantage. Because rendering is one of the most compute consuming parts of a game, we only want to render whats necessary. This typically means: only the tiles visible on a players screen. In the Staggered systems rectangular cutout of the screen, all the tiles will be in range ([x1] - [x2], [y1] - [y2]). Thanks to this, we can exclusively iterate over this sub-matrix of tiles. Notice the diagram of the Diamond systemthere isnt a simple way to describe its range.
Of course, this system has disadvantages too. Mainly, its ineffective at interpreting coordinates. This is a problem, for example, when moving objects. If we want to move in the four main directions, the Diamond system makes computation of neighboring tiles very simple. Just add or subtract 1 in the [x] or [y] axis. This gives us all four directions. In the Staggered system, its more complicated, and also the formula differs for odd and even rows.
In general, most of the operations in the Staggered system are more complicated. Another example being the distances between two tiles (with limitation to the four main directions) or even the rotation of the whole system, which in itself can be a separate blog post. In the end, we have transformation functions between the Staggered and Diamond systems. And for more complicated operations, we may use the more convenient one.
With ongoing developments, theres a growing number of coordinate operations we need. Although none of the operations are complex, it can be difficult to invent and test them. Under the text, you will find snippets of our source code with some of coordinate operations. You can use the code in your own project or to test your spatial orientation.
Next time, well look at another type of coordinate system as well as other operations. For example, an important element is the transformation between screen space and game space. We hope you learned something new about isometric games in this post. Feel free to ask any questions about this topic or any other.
See you soon!
And as always, you can also read and discuss this blog post on our websites, forum or reddit.
/**
* @brief Get neighbor tile in the given direction.
*
* @param pos Cooridinates of origin tile.
* @param direction Neighbor direction in range <0, 7> for NE, E, SE, S, SW, W, NW, N.
*/
N_Point Tile_Scene::neighbor(const N_Point pos, const int direction) {
const int x = pos.x;
const int y = pos.y;
switch (direction) {
case 0:
return {x + std::abs(y % 2), y - 1};
case 2:
return {x + std::abs(y % 2), y + 1};
case 4:
return {x - std::abs((y + 1) % 2), y + 1};
case 6:
return {x - std::abs((y + 1) % 2), y - 1};
case 1:
return {x + 1, y};
case 3:
return {x, y + 2};
case 5:
return {x - 1, y};
case 7:
return {x, y - 2};
}
n_assert_msg("Invalid parameters.");
return N_Point();
}
/**
* @brief Get direction between two neighboring tiles.
*
* @param from Cooridinates of origin tile.
* @param to Cooridinates of destination tile.
*
* @return Direction in range <0, 7> for NE, E, SE, S, SW, W, NW, N.
*/
int Tile_Scene::direction(const N_Point from, const N_Point to)
{
const int x = to.x - from.x;
const int y = to.y - from.y;
if ((x == std::abs(from.y % 2)) && (y == -1)) {
return 0;
}
else if ((x == std::abs(from.y % 2)) && (y == 1)) {
return 2;
}
else if ((x == -std::abs((from.y + 1) % 2)) && (y == 1)) {
return 4;
}
else if ((x == -std::abs((from.y + 1) % 2)) && (y == -1)) {
return 6;
}
else if ((x == 1) && (y == 0)) {
return 1;
}
else if ((x == 0) && (y == 2)) {
return 3;
}
else if ((x == -1) && (y == 0)) {
return 5;
}
else if ((x == 0) && (y == -2)) {
return 7;
}
n_assert_msg("Invalid parameters.");
return 0;
}
/**
* @brief Get diamond coordinates for given staggered coordinates.
*
* @param pos Staggered coordinates.
*/
N_Point Tile_Scene::to_diamond(const N_Point pos)
{
return {((pos.y + 1) / 2) + pos.x + (((pos.y + 1) % 2) * (pos.y < 0)), (pos.y / 2) - pos.x + ((pos.y % 2) * (pos.y < 0))};
}
/**
* @brief Get staggered coordinates for given diamond coordinates.
*
* @param pos Diamond coordinates.
*/
N_Point Tile_Scene::from_diamond(const N_Point pos)
{
if (pos.x % 2) {
return {(pos.x / 2) - ((pos.y - (pos.y < 0)) / 2) + ((pos.x % 2) * (pos.x < 0)), pos.x + pos.y};
}
else {
return {(pos.x / 2) - ((pos.y + (pos.y > 0)) / 2) + ((pos.x % 2) * (pos.x < 0)), pos.x + pos.y};
}
}
[ 6079 ]
[ 2063 ]
[ 4245 ]