1. Nebuchadnezzar
  2. News

Nebuchadnezzar News

Blog post #5 - Map coordinates

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, we’ll 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.



Let’s take a look.

From the beginning of Nebuchadnezzar’s development, we knew we wanted the maps to have a rectangular shape because it’s 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, wouldn’t 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 advantageous—mainly during rendering, when in each frame we need to iterate over many tiles.



It’s connected with another big advantage. Because rendering is one of the most compute consuming parts of a game, we only want to render what’s necessary. This typically means: only the tiles visible on a player’s screen. In the Staggered system’s 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 system—there isn’t a simple way to describe its range.



Of course, this system has disadvantages too. Mainly, it’s 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, it’s 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, there’s 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, we’ll 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 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 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};
}
}

Blog post #4 - Game loop

Hi and welcome to our next blog post about the more slightly technical side of game development. In today’s post, we’ll cover almost the whole game because we’ll be looking at the game loop: what it is and how it looks.

Game loop is the main part of a game program, where the game spend most of it's time. It is a basic logic which manage the whole run of a game. Of course there are step which are taken before and after the game loop but these are not important for this article. The basic architecture of a game loop may have many forms, but the the most simple and often sufficient looks like this:



That’s it. But don’t forget, under each of these parts there may be thousands or even millions of lines of source code. So lets look at these parts in more detail.

At the beginning of a game loop it’s necessary to handle all the inputs which may affect the state of the game. Typically this input comes from the player’s devices like a keyboard, mouse or controller. But in a multiplayer game, input may come from the server as well. In VR games input may be derived from the position or movement of the VR controller. There are also other sources for input. This is the simplest function of a game loop but no less important.

The second part is simulation of the game world. Each pass of a game loop moves game time forward and its task, at this point, is to compute how the game world is altered during gameplay. Typically, this means: go over the game’s objects, compute their mutual interaction ( i.e. a bullet hitting a wall) and also compute interactions with handled inputs from the previous part of game loop. ( i.e. player press key to move forward) After the computation the game stores this new world state and goes to the third part.

The last part is rendering. In this part, we render the current state of the world to output devices. In modern games, this part of the process is the most performance consuming. But in games focusing on advanced simulation, the simulation part may be more demanding.

[h2]Parallelization[/h2]
The biggest disadvantage of the mentioned model is that it doesn’t use modern hardware in an optimal way. That’s to say, the usage of multiple processor threads. The most straightforward multi-thread model is to compute inputs and simulation on one thread and render on another simultaneously, or in parallel. But there’s a problem. If we simultaneously change the world state in the simulation phase of game loop and render the world state in the rendering phase, then we will often render parts of the world before computing the next state. So the game loop process will get ahead of itself, and we don’t want that.



One of the solutions would be to wait until the simulation thread is finished before moving on to the render thread but that would bring us back to the original model and with a useless thread. The better solution is to compute only the simulation in the first iteration, and to store the results. Then in the second iteration, we can compute both simulation and rendering concurrently, because the rendering phase uses data computed in the previous iteration and does not collide with the running simulation thread. The only disadvantage is that the rendering is one iteration delayed, but player’s wont notice in most situations.

Even in this little problem you can see that, in general, the parallelization in computer games is complex and difficult because in games you typically have many dependent computations that are not easy to parallelize.

Of course, even the mentioned parallel model is very primitive and modern games use very complex game loops consisting of many threads with different degrees of dependency to one another. Or even multiple parallel game loops where each one computes its own part of simulation or rendering.

And that is, in short, a basic explanation of the function of a game loop: the process running in the background of every game, including ours. We hope you enjoyed the post and that you learned something new. Share your thoughts with us and others on our forum, Twitter, Facebook or Reddit. See you soon!

And as always, you can also read and discuss this blog post on our websites, forum or reddit.

Nebuchadnezzar development update: World map UI screen

Hello and welcome to another UI update.

Today we would like to show you Nebuchadnezzar's latest UI screen - world map screen. In this screen you will manage all trade and diplomacy. All is subject to change. Mainly the placeholder cities :)



Do you like it? Share your thoughts on our social sites or here on Steam. See you soon.