Bitmap way of displaying tile cards
The technique of automatically selecting the desired tile from the tile map.
At first, this article was the answer to a question on TIGSource , but it seemed to me that it was worth expanding it a bit and publishing it separately.
Task: we generated a beautiful platforming level and want to be able to automatically arrange tiles on it taking into account neighbors so that they look right.
Tiles based on their neighbors
Tiles in Super Mario do not take into account their neighbors: a stone block always looks the same, both as a separate fragment and as part of a wall.
This is quite suitable for many games, but when creating a more harmonious design it may seem unnatural. Tiles that take into account their neighbors solve this problem by comparing their appearance with neighboring tiles.
One bit map
Imagine that using some tricky techniques, we created a platform-level diagram consisting only of stone blocks and “air” between them. The level can be represented as a single-bit image in which the state of each pixel is determined by a single bit (1 - stone block, 0 - "air"). Here is an enlarged example of a part of this level with added grid lines:
A set of tiles (tileset)
A tileset is a set of graphic images that you can use to populate a map. The Mario tileset is quite limited, it consists of several types of blocks and “decorations”, but our set will contain many images for each type of tiles:
To determine which tile should be located at a particular point on the map, we must examine the immediate neighbors of that point (for now, we will ignore the neighbors diagonally). In order not to write large constructions from if / else-if to handle all possible combinations of neighbors, we use a system that assigns values in each direction.
The value for each point is found by examining its neighbors and adding values for those of them in which there is a stone. For example, if the neighboring point at the top is also filled with a stone, then it is assigned a value of 1. If the neighbors are filled with a stone from the top and bottom, then the point is assigned the value 1 + 4, that is 5.
You may notice that the assigned direction values are the same as the values in the positions in binary numbers, and this is not surprising: both types of values are ways of representing possible combinations of four positions, each of which can be in the “on” or “off” states ( stone or "air").
Here is a map segment with the filled values of all tiles. You can try to manually calculate the value for a pair of tiles to understand how this works.
The method for arranging the tileset shown in the image is not accidental: it is positioned in such a way that each tile corresponds to the map tile to which the value should be assigned. Having assigned values to all points of the map, we simply look for the value in the tileset and place the appropriate tile at this point:
Part one: getting rid of the "air"
The above example works well for hanging platforms, but doesn’t actually fully process two types of tiles.
Imagine that instead of a platformer, we are working on a two-dimensional strategy with a top view, in which there are two types of tiles - grass and water. In this case, the tile image will be present at each point of the map, there will be no empty places in it, as in the platform game. This means that to determine the appropriate tile, each point on the map must have a value generated based on its neighbors.
We can use the exact same neighbor assessment system as before, but we need a method that allows us to determine what is in it - grass or water when studying the point. It is very simple to implement - just add one more value to the point itself, using the same pattern “2 to the power of n” from other values:
Let's decide that if there is water, we will add points to the value, but if there is grass, we won’t . That is, a grass tile surrounded by grass on all sides will have a value of 0. A grass tile with water on top and right has a value of 1 + 8 = 9. A water tile surrounded by grass on all sides will have a value of 16. Water tile surrounded by from all sides with water, it has a value of 1 + 2 + 4 + 8 + 16 = 31
Part Two: Add Variability
How to provide processing of other types of relief?
For example, in a top-view game, there are three types of terrain: water, grass, and forest. We are already processing the borders of water and grass, now we need to learn how to process the borders of water and forest, as well as grass and forests.
Previously, we had two options for tiles for each neighboring position (grass or water), so we used a binary system. Now there are already three options, so we need to use the ternary system. It is necessary to change the neighbor evaluation system to match the new calculus system:
In the binary system, the template “2 to the power of n” was used, in the new one we will use the template “3 to the power of n”.
In the ternary system, each position has three possible states: grass, water, forest, or 0, 1, 2. When grass is at the current point, we ignore the value (we multiply it by 0). When there is water at the point, we add the set value (multiply it by 1). In the case of the forest, we add double the value (multiply it by 2).
That is, in the case of a forest tile, above and to the right of which there is water, below is a forest, and on the left is grass: 81 * 3 + 1 * 2 + 3 * 1 + 9 * 3 + 27 * 0 = 275
As you can see, at this stage for To cover all card combinations with three terrain types, 324 tile images must already be drawn. When working manually, this would take a very long time. I highly recommend exploring at least partially automated ways to create so many combinations.
Of course, in the same way, the system can be expanded to a larger number of terrain types, but the number of tile images will increase significantly. Therefore, I recommend imposing restrictions on which tiles can be adjacent to each other. For example, if forest and water tiles can never border each other, then in the above example, you will need several hundred tile images less.