Desenvolvimento - C#

Carregando e desenhando sprites com SFML.NET

Neste artigo veremos como carregar sprites com a biblioteca SFML.NET e desenhá-los na tela.

por Joel Rodrigues



O uso de sprites é uma técnica bastante comum no desenvolvimento de games, pois facilita o armazenamento e carga de imagens. Esta técnica consiste da utilização de um único arquivo de imagem para o armazenamento de várias figuras, que juntas formam algum objeto com maior sentido. Por exemplo, um dos usos mais comuns para esse tipo de imagem é no armazenamento de várias figuras que juntas representam um personagem em movimento, onde cada parte contém o personagem em uma posição distinta. A imagem que contém os sprites é chamada de Sprite Sheet.

Em tempo de execução, esse único arquivo é carregado e “cortado” em partes menores a partir de coordenadas e dimensões conhecidas. A Figura 1 ilustra um exemplo de figura que contém os desenhos de várias cartas que serão carregadas individualmente em tempo de execução. As dimensões de cada carta estão disponíveis na Figura 2.

Imagem base dos sprites

Figura 1. Imagem base dos sprites

Dimensões de cada sprite

Figura 2. Dimensões de cada sprite

Podemos então partir para a parte prática e carregar os sprites dinamicamente. Para isso, utilizaremos a classe Sprite da SFML, que em um de seus construtores recebe um objeto Texture, que deve conter a imagem já carregada, e um IntRect contendo as coordenadas e dimensões do retângulo que será recortado.

A Listagem 1 mostra o código da função Main de uma aplicação console onde já foram adicionadas as devidas referências à SFML.NET, bem como os namespaces incluídos na classe Program com a diretiva using.

Após criar o objeto RenderWindow que representará a janela principal da aplicação, criamos uma Texture a partir de uma imagem contida na pasta Assets, que deve ser criada na solução.

Em seguida, como já sabemos quantas cartas existem na imagem (4 x 13 = 52 cartas), fazemos dois laços for para percorrê-la vertical e horizontalmente. No laço interno, criamos um objeto do tipo Sprite, passando para ele a textura que já foi criada e um IntRect contendo as coordenadas do ponto superior esquerdo de cada carta e suas dimensões (71 x 96px).

Logo após, apenas definimos uma posição na janela para desenhar a carta carregada (essa posição foi definida aleatoriamente para este exemplo) e utilizamos o método Draw da janela para desenhar o sprite que acabamos de carregar.

Listagem 1. Código do método Main

  static void Main(string[] args)
  {
      RenderWindow janela = new RenderWindow(new VideoMode(1024, 768), "Sprites SFML.NET");
      janela.Closed += Janela_Closed;
      janela.Clear(new Color(215, 225, 235));
   
      Texture texturaCartas = new Texture("Assets/cartas.png");
   
      for (int i = 0; i < 4; i++)
      {
          for (int j = 0; j < 13; j++)
          {
              Sprite s = new Sprite(texturaCartas, new IntRect(j * 73 + 1, i * 98 + 1, 71, 96));
              s.Position = new SFML.System.Vector2f(j * 12 + i * 220, j * 10);
              janela.Draw(s);
          }
      }
   
      janela.Display();
   
      while (janela.IsOpen)
      {
          janela.DispatchEvents();
      }
  }
  

Neste exemplo estamos desenhando as cartas apenas uma vez, por isso esse procedimento ficou fora do laço em que capturamos e tratamos os eventos da janela (laço while logo em seguida). Caso os sprites carregados representassem, por exemplo, um personagem em movimento, provavelmente utilizaríamos o loop principal para desenhá-lo sempre em sua posição atualizada.

Executando o código da listagem anterior, temos como resultado o que mostra a Figura 3, com as cartas carregadas e desenhadas individualmente, em posições que definimos apenas para este exemplo.

Janela com sprites renderizados

Figura 3. Janela com sprites renderizados

Por fim, simplesmente tratamos o evento Closed da janela em uma função que será responsável por fechá-la quando o usuário solicitar, interrompendo assim o loop principal. A Listagem 2 mostra a função criada para esta finalidade e atribuída ao evento Closed logo após a criação da RenderWindow.

Listagem 2. Evento Closed para tratar o fechamento da janela

  private static void Janela_Closed(object sender, EventArgs e)
  {
      ((RenderWindow)sender).Close();
  }
  

Note que o conhecimento da regra que define as posições dos sprites é fundamental para que possamos simplificar o procedimento de leitura dessas imagens. Caso não houvesse um padrão a ser seguido, provavelmente cada parte da imagem precisaria ser carregada individualmente. Isso geralmente é feito em imagens cujos sprites representam partes de um mapa, terrenos, objetos, etc., ou seja, figuras que não formam uma sequência lógica entre si, como o que ilustra a Figura 4.

Sprite

Figura 4. Sprite sheet de mapa

Se desejássemos, por exemplo, carregar algum desses sprites, precisaríamos saber a posição de cada um. Vejamos então como faríamos para carregar alguns desses sprites para montar parte de um certo mapa. Na Listagem 3 utilizamos novamente as classes Texture e Sprite para carregar a imagem e recortar apenas as partes que desejamos.

Listagem 3. Carregando sprite em posição específica

  static void Main(string[] args)
  {
      RenderWindow janela = new RenderWindow(new VideoMode(640, 280), "Sprites SFML.NET");
      janela.Closed += Janela_Closed;
      janela.Clear(new Color(255, 255, 255));
   
      Texture texturaMapa = new Texture("Assets/mapa.png");
   
      Sprite spriteBloco = new Sprite(texturaMapa, new IntRect(144, 0, 70, 40));
      Sprite spriteLava = new Sprite(texturaMapa, new IntRect(504, 0, 70, 40));
      Sprite spriteTocha = new Sprite(texturaMapa, new IntRect(92, 145, 28, 67));
              
      for (int i = 0; i < 10; i++)
      {
          spriteBloco.Position = new SFML.System.Vector2f(i * 70, 200);
          spriteLava.Position = new SFML.System.Vector2f(i * 70, 240);                
          janela.Draw(spriteBloco);
          janela.Draw(spriteLava);
   
          if(i % 2 == 0)
          {
              spriteTocha.Position = new SFML.System.Vector2f(i * 100, 130);
              janela.Draw(spriteTocha);
          }
      }            
   
      janela.Display();
   
      while (janela.IsOpen)
      {
          janela.DispatchEvents();
      }
  } 

Após carregar a textura e os sprites que desejamos, os desenhamos várias vezes na janela, simulando o que poderia ser, por exemplo, parte do mapa de um jogo 2D no estilo plataforma. O resultado é mostrado na Figura 5.

Sprite de mapa desenhado

Figura 5. Sprite de mapa desenhado

A biblioteca SFML.NET nos provê diversos recursos que tornam bastante simples e prático o desenvolvimento de jogos e aplicações multimídia. E a partir dos conceitos aqui apresentados, é possível montar cenários e personagens partindo de seus sprites, bastando conhecer as coordenadas e dimensões de cada parte e desenhá-las na tela da forma mais adequada.

Links

Página oficial da SFML
sfml-dev.org

Joel Rodrigues

Joel Rodrigues - Técnico em Informática - IFRN Cursando Bacharelado em Ciências e Tecnologia - UFRN Programador .NET/C# e Delphi há quase 3 anos, já tendo trabalhado com Webservices, WPF, Windows Phone 7 e ASP.NET, possui ainda conhecimentos em HTML, CSS e Javascript (JQuery).