Tile Mapping

8 10 2008

    Técnicas de tile mapping são bastante utilizados em jogos que precisem usar imagens muito grandes em dispositivos com uma memória reduzida. Neste artigo eu vou falar um pouco sobre a estrutura destes arquivos e sua respectiva implementação em Delphi (Object Pascal).

- O que é tile mapping? -

    Tile Mapping é uma técnica que consiste em formar imagens 2d grandes a partir de blocos menores, chamados tiles. Uma imagem formada por esta técnica tende a ter várias partes que se repetem, portanto seria desperdício alocar memória para esta mesma parte várias vezes.

    Tiles são, além de blocos menores de imagem, estruturas que guardam várias informações sobre a imagem que será desenhada, como o tipo de bloco que ele representa, se ele é um obstáculo ou não, se ele possui animações, etc.

    Você deve estar acostumado a ver jogos usando esta técnica em RPGs, jogos de plataforma 2d e mesmo jogos desenvolvidos para plataformas móveis (GB, GBA, NDS, celulares, …).

    Exemplo de jogo usando técnicas de tile mapping:


Mario é um exemplo de jogo que utiliza a técnica de tile mapping

- Estrutura -

    Como foi dito antes, a técnica de tile mapping consiste em formar uma imagem grande a partir de blocos menores de imagem. Para isso nós usamos uma matriz bidimensional, o tile map, que indica qual posição cada um desses blocos ocupa.

    A estrutura abaixo representa um tipo comum de tilemap:

  //essas flags são usadas para determinar algumas
  //informações sobre o tile
  TTileFlag = (tfObstaculo, tfPlataforma, tfAgua, tfEspinho, tfFogo);
 
  //aqui eu usei o "set" porque é mais conveniente.
  //mas você pode usar inteiros também.
  TTileFlags = set of TTileFlag;
 
  //Estrutura dos tiles. Varia muito de acordo com as
  //funções que você quer que seu mapa tenha. Este
  //é apenas um exemplo básico 
  TTile = record
    //o índice da imagem que será desenhada
    Imagem: Integer;
 
    //outras informações sobre o tile
    Flags: TTileFlags;
  end;
 
  //Estrutura do mapa de tiles
  TTileMap = record
    //Matriz bidimensional que indica a posição de
    //cada tile no mapa
    tiles: array of array of TTile;
 
    //O tileset que vamos usar para guardar as imagens
    //dos tiles.
    TileSet: TBitmap;
 
    //A largura e altura de cada tile armazenado no
    //tileset.
    Width, Height: Integer;
  end;

    Como vocês podem ver, as imagens não são armazenadas diretamente nos tiles. Isto acontece porque vários tiles diferentes costumam usar a mesma imagem, neste caso é melhor colocar todas as imagens em um outro lugar e usar apenas um índice para identificá-las. Para isso usamos os tilesets (conjunto de tiles).

- Tile sets -

    Os tilesets normalmente são um grande arquivo bitmap (ou outro formato de imagem) contendo as imagens. Como todos os tiles têm o mesmo tamanho não há problema em colocá-los todos juntos em uma única e grande imagem.

    Dê uma olhada nesta imagem:


Um tileset utilizado por RPGs

    Quando o programa precisa desenhar umtile, ele pega o índice da imagem, procura por esta imagem no tileset e copia a imagem do tileset para a tela.

    O código abaixo é apenas um exemplo de uma rotina de desenho de tilemaps.

  //percorre todas as linhas da matriz de tiles 
  //(tilemap)
  for Y:= to High(TileMap.tiles) do
  begin
    //percorre todas as colunas da linha Y da
    //matriz de tiles (tilemap)
    for X := to High(TileMap.Tiles[Y]) do
    begin
      //pega a distância do canto esquerdo no tileset
      //note que aqui as imagens usam um índice que
      //começa em 1
      Ix:= ((TileMap.Tiles[Y][X].Imagem - 1mod TILESET_COLS) * TileMap.Width;
 
      //pega a distância do canto direito do tileset
      Iy:= ((TileMap.Tiles[Y][X].Imagem div TILESET_COLS) - 1) * TileMap.Height;
 
      //variável com a área a ser copiada do tileset
      TileRect:= Rect(Ix, Iy, Ix + TileMap.Width, Iy + TileMap.Height);
 
      //variável com a área de destino na tela
      DestRect:= Rect(X * TileMap.Width, Y * TileMap.Height, (X+1) * TileMap.Width,
        (Y+1) * TileMap.Height);
 
      //copia do tileset para a tela...
      Canvas.CopyRect(DestRect, TileMap.TileSet.Canvas, TileRect);
    end;
  end;

    As coisas devem começar a fazer sentido agora. Quando o programa vai desenhar o mapa, ele percorre a matriz, pega a imagem correspondente a cada tile no tileset e copia para a tela. Mas imaginem um mapa de 100 x 100, ou 1000 x 1000, ou até maior! Seria muito demorado percorrer um mapa deste tamanho e seria desperdício de tempo e recursos, afinal, nós não precisamos percorrer um mapa inteiro de 100 x 100 tiles se em nossa tela só cabem 10 x 10 tiles.

    Então vamos otimizar um pouquinho o código, percorrendo apenas os tiles que estarão visíveis na tela.

  //calcula o número de tiles visíveis horizontalmente
  //Ceil faz com que o número seja arredondado para cima
  //1.3 vira 2, 7.1 vira 8, etc.
  TilesX:= Ceil(ClientWidth / TileMap.Width);
  //calcula o número de tiles visíveis verticalmente
  TilesY:= Ceil(ClientHeight / TileMap.Height);
 
  //ViewX e ViewY são os pontos de onde o mapa começará
  //a ser desenhado.
 
  //percorre as linhas visíveis da matriz de tiles
  //(tilemap)
  for Y:= ViewY to ViewX + TilesX do
  begin
    //percorre as colunas visíveis da linha Y da matriz
    //de tiles (tilemap)
    for X := ViewX to ViewY + TilesY do
    begin
      //pega a distância do canto esquerdo no tileset
      //note que aqui as imagens usam um índice que
      //começa em 1
      Ix:= ((TileMap.Tiles[Y][X].Imagem - 1mod TILESET_COLS) * TileMap.Width;
 
      //pega a distância do canto direito do tileset
      Iy:= ((TileMap.Tiles[Y][X].Imagem div TILESET_COLS) - 1) * TileMap.Height;
 
      //variável com a área a ser copiada do tileset
      TileRect:= Rect(Ix, Iy, Ix + TileMap.Width, Iy + TileMap.Height);
 
      //variável com a área de destino na tela
      DestRect:= Rect(X * TileMap.Width, Y * TileMap.Height, (X+1) * TileMap.Width,
        (Y+1) * TileMap.Height);
 
      //copia do tileset para a tela...
      Canvas.CopyRect(DestRect, TileMap.TileSet.Canvas, TileRect);
    end;
  end;

    Como vocês puderam ver, o código percorre apenas os tiles visíveis e os desenha na tela.

- Finalizando –

    É isso por hoje, abaixo eu deixo um pequeno código fonte em Delphi de com desenhar esses mapas usando a GDI do windows.

Download do código fonte

    Pretendo escrever outros artigos sobre outros tipos de mapas (como os isométricos) e aprofundar um pouco nos tilemaps, usando diversos layers (camadas).


Ações

Informações

Deixe um comentário