Esta tradução está incompleta. Ajude atraduzir este artigo.
A construção de tilemaps é uma técnica muito popular no desenvolvimento de jogos 2D, consistindo em construir o mundo do jogo ou mapas de pequeno porte através de imagens em forma de quadrado regular chamadas tile. O uso de tilemaps resulta em uma melhor performance e baixo gasto de memória nos jogos — uma grande imagem como fundo contém locais inacessíveis ao jogador e acaba consumindo uma quantidade de memória desnecessariamente. Esse conjunto de artigos contém o básico para criar tilemaps usando JavaScript e o Canvas (embora essas mesmas técnicas pudessem ser aplicadas em qualquer outra linguagem de programação.)
Baseado no ganho de perfomance, os tilemaps também podem ser construidos como uma grade lógica, no qual pode ser usado de outras formas dentro da lógica do jogo ( por exemplo para criar um sistema de path-finding ou de colisões) ou criar uma camada de edição.
Alguns jogos populares que utilizam essa técnica são Super Mario Bros, Pacman, Zelda: Link's Awakening, Starcraft, e Sim City 2000. Imagine algum jogo que use regularmente repetidos quadrados no fundo da tela, provavelmente ele funciona com o uso de tilemaps.
The tile atlas
The most efficient way to store the tile images is in an atlas or spritesheet. This is simply all of the required tiles grouped together in a single image file. When it's time to draw a tile, only a small section of this bigger image is rendered on the game canvas. The below images shows a tile atlas of 8 x 4 tiles:
Using an atlas also has the advantage of naturally assigning every tile an index. This index is perfect to use as the tile identifier when creating the tilemap object.
The tilemap data structure
It is common to group all the information needed to handle tilemaps into the same data structure or object. These data objects (map object example) should include:
- Tile size: The size of each tile in pixels across / pixels down.
- Image atlas: The Image atlas that will be used (one or many.)
- Map dimensions: The dimensions of the map, either in tiles across / tiles down, or pixels across / pixels down.
- Visual grid: Includes indices showing what type of tile should be placed on each position in the grid.
- Logic grid: This can be a collision grid, a path-finding grid, etc., depending on the type of game.
Note: For the visual grid, a special value (usually a negative number, 0
or null
) is needed to represent empty tiles.
Square tiles
Square-based tilemaps are the most simple implementation. A more generic case would be rectangular-based tilemaps — instead of square — but they are far less common. Square tiles allow for two perspectives:
- Top-down (like many RPG's or strategy games like Warcraft 2 or Final Fantasy's world view.)
- Side-view (like platformers such as Super Mario Bros.)
Static tilemaps
A tilemap can either fit into the visible screen area screen or be larger. In the first case, the tilemap is static — it doesn't need to be scrolled to be fully shown. This case is very common in arcade games like Pacman, Arkanoid, or Sokoban.
Rendering static tilemaps is easy, and can be done with a nested loop iterating over columns and rows. A high-level algorithm could be:
for (var column = 0; column < map.columns; column++) { for (var row = 0; row < map.rows; row++) { var tile = map.getTile(column, row); var x = column * map.tileSize; var y = row * map.tileSize; drawTile(tile, x, y); } }
You can read more about this and see an example implementation in Square tilemaps implementation: Static maps.
Scrolling tilemaps
Scrolling tilemaps only show a small portion of the world at a time. They can follow a character — like in platformers or RPGs — or allow the player to control the camera — like in strategy or simulation games.
Positioning and camera
In all scrolling games, we need a translation between world coordinates (the position where sprites or other elements are located in the level or game world) and screen coordinates (the actual position where those elements are rendered on the screen). The world coordinates can be expressed in terms of tile position (row and column of the map) or in pixels across the map, depending on the game. To be able to transform world coordinates into screen coordinates, we need the coordinates of the camera, since they determine which section of the world is being displayed.
Here are examples showing how to translate from world coordinates to screen coordinates and back again:
// these functions assume that the camera points to the top left corner function worldToScreen(x, y) { return {x: x - camera.x, y: y - camera.y}; } function screenToWorld(x,y) { return {x: x + camera.x, y: y + camera.y}; }
Rendering
A trivial method for rendering would just be to iterate over all the tiles (like in static tilemaps) and draw them, substracting the camera coordinates (like in the worldToScreen()
example shown above) and letting the parts that fall outside the view window sit there, hidden. Drawing all the tiles that can not be seen is wasteful, however, and can take a toll on performance. Only tiles that are at visible should be rendered ideally — see the Performance section for more ideas on improving rendering performance.
You can read more about implementing scrolling tilemaps and see some example implementations in Square tilemaps implementation: Scrolling maps.
Layers
The visual grid is often made up of several layers. This allows us to have a richer game world with less tiles, since the same image can be used with different backgrounds. For instance, a rock that could appear on top of several terrain types (like grass, sand or brick) could be included on it's own separate tile which is then rendered on a new layer, instead of several rock tiles, each with a different background terrain.
If characters or other game sprites are drawn in the middle of the layer stack, this allows for interesting effects such as having characters walking behind trees or buildings.
The following screenshot shows an example of both points: a character appearing behind a tile (the knight appearing behind the top of a tree) and a tile (the bush) being rendered over different terrain types.
The logic grid
Since tilemaps are an actual grid of visual tiles, it is common to create a mapping between this visual grid and a logic grid. The most common case is to use this logic grid to handle collisions, but other uses are possible as well: character spawning points, detecting whether some elements are placed together in the right way to trigger a certain action (like in Tetris or Bejeweled), path-finding algorithms, etc.
Note: You can take a look at our demo that shows how to use a logic grid to handle collisions.
Isometric tilemaps
Isometric tilemaps create the illusion of a 3D environment, and are extremely popular in 2D simulation, strategy or RPG games. Some of these games include SimCity 2000, Pharaoh or Final Fantasy Tactics. The below image shows an example of an atlas for an isometric tileset.
Performance
Drawing scrolling tile maps can take a toll on performance. Usually, some techniques need to be implemented so scrolling can be smooth. The first approach, as discussed above, is to only draw tiles that will be visible. But sometimes, this is not enough.
One simple technique consists of pre-rendering the map in a canvas on its own (when using the Canvas API) or on a texture (when using WebGL), so tiles don't need to be re-drawn every frame and rendering can be done in just one blitting operation. Of course, if the map is large this doesn't really solve the problem — and some systems don't have a very generous limit on how big a texture can be.
One way consists of drawing the section that will be visible off-canvas (instead of the entire map.) That means that as long as there is no scrolling, the map doesn't need to be rendered.
A caveat of that approach is that when there is a scrolling, that technique is not very efficient. A better way would be to create a canvas that is 2x2 tiles bigger than the visible area, so there is one tile of "bleeding" around the edges. That means that the map only needs to be redrawn on canvas when the scrolling has advanced one full tile — instead of every frame — while scrolling.
In fast games that might still not be enough. An alternative method would be to split the tilemap into big sections (like a full map split into 10 x 10 chunks of tiles), pre-render each one off-canvas and then treat each rendered section as a "big tile" in combination with one of the algorithms discussed above.
See also
- Related articles on the MDN:
- External resources: