GameDev #8: Criando, desenhando e trabalhando com sprites no XNA

em 14/08/2010

No nosso último encontro aqui na coluna GameDev criamos o nosso primeiro projeto de jogo XNA no Visual C# Express Edition, analisamos c... (por Jones Oliveira em 14/08/2010, via Nintendo Blast)

No nosso último encontro aqui na coluna GameDev criamos o nosso primeiro projeto de jogo XNA no Visual C# Express Edition, analisamos cada arquivo desse projeto e trouxemos à prática cada um dos conceitos vistos nas colunas anteriores. Ainda conseguimos visualizar cada um dos principais métodos do jogo e para que cada um deles serve.

No encontro de hoje, continuaremos a trabalhar no projeto vazio criado anteriormente. Entenderemos o que são sprites, os criaremos e aprenderemos a trabalhar com eles no nosso jogo – desenhando e fazendo-os movimentar na tela.

Se você não acompanhou o nosso último encontro e não tiver o projeto vazio que foi criado naquela oportunidade, poderá fazer o download dele acessando o endereço http://bit.ly/c1eKHw

Lembrando que, para desenvolvermos o que é proposto nessa coluna, é necessário ter instalado em sua máquina o XNA Game Studio e o Microsoft Visual C# Express Edition, ambos gratuitos, que podem ser encontrados na sessão downloads do XNA Creators Club (http://creators.xna.com/en-US/downloads).

Vs_Express_logoXNA_logo

Entendendo os termos e os conceitos dos gráficos 2D

Como hoje iremos trabalhar com gráficos 2D no nosso projeto, acredito que seja importante conhecermos um pouco dos termos que são utilizados no meio e que também utilizaremos daqui em diante.

  • ash_spriteSprites: é uma imagem 2D que pode ser manipulada independentemente do restante dos gráficos que estão na tela do jogo. Geralmente é o personagem que você controla nos jogos de plataforma, como o Mario em Super Mario World. Porém, ao contrário das demais imagens utilizadas no jogo, possui características definidas em nível de programação, como velocidade, posição, altura e largura. É importante perceber que o computador sempre irá desenhar as imagens como retângulos no jogo e é muito comum nós desenharmos imagens com áreas transparentes para dar noção de desenho não retangular. Ainda fala-se em sprite animado para denominar um sprite que muda de forma a cada determinado intervalo de tempo.
  • Texturas: é uma imagem 2D que é carregada sobre um modelo 3D para criar a ilusão de gráficos bem detalhados.

textura

  • Billboard: é um tipo especial de textura utilizada para representar objetos complexos sem que haja a necessidade renderizar um modelo 3D completo. Sua ideia consiste em, aonde pudesse haver um objeto 3D, haverá um sprite 2D com uma imagem da textura do objeto sobre ele. Obviamente não é uma técnica que substitui um modelo 3D e, por isso, é utilizada somente quando se deseja manter a performance de renderização e processamento de uma cena cujos detalhes devem ser voltados a outras coisas.

Confira um exemplo de Billboard no endereço http://creators.xna.com/en-US/sample/billboard

  • smw_backgroundBackground: essa todos nós conhecemos – é a imagem que fica no plano de fundo nos jogos de plataforma 2D. Fala-se em 3 tipos de backgrounds: estáticos, animados e animados por paralaxe. Como o próprio nome diz, os estáticos não saem do canto e são background para uma tela apenas – Ninja Gaiden era desse jeito. Os backgrounds animados estão presentes nos jogos batizados de scrolling, como SMW. Já os backgrounds animados por paralaxe são formados por mais de um background, cada um deles se movendo a velocidades diferentes, e às vezes em sentidos opostos, dando a falsa impressão de tridimensionalidade e profundidade de cenário.

Não tem como fugir desses termos quando trabalhamos com jogos em 2D. No entanto, como iremos trabalhar apenas os conceitos de desenhar, movimentar e fazer colidir dois objetos no jogo, trabalharemos apenas o conceito de sprites.

Porém, antes de sairmos criando os nossos sprites e fazermos eles colidirem um com o outro, também é importante ter uma noção mínima de como funciona o sistema de coordenadas 2D no XNA, se não será impossível compreendermos o que estamos fazendo e aonde estamos fazendo.

 

2D e o sistema de coordenadas da tela

Lembra-se daquelas aulas chatas de geometria plana que você teve no seu ensino fundamental e até mesmo no ensino médio? Quem nunca quis esganar o professor por causa daqueles inúmeros planos cartesianos desenhados no caderno para calcular a distância entre dois pontos, área do triângulo, e tudo o mais a partir das coordenadas cartesianas, que atire o primeiro esquadro.

Pois bem, saiba que aquilo tudo não foi em vão – nós precisaremos daquele conceito aqui. Vamos apenas recapitular como funcionam as coordenadas 2D para que possamos compreender a maneira correta de posicionar os nossos objetos dentro do jogo.

Lá nas aulas de geometria nós utilizávamos o plano cartesiano comum para definirmos as figuras geométricas:

plano_cartesiano

Perceba que nesse plano a origem dos pontos se dá no canto inferior esquerdo e o valor de Y é crescente quando o seu eixo sobe, e decrescente quando desce.

Quando estamos trabalhando com computadores, no entanto, também estamos trabalhando com o sistema de coordenadas de tela, que é semelhante ao cartesiano:

coordenadas_tela

Analisando a figura, podemos perceber as três diferenças existentes entre esses dois sistemas. Primeiro, a origem dos eixos se dá na parte superior esquerda do plano. Segundo, o valor de Y é crescente quando seu eixo cresce para baixo e, por último, os valores de Y e X terão um valor máximo que não deverá ser extrapolado.

Ou seja, se você estiver trabalhando em uma tela cuja resolução seja 1024x768, o valor máximo que X poderá assumir é 1024 e Y é 768.

 

Desenhando um sprite no XNA

Agora sim, vamos começar a usar o XNA para desenhar um sprite na tela do nosso jogo. Para efeitos de demonstração, utilizaremos no encontro de hoje uma imagem simples que pode ser criada no próprio Microsoft Paint – ou você pode pegar qualquer imagem pronta por aí.

Aqui, criei uma imagem de 64x64 pixels, com fundo magenta e que atribui o nome de bola.bmp. Salvei-a em BMP para que eu pudesse esconder a área em magenta, deixá-la transparente. Se eu salvasse-a em JPG, isso não seria possível, pois esse formato não preserva o formato original das cores quando salvamos a imagem.

bola

Agora que temos a imagem criada, devemos ir até o Visual C# Express Edition para criar a classe para os nossos sprites. A classe será criada para agruparmos a imagem do sprite e associá-la a algumas propriedades – tais como tamanho, posição e velocidade. Por enquanto essa classe será bastante simples e iremos trabalhando-a ao longo do desenvolvimento dos nossos encontros.

Observação: como eu havia dito no encontro anterior, nós faremos uso de alguns termos que são comuns a quem trabalha com programação orientada a objetos e que podem ser totalmente novas para aqueles que nunca nem ouviram falar em programação. A ideia e conceito de classe é um desses termos e para que ninguém fique perdido, novamente sugiro a leitura de uma apostila de introdução a programação orientada a objetos e/ou artigos da wikipedia que abordem o assunto. Para entender um pouco mais sobre classes, acessem o link http://pt.wikipedia.org/wiki/Classe_(programação)

Para criar uma classe no projeto, clique com botão direito em cima do nome do projeto no Solution Explorer do Visual C# Express Edition e selecione a opção Add/New Item (Figura 1) e logo em seguida escolha Class e atribua um nome à sua classe – aqui eu atribui o nome classSprite.cs (Figura 2)

figura1
Figura 1

figura2
Figura2

 

Ao finalizarmos a criação da classe, o Visual C# nos levará automaticamente para ela e nos revelará uma estrutura básica que foi gerada automaticamente. Vamos trabalhar um pouco nessa estrutura – vamos apagar tudo o que está lá e adicionarmos as linhas que seguem abaixo no seu escopo:

image

Por enquanto a nossa classe não tem nada de especial, mas aqui cabe explicar um pouco as três propriedades que estamos utilizando:

  • Textura: armazenará a imagem do sprite usando a classe Texture2D do XNA. Essa classe nos fornece vários métodos que nos ajudarão a lidar com os sprites que utilizaremos.
  • Tamanho: armazenará o tamanho do sprite utilizando a classe Vector2 do XNA. Essa classe tem duas propriedades – X e Y – que serão utilizadas, respectivamente, para definir a largura e altura do sprite.
  • Posição: tal qual o tamanho, utilizará a classe Vector2 do XNA. Todavia, as propriedades X e Y armazenarão as coordenadas de tela para o posicionamento do sprite.

Agora que já criamos a imagem do nosso sprite e a classe dos sprites do nosso jogo, é hora de importar essa imagem para o Content Pipeline do projeto. Para fazer isso, basta clicar com o botão direito na pasta Content no Solution Explorer do projeto e selecionar Add/Existing Item (Figura 3). Na janela que abrir, procure pelo local em que você salvou a imagem do sprite e aperte em Add e voilá – a imagem do sprite estará lá na pasta Content do seu projeto (Figura 4)

figura3
Figura 3

figura4
Figura 4

Agora que já temos a imagem do sprite criada, temos a classe do sprite no projeto, e a imagem devidamente importada para o Content Pipeline, nos resta desenhá-la na tela. Para tanto, nós utilizaremos a SpriteBatch - uma classe do XNA que auxilia o desenho de sprites na tela – e a textura/imagem que acabamos de carregar – nesse caso, essa textura será carregada para dentro da classe classSprite e então a utilizaremos.

Existem diversos meios que nos permitem desenhar o sprite na tela. Porém, geralmente ou se lê a textura na classe classSprite e desenha no método Draw da classe Game1, ou se adiciona um método Draw dentro da própria classe classSprite que desenhará, a partir da classe, o sprite na tela. Vamos trabalhar dessa forma por enquanto.

Voltando à nossa classe classSprite, adicionemos o seguinte método após o método construtor da classe:

image

Aqui nós estamos usando o método Draw da classe SpriteBatch e estamos passando três argumentos para esse método – a textura, a posição e a cor. Essa é a maneira mais simples de desenhar algo na tela.

Caso queira conhecer outras formas de utilizar o método Draw, basta acessar o endereço http://bit.ly/cWHXPt

Agora precisamos ajustar a classe Game1. Ao acessarmos o arquivo, perceberemos que, no ato da criação do projeto, o Visual C# já criou um objeto spriteBatch no começo do arquivo. Vamos agora criar um objeto classSprite logo após a definição dos objetos graphics e spriteBatch. Teremos algo mais ou menos assim:

image

Como tinhamos comentado no nosso encontro anterior, é preciso carregar o conteúdo que utilizaremos no nosso jogo já no método LoadContent, que possui toda a inicialização gráfica do jogo. Então, vamos ao método LoadContent da classe Game1 e inicializemos o objeto mySprite1:

image

É aqui nessa parte que o método construtor da classe classSprite se mostra necessário – a partir da inicialização de mySprite1, já podemos definir que “aspecto” ele terá. Carregamos nele a textura que importamos há pouco tempo (bola.bmp), a posição inicial do nosso sprite (0, 0 – ou seja, o canto superior esquerdo da tela) e o tamanho do sprite (64, 64). Apesar das poucas linhas de código, até aqui já fizemos muitas coisas.

Uma dica de boa prática de programação é destruir tudo aquilo que foi criado por você após a finalização do jogo. Para quem acompanha a coluna, já deve saber que isso deverá ser feito no método UnloadContent. Adicionemos a seguinte linha a ele:

image

Finalmente, podemos ir ao método Draw da classe Game1 para desenhar o nosso sprite na tela:

image

Aqui, estamos simplesmente dizendo ao XNA que começaremos a desenhar algo na tela (spriteBatch.Begin). Logo em seguida, desenhamos o que desejamos (mySprite1.Draw) e finalmente dizemos ao XNA que tudo foi desenhado (spriteBatch.End). Fácil, não?

Uma dica importante aqui – como estamos trabalhando com uma textura que possui áreas de transparência (lembra para que serve o magenta do fundo da nossa bola?), precisamos dizer ao spriteBatch isso. Para tanto, basta modificar o spriteBatch.Begin para:

image

Estamos com o circo armado – se você seguiu todos os passos atentamente, ao dar F5 e mandar o “jogo” rodar, obterá a seguinte tela:

jogo1

Perfeito – antes tinhamos uma grande tela azul, agora temos uma grande tela azul com um ponto preto no canto superior esquerdo. Você pode brincar com o posicionamento da bola lá no método LoadContent, basta alterar os valores que lá se encontram – se divirta, mexa e procure entender como tudo está funcionando mexendo por si próprio antes de seguir para o próximo passo.

 

Movendo o sprite na tela

Como estamos trabalhando com gráficos 2D nesse projeto, é fácil pensar em como iremos fazer com que nosso sprite se mova na tela. Alguém já consegue visualizar como faremos isso?

Na verdade não tem nada de complicado aqui – é tudo bem lógico. Para movermos o nosso sprite para a direita, basta incrementar a coordenada X da sua posição. Para movê-lo para a esquerda, basta decrementar o valor da coordenada X. O mesmo vale para Y – se quiser que desça, incremente seu valor; se quiser que suba, decremente-o.

É importante não esquecer como funciona o sistema de coordenadas da tela do computador nesse momento.

Na estrutura básica do jogo, o XNA nos fornece um método específico para realizarmos cálculos durante a execução do jogo – é o Update. Aqui, poderemos adicionar as lógicas de cálculo de posicionamento, velocidade e muito mais.

Para que o sprite possa mover na tela, podemos adicionar uma única linha de código dentro do método Update:

image

Aperte F5 e observe que a bola se moverá para a direita infinitamente, até desaparecer da tela. Tá, funciona, mas está feio. Vamos fazer algo mais elegante e bonito. Vamos fazer com que o nosso sprite fique “preso” aos limites da tela do jogo.

Para isso, primeiramente apague a linha que digitou acima no método Update da classe Game1. Logo em seguida, vamos à classe classSprite para fazermos algumas pequenas modificações.

O que iremos fazer é declarar uma nova propriedade chamada “velocidade” que indicará a velocidade do sprite nas coordenadas X e Y da tela. Também definiremos uma propriedade que armazenará a resolução atual da tela do jogador para que possamos limitar a área de movimentação do nosso sprite.

Teremos, então, o seguinte:

Em classSprite.cs

image
Agora voltemos à classe Game1 para alterarmos a inicialização de mySprite1 no método LoadContent:

image

Como havíamos definido na classe do nosso sprite que agora armazenaríamos a resolução da tela, aqui na instanciação do objeto do sprite estamos capturando a largura e altura da tela do jogo com o comando graphics.PreferredBackBufferWidth e Height.

Em seguida, estamos definindo a velocidade do nosso sprite. Ao dizermos que ele terá velocidade 1 para horizontal e 1 para vertical, estamos dizendo ao XNA que o nosso objeto se moverá 1 pixel por atualização de tela, tanto na coordenada X, quanto Y – ou seja, ele se moverá na diagonal.

Nós já temos a velocidade do nosso sprite e já sabemos quais os limites de tela e, consequentemente, de movimentação do sprite – agora só está faltando um método que fará com que o sprite se mova dentro dos limites da tela.

Na classe classSprite, abaixo do método Draw, adicionemos um método, que chamarei aqui de “Mover”, que fará o sprite se mover dentro dos limites da tela.

image

Calma, não precisa se desesperar com o tamanho do código e com esses “if”. Basicamente, o que estamos fazendo aqui é verificar se o sprite já atingiu a posição limite da tela e, caso tenha atingido, terá sua velocidade invertida.

Ora, é bem lógico. Como havia explicado anteriormente, para mover o sprite para a direita, basta incrementar o valor de X – para mover para a esquerda, basta decrementar. Então, se o sprite alcançou o limite da direita da tela, o que temos que fazer? Decrementar o valor de sua velocidade no eixo X, pois é a velocidade que fará com que o sprite mude de posição com o passar do tempo – correto?

Dessa forma, só nos resta um passo para finalizarmos essa etapa – adicionar uma chamada do método “Mover” no Update de Game1:

image

Pronto, está feito. Ao apertar F5 você perceberá que o sprite se movimentará dentro dos limites da janela do jogo! Para consolidar o aprendizado, tente alterar a velocidade do sprite e busque por aí a fora uma forma de alterar o tamanho da tela do jogo. Não esqueça também de ler atentamente tudo o que foi feito para garantir que não houve nenhuma dúvida.

Hoje nós aprendemos a desenhar um sprite na tela, adicionamos movimentação a ele e ainda trabalhamos com os limites da tela. Também construímos a nossa primeira classe – a classSprite – para armazenar os sprites que utilizaremos no jogo daqui para frente. No nosso próximo encontro, continuaremos a utilizar o projeto de hoje e adicionaremos detectores de colisão para duas ou mais bolas no nosso jogo. Fique ligado, estude, tire suas dúvidas e até lá!

O projeto atualizado com o código desse encontro pode ser baixado no endereço http://bit.ly/9xrJJS


Escreve para o Nintendo Blast sob a licença Creative Commons BY-SA 3.0. Você pode usar e compartilhar este conteúdo desde que credite o autor e veículo original.
Este texto não representa a opinião do Nintendo Blast. Somos uma comunidade de gamers aberta às visões e experiências de cada autor. Escrevemos sob a licença Creative Commons BY-SA 3.0 - você pode usar e compartilhar este conteúdo desde que credite o autor e veículo original.