Anda di halaman 1dari 38

Tutorial de XNA - Parte I

Criado em 22/10/2007 16:03 Modificado em 17/12/2007 10:19 Tpicos Xna Primeira parte do tutorial de XNA, mostrando como a estrutura de um jogo no XNA :-)

Para o melhor entendimento deste material necessrio que o leitor tenha conhecimentos prvios de C# e de orientao a objetos.

Como bom RPGista que sou, sempre gostei de jogos e ainda tenho a vontade de criar os meus, para tanto j tinha at iniciado meus estudos em Allegro1[1] , o que no foi assim to amigvel. Eu no tive tempo de concluir o meu primeiro experimento em allegro por causa da semana de provas na universidade, sem falar do retorno do grupo de estudos de .NET e do trabalho, resolvi ento retomar o estudo dos jogos unindo o til ao agradvel, o grupo de .NET e o Framework para desenvolvimento de jogos da Microsoft, o XNA. Comecei ento a pesquisar a respeito, primeiro as definies formais, as novidades, porque usar e por fim, a parte mais prazerosa que montar um jogo de exemplo. Ento, sem mais delongas, vamos a introduo ao XNA, espero que este texto seja til para outras pessoas. O que XNA? Bom, como todo framework, o XNA uma plataforma para desenvolvimento que visa facilitar em algum nvel o desenvolvimento de aplicaes, no caso os jogos. Por exemplo, ao desenvolver jogos, o programador precisa criar, dentre outras coisas, um loop principal, onde ficam as etapas bsicas que todo jogo deve ter, como carregamento de recursos, atualizao dos dados do mundo do jogo, desenho das cenas com as modificaes e descarga de recursos, isso o XNA j traz para voc. Sem falar que dependendo dos recursos usados o mesmo jogo produzido para PC pode rodar em outras plataformas (como o XBOX). Resumindo, como muitas iniciativas da Microsoft o XNA visa facilitar em muita coisa o desenvolvimento de jogos. Mas, deixemos de bla-bla-bla e vamos prtica. Voc pode encontrar maiores informaes no endereo: http://msdn2.microsoft.com/en-us/xna/default.aspx O software necessrio para reproduzir este tutorial: - Visual C# Express - Visual Game Studio 1.0 Voc pode baixar ambos do site da Microsoft. Comeando o projeto. Para iniciar o estudo, vamos criar um projeto do tipo Windows Game, quando aparecer o cdigo do template ns vamos apagar e recomear o nosso cdigo, assim vamos entender melhor o que cada parte do cdigo faz.

Para tanto v at File -> New Project e depois escolha Windows Game. No Solution Explorer (onde aparecem os componentes do projeto) vo aparecer dois arquivos Program.cs e Game1.cs, apague os dois, eles contem cdigo pronto para uma estrutura bsica do jogo, ns vamos criar o nosso cdigo. Agora adicione um arquivo em branco onde ns vamos comear a criar o nosso projeto, para tanto, clique com o boto direito dentro do Solution Explorer no nome do projeto e v at a opo Add e depois New Item e selecione Class, d um nome ao arquivo e pronto. A estrutura do jogo. Como eu j havia citado antes, a maioria dos jogos possui alguns elementos comuns em suas estruturas de controle, como um loop geral, um local para carregamento de recursos, um para atualizao do mundo, um para descarga e outro para desenho. No XNA a classe Game fornece essa estrutura. Vamos ao cdigo: Primeiro vamos adicionar os namespaces necessrios para a estrutura do jogo, no caso para poder usar a classe Game do XNA.
usingSystem; usingMicrosoft.Xna.Framework;

Agora vamos herdar da classe Game uma nova classe que vai conter a estrutura do nosso jogo:
//ocdigo da classe jogo, herdado da classe pai "Game" classjogo:Game { //construtor daclasse publicjogo() { } }

Repare que eu apenas criei a classe, herdando os membros da classe Game e depois criei um construtor que ainda est vazio. Agora, o cdigo da rotina principal da aplicao:
classnovo_jogo { staticpublicvoidMain()//mtodo principal daaplicao { jogoteste =newjogo(); teste.Run(); }

Com esse cdigo j ser criada uma janela para a nossa aplicao, mas, nada foi feito ainda. Vale ressaltar que agora usaremos a estrutura herdada da classe game na nossa aplicao, ela nos fornece um loop principal para o jogo, cuida da inicializao dos dispositivos e prov alguns mtodos que ns iremos sobrescrever para criar a lgica do nosso jogo.

Agora vamos aos mtodos da classe game que sero utilizados na nossa estrutura: Draw Chamado quando o jogo determina seu tempo especfico de renderizao. Iremos sobrescrever esse mtodo para criar nossas rotinas de desenho das imagens. Chamado depois que Game e GraphicsDevice so criados, mas, antes de LoadGraphicsContent. Chamado quando os recursos grficos precisam ser carregados, iremos sobrescrever esse mtodo para carregar nossas imagens.

Initialize

LoadGraphicsContent

UnloadGraphicsContent Chamado quando os recursos grficos precisam ser descarregados. Update Chamado quando a lgica do jogo precisa ser atualizada, aqui criaremos as rotinas que mantero nosso mundo atualizado.

Eu fiz uma traduo rasteira da documentao do XNA no Msdn, portanto perdoem os erros de traduo. Logicamente, fiz uma reescrita no texto explicativo para adapta-lo as necessidades deste documento. Configurando o ambiente. Agora precisamos configurar o nosso jogo de acordo com o ambiente onde ele est rodando, coisas como resoluo, cores, etc. Para fazer isso, vamos modificar o cdigo da nossa classe jogo, deixando-o da seguinte forma:
classjogo:Game { //permite aconfigurao do ambiente GraphicsDeviceManagerconfig_am; //construtor daclasse publicjogo() { config_am =newGraphicsDeviceManager(this); } }

Repare que depois disso at a cor de fundo da nossa aplicao j recebe um valor default. :-D Agora adicione os seguintes namespaces:
usingSystem; usingMicrosoft.Xna.Framework; usingMicrosoft.Xna.Framework.Content; usingMicrosoft.Xna.Framework.Graphics;

E finalmente podemos cuidar do cdigo para carregar e exibir a imagem. Primeiro, vamos adicionar um ContentManager que usado para gerenciar nossos recursos (os arquivos de texturas, por exemplo) e depois vamos instancia-lo no nosso construtor.
classjogo:Game { //permite aconfigurao do ambiente GraphicsDeviceManagerconfig_am; //gerenciadordos recursos ContentManagerrecursos; //construtor daclasse publicjogo() { config_am =newGraphicsDeviceManager(this); recursos=newContentManager(Services); } }

Agora, antes de carregar propriamente os recursos, vamos adicionar a imagem da textura ao nosso projeto, para tanto, criamos uma pasta no Solution Explorer com o nome imagens e depois adicionamos o arquivo contendo a textura aqui chamado de bola.jpg Agora clique com o boto direito na imagem e depois em Properties e procure pela propriedade Asset Name, ela ser usada para referenciar a imagem dentro do projeto. No caso desse documento, o Asset Name usado bola. A Classe Game do XNA chama automaticamente o mtodo LoadGraphicsContent para carregar os recursos, como estamos herdando dessa classe, vamos aproveitar esse mtodo para carregar as nossas texturas, para fazer isso, vamos sobrescreve-lo.
//mtodo paracarregar a textura. protectedoverridevoidLoadGraphicsContent(boolloadAllContent) { if(loadAllContent) { textura =recursos.Load<Texture2D>(@"imagens\bola"); } }

Nele testamos se os recursos j foram carregados atravs do parmetro LoadAllContent e depois carregamos o que for necessrio. Repare que usamos o objeto recursos que o nosso gerenciador de recursos e que foi instanciado no construtor da classe, nele chamamos o mtodo Load que informando o tipo de objeto que vamos carregar (no caso uma textura 2D) e por final passamos uma string com o asset name da imagem, como criamos uma pasta para a imagem, devemos preceder o nome com a pasta, o arroba usado para passar a string do jeito que est desprezando os caracteres especiais. Pronto, agora passaremos a imagem para um sprite e depois mostraremos ela na tela. A Classe Game do XNA tambm possui um mtodo DRAW que chamado automaticamente e que serve para incluir o cdigo onde os objetos so desenhados, iremos sobrescrever esse mtodo na nossa classe, para desenhar a nossa imagem. Mas, antes vamos adicionar um objeto SpriteBatch a nossa classe que vai servir para desenhar de fato a imagem e depois vamos instancia-lo, tambm vamos adicionar um objeto vector2 que chamaremos de posicao e que ser a posio do desenho na tela. O cdigo da nossa classe ficar assim:
//ocdigo da classe jogo, herdado da classe pai "Game" classjogo:Game { //permite aconfigurao do ambiente GraphicsDeviceManagerconfig_am; ContentManagerrecursos; //texturas

Texture2Dtextura; //sprite SpriteBatchsprite; //struct paraposio, inicializado no zero (x = 0 e y =0) Vector2posicao =Vector2.Zero; //construtor daclasse publicjogo() { config_am =newGraphicsDeviceManager(this); recursos=newContentManager(Services); } //mtodo paracarregar a textura. protectedoverridevoidLoadGraphicsContent(boolloadAllContent) { if(loadAllContent) { textura = recursos.Load<Texture2D>(@"imagens\bola"); sprite =newSpriteBatch(config_am.GraphicsDevice); } } protectedoverridevoidDraw(GameTimegameTime) { sprite.Begin(); sprite.Draw(textura, posicao,Color.White); sprite.End(); } }

Repare nas linhas em que instanciamos o objeto sprite e depois no mtodo Draw onde desenhamos efetivamente o sprite na tela. Pronto, por agora j o bastante, na prxima etapa do tutorial eu mostrarei como movimentar os objetos.

Tutorial de XNA - Parte II


Criado em 17/12/2007 10:00 Modificado em 21/12/2007 13:49 Tpicos Csharp Xna Continuao do Tutorial de XNA para inciantes (segunda parte) - republicado conforme pedidos :-)

Para o melhor entendimento deste material necessrio que o leitor tenha conhecimentos prvios de C# e de orientao a objetos e que tenha lido a primeira parte do tutorial. Dando seqncia ao meu pequeno tutorial, vamos revisar os conceitos estudados no primeiro texto para em seguida aprender algo novo. Na aula anterior, descobrimos o XNA, vimos o que , qual a sua idia e como a estrutura bsica de um jogo. Baseado nisso, criamos a nossa primeira janela do jogo e desenhamos uma imagem na tela. Agora, vamos ver como movimentar a imagem, testando o recebimento de teclas e encerrando o jogo caso o ESC seja pressionado. Revisando a estrutura bsica do jogo: - Primeiro carregamos os elementos necessrios ao jogo (mtodo LoadGraphicsContent) - Atualizamos os dados dos objetos do jogo (mtodo Update) - Desenhamos as imagens na tela. (mtodo Draw) Mas, de nada adianta desenhar a imagem no mesmo lugar, ou seja, ficar sem movimentao. A nossa nova lgica vai ser bem simples, na rotina de atualizao (Update) ns verificamos o status do teclado (teclas pressionadas) e tomamos alguma atitude de acordo com o nosso novo status. Para guardar o status do teclado, ns vamos usar a estrutura KeyboardState, ela vai nos informar as teclas que foram pressionadas pelo usurio. Para obter as teclas usaremos o mtodo GetState da classe Keyboard, para tanto, usamos a seguinte sintaxe:
KeyboardState teclado = Keyboard.GetState();

Aqui ns guardamos o status do teclado, falta apenas testar se alguma das teclas pressionadas corresponde a um de nossos comandos, para tanto, usaremos o mtodo IsKeyDown da classe KeyboardState. A enumerao Keys nos fornece a listagem das teclas para que voc possa testar o status de cada uma delas (caso seja necessrio). Nesse caso testaremos a tecla Right (seta para a direita).
teclado.IsKeyDown(Keys.Right)

O resultado desse comando do tipo booleano (true ou false) e informa se a tecla foi pressionada ou no. Para utilizar essas classes, precisamos incluir o namespace Microsoft.Xna.Framework.Input. Com esses recursos j podemos movimentar o sprite na tela, testando qual foi a tecla pressionada e incrementado as posies X e Y do vetor Posicao. Repare na rotina de desenho do nosso sprite:
sprite.Draw(textura, posicao, Color.White);

Posicao um objeto do tipo vector2 e possui dois atributos x e y do tipo float, que sero usados para representar as posies do nosso sprite na tela, o que faremos aqui , a cada pressionamento das teclas direcionais, modificaremos os valores dessas coordenadas.

Devemos lembrar que as coordenadas da tela iniciam com a posio zero (ponto (0,0)) no canto superior esquerdo da tela, ao contrrio do que estamos acostumados quando estudamos o plano cartesiano, ento nossa tela tem as coordenadas assim:

Ento, se incrementamos os valores de Y, na verdade, estamos fazendo com o que o Sprite se movimente para baixo, os limites para desenhar o sprite, so as resolues, ou seja, para os valores de X temos o tamanho horizontal da tela e para Y o tamanho vertical. Ento o que fazemos , na rotina Update, testamos as teclas pressionadas e atualizamos os valores de X e Y do vetor posio, quando a rotina Draw for chamada, o sprite ser desenhado no local correto, com as posies atualizadas. Por exemplo, para testar se o direcional para cima foi pressionado e movimentar o sprite para cima ns faramos:
if (teclado.IsKeyDown(Keys.Up)) { posicao.Y--; }

Podemos fazer o mesmo com as outras teclas que desejamos testar, assim, chegamos ao nosso novo cdigo:
using using using using using System; Microsoft.Xna.Framework; Microsoft.Xna.Framework.Content; Microsoft.Xna.Framework.Graphics; Microsoft.Xna.Framework.Input;

//executando o jogo namespace meu_jogo { class novo_jogo { static public void Main() //mtodo principal da aplicao

{ jogo teste = new jogo(); teste.Run(); } } } //o cdigo da classe jogo, herdado da classe pai "Game" class jogo : Game { //permite a configurao do ambiente GraphicsDeviceManager config_am; ContentManager recursos; //texturas Texture2D textura; //sprite SpriteBatch sprite; //struct para posio Vector2 posicao = Vector2.Zero; //construtor da classe public jogo() { config_am = new GraphicsDeviceManager(this); recursos = new ContentManager(Services); } //mtodo para carregar a textura. protected override void LoadGraphicsContent(bool loadAllContent) { if (loadAllContent) { textura = recursos.Load<Texture2D>(@"imagens\bola"); sprite = new SpriteBatch(config_am.GraphicsDevice); } } protected override void Update(GameTime gameTime) { //um objeto para guardar o status do teclado KeyboardState teclado = Keyboard.GetState(); //testando a qual tecla foi pressionada e //incrementando a coordenada correta if (teclado.IsKeyDown(Keys.Right)) { posicao.X++; //mover para a direita } else if (teclado.IsKeyDown(Keys.Left)) { posicao.X--; //mover para a esquerda } else if (teclado.IsKeyDown(Keys.Down)) { posicao.Y++; //mover para baixo } else if (teclado.IsKeyDown(Keys.Up)) { posicao.Y--; //mover para cima } else if (teclado.IsKeyDown(Keys.Escape))

{ this.Exit(); //sair da aplicao }

} protected override void Draw(GameTime gameTime) { config_am.GraphicsDevice.Clear(Color.Black); sprite.Begin(); sprite.Draw(textura, posicao, Color.White); sprite.End();

} }

Por hoje s, aprendemos como mover o sprite na tela e como verificar o pressionamento de teclas, espero que este texto seja til a vocs. At a prxima.

Tutorial de XNA - Parte III


Criado em 19/12/2007 13:44 Modificado em 20/12/2007 14:06 Tpicos Csharp Xna Continuao do meu tutorial de XNA para iniciantes - republicado :-)

Para o melhor entendimento deste material necessrio que o leitor tenha conhecimentos prvios de C# e de orientao a objetos e que tenha lido a primeira e a segunda parte do tutorial. At agora ns descobrimos como a estrutura de um jogo no XNA, como movimentamos os nossos desenhos e como recebemos as teclas pressionadas no teclado. Nesta parte do tutorial eu vou falar sobre um tpico muito importante dentro da rea de jogos, a coliso. Antes de falar da coliso usando o XNA propriamente dito, vou explicar a teoria por traz desse mtodo de coliso, ai sim, eu mostrarei um exemplo desse tipo de coliso usando o XNA. Mas, o que coliso? Dentro dos jogos, voc precisa muitas vezes testar se dois objetos esto colidindo, como no mundo real. Por exemplo, se um carro do seu jogo bateu na parede, se um dos jogadores foi atingido por uma bala, se a sua espada ultra-mega-hiper-poderosa atingiu o inimigo, ou simplesmente se sua bolinha atingiu o cho. Mas, lembre, nos jogos 2D todos esses objetos so imagens e para testar se eles colidiram, voc no pode testar apenas as suas coordenadas de desenho. Ai que entram as tcnicas de coliso. Um ponto importante pensar primeiro que para cada caso existe uma tcnica mais adequada, ento o que eu estou mostrando aqui, pode no ser adequado ao seu jogo simplesmente pelo modo como voc o implementou, ou ento porque torna jogo mais lento, geralmente no h uma regra, cada caso um caso.

Bounding Box (caixa delimitadora) A idia bastante simples, ns vamos associar a cada objeto da tela uma rea retangular, dada pelos limites horizontais e verticais da sua imagem. Da, testaremos se a interseco desses limites, resultando ou no na coliso. Ai voc diz: hein??!! e eu digo, ta bom vou explicar melhor :-) Repare na imagem abaixo, onde temos dois retngulos desenhados na tela:

Cada um deles ocupa uma rea da tela delimitada por suas linhas de contorno, que so dadas pelos limites horizontais e verticais, veja a imagem abaixo:

No caso do retngulo azul, ele determinado pelas coordenadas X1, X2, Y1 e Y2. Sendo as outras coordenadas pertencentes ao retngulo verde. Para testar a coliso basta verificar essas coordenadas. Portanto usaremos essas linhas para determinar se um retngulo colidiu com o outro. Vamos pegar o mesmo caso da figura anterior e estudar as coordenadas, na imagem anterior X1 e X2 so menores que X3 o que significa que o segundo retngulo est ao lado direito do primeiro. Caso X1 e X2 fossem maiores que X3 o retngulo verde estaria esquerda do azul. A mesma comparao pode ser feita com as coordenadas Y das imagens, como Y1 e Y2 so menores que Y3, ento o retngulo azul est acima do verde, em caso contrrio, estaria abaixo. Mas, para ns, o mais importante detectar a coliso e no a localizao. Essa coliso acontece quando as linhas que determinam nossos retngulos formam uma rea em comum aos dois retngulos. Por exemplo:

Nesse caso, a rea amarela formada pela interseco dos dois retngulos e nesse caso temos certeza de que os dois objetos colidiram. No exemplo temos X1 < X3 < X2 e Y1 < Y3 < Y2 o que indica uma coliso por baixo e do lado direito. Ento, para testar se o objeto colidiu por baixo e pelo lado direito, precisamos apenas checar se as coordenadas dele atendem a essa condio. Mas, isso no seria suficiente para ns que queremos uma coliso mais genrica, ento precisamos testar outros casos. Bom, no meu caso vou criar um mtodo muito simples que testa se existe uma interseco entre as caixas delimitadoras formadas pelas linhas do exemplo anterior, ele vai informar apenas se os objetos colidem ou no, no me preocupei com a localizao relativa dos objetos (acima, abaixo, etc). Ai vai o cdigo do mtodo testar_colisao que retorna true em caso de coliso e false em caso contrrio.
protected bool testar_colisao(Texture2D box1, Vector2 posi1, Texture2D box2, Vector2 posi2) { //por padro os objetos no colidem bool status = false; //coloque as posies em nomes mais fceis float x1 = posi1.X; float x2 = posi1.X + box1.Width; float x3 = posi2.X; float x4 = posi2.X + box2.Width; float y1 = posi1.Y; float y2 = posi1.Y + box1. Height; float y3 = posi2.Y; float y4 = posi2.Y + box2.Height; //teste os limites e veja se os objetos colidiram if ((((x3 <= x1) && (x1 <= x4)) || ((x3 <= x2) && (x2 <= x4))) && (((y3 <= y1) && (y1 <= y4)) || ((y3 <= y2) && (y2 <= y4)))) { //achei uma coliso!! status = true; } :-)

return status; //devolva o resultado }

Agora que j sabemos como testar a coliso, vamos ver um exemplo de cdigo que usa esse mtodo para testar a coliso. O nosso exemplo vai usar todo o cdigo dos tutoriais anteiores, s que no lugar de uma bola, teremos os dois retngulos que usei nos exemplos deste tutorial para explicar como funciona a coliso. Repare que para achar as coordenadas eu usei as propriedade Width (largura) e Heigth (altura)

das texturas. O software simples, temos dois retngulos, um fixo e outro que vai se mover de acordo com os as setas do teclado, quando esses dois retngulos colidirem, um deles ficar transparente. Inclui duas texturas, uma para cada retngulo:
//texturas Texture2D retanguloazul; Texture2D retanguloverde;

Depois inclumos os vetores de posio para cada um:


//vetor para posio Vector2 posicao_verde = Vector2.Zero; Vector2 posicao_azul; //vai ficar parado

Preciso de um lugar para guardar o resultado os testes de coliso:


//objeto para guardar o resultado dos testes de colisao public bool resultado_colid;

Alm disso, para deixar um dos retngulos transparentes, eu preciso definir uma cor e determinar o Alpha dela, usando um objeto do tipo color que recebe 4 argumentos, os ndices R (red), G (green), B (blue) e o A(alpha) que determina justamente os valores de transparncia. Esses valores variam de 0 a 255 e a mistura deles gera as cores no padro RGB. Quando voc aplica o valor 0 ao A, indica transparncia total e 255 indica sem transparncia.
//cor transparente //branco com transparencia Color my_color = new Color(255, 255, 255, 150);

Para saber mais sobre isso, leia o artigo de Andr Furtado no SharpGames: http://www.sharpgames.net/Artigos/Realizando+alpha+blending+-+ou+transparencia++em+texturas+e+primitivas+graficas+do+XNA.xna Agora, vamos ver como fica o cdigo:
using using using using using System; Microsoft.Xna.Framework; Microsoft.Xna.Framework.Content; Microsoft.Xna.Framework.Graphics; Microsoft.Xna.Framework.Input;

//executando o jogo namespace meu_jogo { class novo_jogo { static public void Main() //mtodo principal da aplicao { jogo teste = new jogo(); teste.Run(); } } }

//o cdigo da classe jogo, herdado da classe pai "Game" class jogo : Game { //permite a configurao do ambiente GraphicsDeviceManager config_am; ContentManager recursos; //texturas Texture2D retanguloazul; Texture2D retanguloverde; //sprite SpriteBatch s_azul; SpriteBatch s_verde; //vetor para posio Vector2 posicao_verde = Vector2.Zero; Vector2 posicao_azul; //vai ficar parado //objeto para guardar o resultado dos testes de colisao public bool resultado_colid; //branco com transparencia Color my_color = new Color(255, 255, 255, 150); //construtor da classe public jogo() { config_am = new GraphicsDeviceManager(this); recursos = new ContentManager(Services); } //inicializa outros itens que no requerem a inicalizao do dispositivo grfico protected override void Initialize() { posicao_azul.X = 200; posicao_azul.Y = 200; base.Initialize(); } //mtodo para carregar a textura. protected override void LoadGraphicsContent(bool loadAllContent) { if (loadAllContent) { retanguloazul = recursos.Load<Texture2D>(@"imagens\rect_azul"); retanguloverde = recursos.Load<Texture2D>(@"imagens\rect_verde"); s_azul = new SpriteBatch(config_am.GraphicsDevice); s_verde = new SpriteBatch(config_am.GraphicsDevice); //preciso inicializar as texturas antes de usar a coliso resultado_colid = testar_colisao(retanguloazul, posicao_azul, retanguloverde, posicao_verde); } } protected override void Update(GameTime gameTime) { //um objeto para guardar o status do teclado KeyboardState teclado = Keyboard.GetState(); //veja se algum objeto colidiu resultado_colid = testar_colisao(retanguloazul, posicao_azul, retanguloverde, posicao_verde);

//testando a qual tecla foi pressionada e //incrementando a coordenada correta if (teclado.IsKeyDown(Keys.Right)) { posicao_verde.X++; //mover para a direita } else if (teclado.IsKeyDown(Keys.Left)) { posicao_verde.X--; //mover para a esquerda } else if (teclado.IsKeyDown(Keys.Down)) { posicao_verde.Y++; //mover para baixo } else if (teclado.IsKeyDown(Keys.Up)) { posicao_verde.Y--; //mover para cima } else if (teclado.IsKeyDown(Keys.Escape)) { this.Exit(); //sair da aplicao } } protected override void Draw(GameTime gameTime) { config_am.GraphicsDevice.Clear(Color.Black); //desenhe o retangulo azul em uma posico fixa e sempre com a mesma cor. s_azul.Begin(); s_azul.Draw(retanguloazul, posicao_azul, Color.White); s_azul.End(); if (resultado_colid) //se colidiu, desenhe com transparencia { s_azul.Begin(); s_azul.Draw(retanguloverde, posicao_verde, my_color); s_azul.End(); } else //em caso contrrio desenhe normal { s_azul.Begin(); s_azul.Draw(retanguloverde, posicao_verde, Color.White); s_azul.End(); } } //testa a colisao entre dois objetos //recebe como argumentos as texturas e os vetores de posies de cada objeto protected bool testar_colisao(Texture2D box1, Vector2 posi1, Texture2D box2, Vector2 posi2) { //por padro os objetos no colidem bool status = false; //coloque as posies em nomes mais fceis float x1 = posi1.X; float x2 = posi1.X + box1.Width; float x3 = posi2.X; float x4 = posi2.X + box2.Width; :-)

float y1 = posi1.Y; float y2 = posi1.Y + box1.Height; float y3 = posi2.Y; float y4 = posi2.Y + box2.Height; //teste os limites e veja se os objetos colidiram if ((((x3 <= x1) && (x1 <= x4)) || ((x3 <= x2) && (x2 <= x4))) && (((y3 <= y1) && (y1 <= y4)) || ((y3 <= y2) && (y2 <= y4)))) { //achei uma coliso!! status = true; } return status; //devolva o resultado } }

Agora vamos aos pontos fortes e fracos desse mtodo de coliso, primeiro os pontos fortes: - Fcil de implementar para objetos que estejam alinhados aos eixos X e Y. - Tem bom desempenho para objetos alinhados com os eixos, se comparado a outros mtodos. Depois os pontos fracos: - No atende a maioria dos casos, visto que os objetos nem sempre so retangulares. - Em objetos de formas no retangulares, provoca falsas colises visto que as texturas nem sempre preenchem toda a rea das caixas delimitadoras. Um exemplo simples, onde as texturas provocariam falsas colises, seria o mesmo cdigo, usando crculos no lugar de retngulos. Agora vou dar umas dicas de utilizao desse mtodo nos seus jogos: - Se for usar esse mtodo, desenhe os objetos de forma que eles ocupem o mximo da rea da imagem, isso vai reduzir os espaos em branco na imagem e por tabela reduz os erros nas colises. - Divida as imagens em imagens menores e anime via software, na hora de testar a coliso, voc testa as partes individualmente, isso melhora a aproximao e diminui muito o erro nas colises por bounding box. Por exemplo, se voc tem um personagem do tipo medieval, um guerreiro com uma espada e quer testar a coliso do sprite de ataque do guerreiro com um monstro, voc tem duas opes, veja o exemplo abaixo. A primeira desenhar o boneco parado e em um outro sprite, desenhar o ataque.

Se voc fizer isso, vai testar toda a rea do sprite de ataque e isso inclui as reas abaixo e acima da espada que no devem provocar coliso. Isso geraria falsos ataques e seu boneco seria invencvel XD Outra idia, mais realista, criar trs sprites, um parado, outro de ataque e em separado, criar o sprite da espada que ser desenhado na posio do brao do seu guerreiro. Quando for necessrio testar a coliso, voc testa apenas o sprite da espada e como a espada pequena, a aproximao da coliso boa.

Quando voc for desenhar os objetos, mande desenhar a espada no local do brao e depois teste a coliso apenas na espada, repare que o sprite da espada sozinho ocupa quase toda a rea da imagem (pontilhado vermelho), melhorando bastante a aproximao da coliso.

Agora, j sabemos como criar a estrutura bsica de um jogo me XNA, sabemos desenhar na tela, mover, usar transparncia nos nossos sprites e por fim aprendemos um mtodo de coliso. Com o que mostrei nas trs partes desse tutorial, voc j pode criar diversos joguinhos, espero que a leitura tenha valido a pena, por hoje s e at o prximo tutorial ;-)

Tutorial de XNA - Parte IV


Criado em 02/01/2008 13:36 Modificado em 02/01/2008 14:24 Tpicos Csharp Xna Xna+Game+Studio Continuao do tutorial de XNA para iniciantes, aborda desenho de imagens com reas transparentes e coliso por pixel.

At agora, ns descobrimos como a estrutura de um jogo no XNA, como movimentamos os nossos desenhos, como recebemos as teclas pressionadas no teclado e como funciona um mtodo de coliso. Porm, o mtodo de coliso das caixas delimitadoras no dos melhores, por causa das falhas que ele provoca quando as imagens no preenchem todo o espao retangular, assim, vamos tratar de um outro mtodo de coliso, a coliso por pixel.
Para o melhor entendimento deste material necessrio que o leitor tenha conhecimentos prvios de C#, de orientao a objetos e que tenha lido as outras 3 partes desse tutorial.

Mas, antes que algum grite: Espere ai!! O que um pixel?? Como assim bitmap??, eu farei uma pequena explanao sobre os tipos de imagens e suas peculiaridades, assim o leitor vai entender melhor o mtodo de coliso por pixels e a teoria por traz dele. Atualmente dois tipos de imagens so usados com mais freqncia nas aplicaes, as imagens de bitmap e as imagens vetoriais. No pargrafos abaixo, falarei sobre as duas, mas, s utilizaremos um tipo nesse tutorial, os bitmaps. O que so imagens de bitmap? Pense em uma tabela enorme, com umas 200 linhas e 200 colunas, agora em cada quadrinho da tabela voc coloca um ponto com uma cor diferente, dependendo da disposio das cores dentro da sua tabela, voc pode at formar desenhos, essa a idia dos bitmaps. Um bitmap basicamente uma matriz de pontos coloridos onde cada ponto da matriz chamado de pixel. Podemos perceber isso se aumentarmos o zoom na visualizao de uma foto, voc perceber os quadrados que formam a imagem, inclusive ver que as curvas ficam serrilhadas, j que tambm so formadas por pontos. Veja o exemplo na foto abaixo:

Esse tipo de imagem usado em diversas aplicaes, como o Paint2[1], por permitir a criao de efeitos mais realistas, porm, mais pesados. Um dos problemas que acontece com mais freqncia quando se trabalha com esse tipo de imagem, ocorre quando o usurio tenta redimensionar a imagem, principalmente quando ele tenta aument-la.

Como as imagens de bitmap so formadas por pontos, quando se diminui a imagem apenas retiramos pontos, tentando manter o padro, com isso perdemos informao e por conseqncia perdemos qualidade na imagem, no entanto, a visualizao no to afetada. Um problema maior acontece quando tentamos aumentar uma imagem, o software vai tentar manter o padro da imagem inserindo pontos com as cores mais prximas dos j existentes, isso causa um serrilhamento e o resultado na maioria das vezes no satisfatrio. O que so imagens vetoriais? Bom, vocs j viram os problemas encontrados nas imagens de bitmap, agora imaginem uma grfica, onde as imagens so redimensionadas vrias vezes para reproduzir a mesma imagem em vrios tipos de documentos e onde geralmente se precisa do melhor resultado possvel na apresentao de um trabalho. Lgico que se fossem usados somente bitmaps nesse tipo de ambiente, diversos problemas iriam surgir, por isso se usa um outro tipo de imagem, as imagens vetoriais. As imagens vetoriais so formadas por equaes matemticas que formam os objetos desenhados, quando algum tenta redimensionar uma imagem dessas, os pontos so recalculados e a imagem refeita sem perder qualidade. Esse tipo de imagem ideal para desenhos que so redimensionados diversas vezes, como no caso da grfica. Um software bastante conhecido que utiliza imagens vetoriais o Corel Draw. Nos jogos 2D, em quase toda a sua totalidade, so usadas imagens de bitmap e agora que j sabemos o bsico sobre os tipos de imagem j podemos falar sobre as colises por pixel. Como funciona a coliso por pixel? A idia bem simples, se duas imagens esto colidindo, as suas reas retangulares possuem uma interseo, uma rea comum formada por suas caixas delimitadoras (lembre dos conceitos vistos na outra parte do tutorial sobre coliso por bounding box), como as imagens usadas so duas imagens de bitmap essa interseo, na verdade, formada por duas reas sobrepostas e essas reas tambm possuem seus respectivos pixels. Veja o exemplo:

Aqui temos duas imagens, um cavaleiro desferindo um golpe e um drago, as imagens esto sobrepostas de modo a formar uma rea comum, destacada em amarelo. Se estivssemos usando o modo de coliso por Bounding Box, o drago j teria sido atingido e isso seria mentira, pois o cavaleiro est a uma certa distncia do drago, mas, como a rea da imagem retangular, a coliso seria positiva, ou seja, se a espada do cavaleiro no emitir nenhuma aura mgica que ataque a distncia, estaramos apelando e o pobre drago sofreria um dano injusto. :-) No nosso caso, a espada do cavaleiro desfere apenas golpes fsicos e precisamos testar se ele acertou o drago ou no, ento, o mtodo que j conhecemos no adequado e precisamos usar colises por pixel. Vejamos mais alguns exemplos sobre colises destacando reas comuns em algumas imagens, a partir dessa rea que vamos descobrir se existe coliso ou no, repare que se no existir rea comum, a coliso j est descartada. Considere a rea branca dentro dos retngulos pontilhados como transparente.

Repare que nos trs exemplos, se estivssemos usando o mtodo de coliso anterior (bounding box) os trs casos resultariam em uma coliso positiva. Vale ressaltar que os desenhos so muito simples. Nas imagens que possuem uma rea transparente algumas reas da imagem no so desenhadas na tela. Essas reas possuem pixels e nesses pixels as cores armazenadas possuem o valor Alpha (responsvel pela transparncia) com o valor 0 (zero), esse pequeno detalhe a chave da coliso por pixel. Basta testar as cores dos pixels das duas reas dessa interseco, se uma das cores for transparente ento no h nada desenhado ali e por tabela, no h coliso, a coliso s vai existir se os pixels presentes na interseo no forem preenchidos, em ambas as imagens, com a cor de transparncia. Pronto, agora j sabemos como funciona a coliso por pixels, agora, veremos como implementar esse mtodo no seu jogo. A implementao. Aqui, vou comentar somente sobre os itens que no conhecemos ainda, se voc no leu os outros tutoriais, talvez seja uma boa hora para procurar neles algum item que voc no conhece. Depois de fazer os comentrios eu vou colocar o cdigo por partes (com mais comentrios) e depois colocarei o cdigo completo, sero poucas modificaes e faremos poucas continhas, o mais importante a lgica usada para resolver o problema. Antes vamos cuidar de um detalhe nas imagens, at agora, todos os nossos desenhos ocupavam toda a rea da imagem e nunca nos preocupamos em desenhar uma rea da imagem transparente, sempre desenhvamos da mesma cor do fundo. Agora o nosso cavaleiro medieval no ocupa toda a rea, inclusive esse um dos motivos para usarmos coliso por pixel, e para desenhar uma rea da imagem transparente precisaremos usar um formato de imagem que suporte transparncia, um desses formatos o PNG. Nesse tipo de imagem so salvos no arquivo informaes extras que vo dizer as aplicaes quais reas da imagem so transparentes. Um software gratuito que voc pode usar para desenhar as imagens, com suporte a arquivos no formato PNG, o Paint.NET que pode ser baixado nesse endereo: http://baixaki.ig.com.br/download/PaintNET.htm Uma imagem com fundo branco no o mesmo que uma imagem com o fundo transparente, veja a diferena nesse exemplo:

O fundo quadriculado na imagem representa as rea transparente e essa parte da imagem no ser mostrada na tela quando estivermos desenhando as nossas imagens no jogo, j as imagens com fundo branco, tero a cor branca desenhada na tela j que pixels brancos tambm so pixels :-) Voc pode encontrar um texto explicando os formatos de imagem suportados pelo XNA no blog da equipe: http://blogs.msdn.com/xna/archive/2006/08/29/730168.aspx Vamos relembrar a nossa idia geral, achar a interseo das imagens (isso ns j sabemos fazer), descobrir quais cores esto armazenadas naquela rea de interseo (ainda vamos descobrir como) e depois testar se as cores guardadas naqueles locais so transparentes ou no. Manterei muita coisa do exemplo anterior, mudando s o que for necessrio:
//texturas Texture2D TexCavaleiro; Texture2D TexDragao; //sprites SpriteBatch SpCavaleiro; SpriteBatch SpDragao; //vetores para as posies Vector2 posiCavaleiro = Vector2.Zero; Vector2 posiDragao = Vector2.Zero; //vai ficar parado //objeto para guardar o resultado dos testes de colisao bool resultado_colid = false; //cor semi-transparente Color my_color = new Color(255, 255, 255, 150); //branco com transparencia!!

Esses itens j so conhecidos, se voc tem alguma dvida na utilizao desses itens consulte as outras partes do tutorial. Tambm precisamos de lugar para guardar as cores que esto presentes nas nossas texturas, para isso vamos usar um Array do tipo Color:
//array com as cores das texturas Color[] coresDrag; Color[] coresCav;

No exemplo anterior, o nosso cavaleiro teria velocidade 1, ou seja, ele seria movimentado de pixel em pixel incrementando suas coordenadas de 1 em 1, nesse exemplo eu vou incluir um item que vai controlar sua velocidade, que inicialmente ser 5:
//velocidade do cavaleiro int velocidade = 5;

Agora vamos s inicializaes, nosso drago ficar sempre parado na posio 200x200:
//inicializa outros itens que no requerem a inicalizao do dispositivo grfico protected override void Initialize() { //drago na posio (200,200), cuidado!! posiDragao.X = 200; posiDragao.Y = 200; base.Initialize();

Depois carregamos nossas texturas e aproveitamos para pegar as cores das texturas e armazenar nos nossos arrays de cores:
//mtodo para carregar as texturas. protected override void LoadGraphicsContent(bool loadAllContent) { if (loadAllContent) { //carregando texturas TexCavaleiro = recursos.Load<Texture2D>(@"imagens\cavaleiro"); TexDragao = recursos.Load<Texture2D>(@"imagens\dragao"); SpCavaleiro = new SpriteBatch(config_am.GraphicsDevice); SpDragao = new SpriteBatch(config_am.GraphicsDevice); //determinando o tamanho do meu array coresCav = new Color[TexCavaleiro.Width * TexCavaleiro.Height]; coresDrag = new Color[TexDragao.Width * TexDragao.Height]; //armazenando as cores das texturas em um array TexCavaleiro.GetData(coresCav); TexDragao.GetData(coresDrag); } }

Repare que o array de cores criado com o tamanho de cores que tiver na textura, por exemplo, se minha textura do cavaleiro tiver 50x50 (pixels), ento o meu array de cores ter de ter um tamanho de 2500 cores, ou seja, a altura vezes a largura da imagem (50x50).
//determinando o tamanho do meu array coresCav = new Color[TexCavaleiro.Width * TexCavaleiro.Height];

Agora que eu j sei o tamanho, preciso preencher o array de cores com as cores da textura usando o mtodo GetData da classe Texture2D que copia as cores da textura no array.
//armazenando as cores das texturas em um array TexCavaleiro.GetData(coresCav);

Um detalhe importante e um pouco bvio que eu s posso usar esse mtodo depois de carregar a textura, caso contrrio eu no teria o que colocar no array. Depois que carregarmos as texturas e tivermos as cores delas em um array, podemos tratar do mtodo que ir controlar a atualizao do nosso mundo, o mtodo UpDate, nele iremos testar a coliso e usar a nossa varivel que controla a velocidade:
protected override void Update(GameTime gameTime) { //um objeto para guardar o status do teclado KeyboardState teclado = Keyboard.GetState(); //os retangulos que contem as coordenadas precisam ser //atualizados a cada loop, por isso esto aqui. Rectangle retanguloCav = new Rectangle((int)posiCavaleiro.X, (int)posiCavaleiro.Y, TexCavaleiro.Width, TexCavaleiro.Height); Rectangle retanguloDrag = new Rectangle((int)posiDragao.X, (int)posiDragao.Y, TexDragao.Width, TexDragao.Height); //veja se algum objeto colidiu

resultado_colid = testar_colisao_pixel(retanguloCav, retanguloDrag, coresCav, coresDrag); //testando a qual tecla foi pressionada e //incrementando a coordenada correta if (teclado.IsKeyDown(Keys.Right)) { posiCavaleiro.X += velocidade; //mover } else if (teclado.IsKeyDown(Keys.Left)) { posiCavaleiro.X -= velocidade; //mover } else if (teclado.IsKeyDown(Keys.Down)) { posiCavaleiro.Y += velocidade; //mover } else if (teclado.IsKeyDown(Keys.Up)) { posiCavaleiro.Y -= velocidade; //mover } else if (teclado.IsKeyDown(Keys.Escape)) { this.Exit(); //sair da aplicao } }

para a direita

para a esquerda

para baixo

para cima

O mtodo UpDate funciona da seguinte forma, primeiro precisamos checar o status do teclado, depois instanciamos dois Retangulos, eles iro representar as coordenadas das imagens e sero usados na nossa rotina de coliso, para criar um retngulo basta fornecer dois pontos, um com as coordenadas iniciais e outro com as coordenadas finais, no nosso caso a posio da imagem e as coordenadas de final da imagem (baseadas no tamanho dela), veja o exemplo:
Rectangle retanguloCav = new Rectangle((int)posiCavaleiro.X, (int)posiCavaleiro.Y, TexCavaleiro.Width, TexCavaleiro.Height);

Esse retngulo ter o mesmo tamanho que a imagem e servir para encontrarmos a interseo entre as duas imagens. Lembre que a cada movimentao as coordenadas mudam e por isso que temos de criar o retngulo a medida que precisamos testar novas colises. Agora que j temos as coordenadas, podemos testar se houve coliso, mais a frente eu vou detalhar o mtodo de coliso.
//veja se algum objeto colidiu resultado_colid = testar_colisao_pixel(retanguloCav, retanguloDrag, coresCav, coresDrag);

Depois testamos qual a tecla que foi pressionada e realizamos a ao adequada, aqui eu testarei se a tecla pressionada foi uma das setas direcionais e incremento a posio do personagem usando a nossa varivel velocidade, por exemplo:
//testando a qual tecla foi pressionada e //incrementando a coordenada correta if (teclado.IsKeyDown(Keys.Right)) { posiCavaleiro.X += velocidade; //mover para a direita }

Agora o nosso exemplo est praticamente pronto, s falta explicar o mtodo que testa a coliso, ele receber como argumentos, as cores das texturas e as coordenadas (atravs dos retngulos):

static bool testar_colisao_pixel(Rectangle retanguloA, Rectangle retanguloB, Color[] coresA, Color[] coresB) Depois ns vamos encontrar a interseo das imagens usando as suas coordenadas: //encontrando as coordenadas da interseo das imagens int topo = Math.Max(retanguloA.Top, retanguloB.Top); int inferior = Math.Min(retanguloA.Bottom, retanguloB.Bottom); int esquerda = Math.Max(retanguloA.Left, retanguloB.Left); int direita = Math.Min(retanguloA.Right, retanguloB.Right);

Agora, baseado nessas coordenadas da interseo, vamos percorrer os arrays de cores para testar se houve coliso, a cada loop, testamos as cores das imagens para verificar se ambas so diferentes da transparncia, o que caracterizaria a coliso, como o array s tem uma dimenso, precisamos fazer umas continhas para achar a cor correspondente a rea de interseo das imagens e depois testar cada cor dessa rea:
for (int y = topo; y < inferior; y++) { //enquanto existirem colunas na interseo, continue procurando pontos for (int x = esquerda; x < direita; x++) { //o array de cores s tem uma dimenso, calcule as coordenadas e //retorne a cor daquela posio Color corA = coresA[(y - retanguloA.Top) * retanguloA.Width + (x - retanguloA.Left)]; //ache as coordenadas para o outro objeto Color corB = coresB[(y - retanguloB.Top) * retanguloB.Width + (x - retanguloB.Left)]; //agora que j achou as cores, teste se alguma delas transparente //se nenhuma das cores for transparente, colidiu! if (corA.A != 0 && corB.A != 0) { //encontrou coliso return true; } } }

Agora, j podemos ir para o cdigo inteiro:


#region referencias using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; #endregion //executando o jogo namespace meu_jogo { class novo_jogo { static public void Main() //mtodo principal da aplicao { jogo teste = new jogo(); teste.Run(); } } }

//o cdigo da classe jogo, herdado da classe pai "Game" class jogo : Game { //permite a configurao do ambiente GraphicsDeviceManager config_am; ContentManager recursos; //texturas Texture2D TexCavaleiro; Texture2D TexDragao; //sprites SpriteBatch SpCavaleiro; SpriteBatch SpDragao; //vetores para as posies Vector2 posiCavaleiro = Vector2.Zero; Vector2 posiDragao = Vector2.Zero; //vai ficar parado //array com as cores das texturas Color[] coresDrag; Color[] coresCav; //velocidade do cavaleiro int velocidade = 5; //objeto para guardar o resultado dos testes de colisao public bool resultado_colid = false; //cor semi-transparente Color my_color = new Color(255, 255, 255, 150); //branco com transparencia!! //construtor da classe public jogo() { config_am = new GraphicsDeviceManager(this); recursos = new ContentManager(Services); } //inicializa outros itens que no requerem a inicalizao do //dispositivo grfico protected override void Initialize() { //drago na posio (200,200), cuidado!! posiDragao.X = 200; posiDragao.Y = 200; base.Initialize(); } //mtodo para carregar as texturas. protected override void LoadGraphicsContent(bool loadAllContent) { if (loadAllContent) { //carregando texturas TexCavaleiro = recursos.Load<Texture2D>(@"imagens\cavaleiro"); TexDragao = recursos.Load<Texture2D>(@"imagens\dragao"); SpCavaleiro = new SpriteBatch(config_am.GraphicsDevice); SpDragao = new SpriteBatch(config_am.GraphicsDevice); //determinando o tamanho do meu array coresCav = new Color[TexCavaleiro.Width * TexCavaleiro.Height];

coresDrag = new Color[TexDragao.Width * TexDragao.Height]; //armazenando as cores das texturas em um array TexCavaleiro.GetData(coresCav); TexDragao.GetData(coresDrag); } } protected override void Update(GameTime gameTime) { //um objeto para guardar o status do teclado KeyboardState teclado = Keyboard.GetState(); //os retangulos que contem as coordenadas precisam ser //atualizados a cada loop, por isso esto aqui. Rectangle retanguloCav = new Rectangle((int)posiCavaleiro.X, (int)posiCavaleiro.Y, TexCavaleiro.Width, TexCavaleiro.Height); Rectangle retanguloDrag = new Rectangle((int)posiDragao.X, (int)posiDragao.Y, TexDragao.Width, TexDragao.Height); //veja se algum objeto colidiu resultado_colid = testar_colisao_pixel(retanguloCav, retanguloDrag, coresCav, coresDrag); //testando a qual tecla foi pressionada e //incrementando a coordenada correta if (teclado.IsKeyDown(Keys.Right)) { posiCavaleiro.X += velocidade; //mover } else if (teclado.IsKeyDown(Keys.Left)) { posiCavaleiro.X -= velocidade; //mover } else if (teclado.IsKeyDown(Keys.Down)) { posiCavaleiro.Y += velocidade; //mover } else if (teclado.IsKeyDown(Keys.Up)) { posiCavaleiro.Y -= velocidade; //mover } else if (teclado.IsKeyDown(Keys.Escape)) { this.Exit(); //sair da aplicao } } protected override void Draw(GameTime gameTime) { config_am.GraphicsDevice.Clear(Color.Black);

para a direita

para a esquerda

para baixo

para cima

//desenhe o retangulo azul em uma posico fixa e sempre //com a mesma cor. SpDragao.Begin(); SpDragao.Draw(TexDragao, posiDragao, Color.White); SpDragao.End(); if (resultado_colid) //se colidiu, desenhe com transparencia { SpCavaleiro.Begin(); SpCavaleiro.Draw(TexCavaleiro, posiCavaleiro, my_color); SpCavaleiro.End();

} else //em caso contrrio desenhe normal { SpCavaleiro.Begin(); SpCavaleiro.Draw(TexCavaleiro, posiCavaleiro, Color.White); SpCavaleiro.End(); } } //testa a colisao por pixel entre dois objetos //recebe como argumentos dois retangulos que formam a interseo //das imagens e os arrays de cores das imagens static bool testar_colisao_pixel(Rectangle retanguloA, Rectangle retanguloB, Color[] coresA, Color[] coresB) { //encontrando as coordenadas da interseo das imagens int topo = Math.Max(retanguloA.Top, retanguloB.Top); int inferior = Math.Min(retanguloA.Bottom, retanguloB.Bottom); int esquerda = Math.Max(retanguloA.Left, retanguloB.Left); int direita = Math.Min(retanguloA.Right, retanguloB.Right); //agora resta achar as cores de cada ponto da interseo, //dentro dos arrays das texturas. //enquanto no chegar a coordenada y final da interseo, encontre //uma cor para testar. for (int y = topo; y < inferior; y++) { //enquanto existirem colunas na interseo, //continue procurando pontos for (int x = esquerda; x < direita; x++) { //o array de cores s tem uma dimenso, calcule as //coordenadas e retorne a cor //daquela posio Color corA = coresA[(y - retanguloA.Top) * retanguloA.Width + (x - retanguloA.Left)]; //ache as coordenadas para o outro objeto Color corB = coresB[(y - retanguloB.Top) * retanguloB.Width + (x - retanguloB.Left)]; //agora que j achou as cores, teste se alguma delas //transparente //se nenhuma das cores for transparente, colidiu! if (corA.A != 0 && corB.A != 0) { //encontrou coliso return true; } } } return false; //no colidiram. } }

Esse cdigo bem bsico, mas, j deve servir como base para um jogo simples, mostra como desenhar uma imagem com uma rea transparente e ainda exemplifica a coliso por pixels. Tambm usei boa parte do cdigo do exemplo anterior, possibilitando uma reviso dos conceitos e ainda aproveitei para falar sobre os tipos de imagens (vetoriais e de bitmap), espero que tenha sido til e at a prxima parte deste tutorial.

Tutorial de XNA - Parte V


Criado em 09/01/2008 10:02 Modificado em 09/01/2008 10:39 Tpicos Csharp Xna Xna+Game+Studio Continuao do tutorial de XNA para iniciantes, mostra como desenhar textos na tela e como realizar um evento de tempos em tempos.

Para o melhor entendimento deste material necessrio que o leitor tenha conhecimentos prvios de C#, de orientao a objetos e que tenha lido as outras 4 partes desse tutorial. O que j foi visto nas outras partes do tutorial permite a criao de vrios joguinhos, se voc ainda no criou o seu, talvez esteja na hora de criar algo, mesmo que seja para praticar os conhecimentos adquiridos. Para chegar at aqui, passamos pela estrutura bsica de um jogo no XNA, vimos como receber a entrada do teclado, desenhamos nossas imagens na tela, movimentamos o personagem e aprendemos dois mtodos de coliso, alm de utilizar reas transparentes em nossas imagens para melhorar os nossos grficos :-) Nesta parte do tutorial mostrarei como desenhar textos na tela, como formatar a fonte e aproveitarei para dar um exemplo de como realizar uma tarefa de tempos em tempos no seu jogo. Cuidado ao utilizar fontes de terceiros no seu jogo. Antes de mostrar como utilizar os textos no seu jogo, devo tocar em um ponto importante das fontes, chamado licena. Nem todas as fontes que voc usa permitem que voc as utilize em seus projetos, portanto, conhea bem a licena das fontes que pretende usar antes de comear a projetar o visual do seu jogo, porque no seria agradvel perceber ao final do projeto que sua fonte no pode ser usada porque a licena no permite. A primeira parte do projeto, uma nova estrutura. At agora ns sempre adicionamos novos itens a estrutura j existente do nosso projeto, porm, como o assunto desta parte do tutorial relativamente simples, eu resolvi criar um outro projeto para mostrar como desenhar as fontes. O nosso novo projeto ser bem simples, ele mostrar na tela o tempo gasto no jogo (centralizado). Tambm usarei alguns outros itens j vistos, como transparncia, para mostrar as possibilidades de uso nos textos. Eu resolvi criar este exemplo mais simples porque o cdigo estava ficando didaticamente grande e eu preferi isolar os temas para tornar a coisa mais modular, permitindo que as pessoas estudem as partes do tutorial em separado. Aproveite para revisar a estrutura de um jogo no XNA. Vamos criar um projeto do tipo windows game e depois apagar o cdigo pronto, se voc preferir adaptar o cdigo ao meu exemplo, fique a vontade, depois do que j foi visto nas outras partes do tutorial voc j deve ser capaz de fazer isso sozinho. Depois de ter o projeto bsico pronto vamos adicionar os seguintes itens ao nosso cdigo, dentro da nossa classe herdada da classe game (vide outros tutoriais):
//string para mostrar texto na tela. string meu_texto; string resolucao; //posicao do texto na tela Vector2 posicao;

//nova linha para o tempo com transparncia Vector2 posicao2; //posicao do texto da resolucao na tela Vector2 posi_resolucao = Vector2.Zero; //vai informar o ultimo multiplo de cinco, para controlar os eventos int segundo_anterior = 0; //contador dos multiplos de cinco, ser incrementado a cada cinco segundos int total = 0; //cor para desenhar os textos com transparncia Color teste = new Color(Color.Yellow.R, Color.Yellow.G, Color.Yellow.B, 150);

At aqui no temos nenhuma novidade, s o contador e o segundo_anterior que indicar o ultimo mltiplo de 5 (cinco) que passou, isso vai ser explicado melhor mais a frente. Agora temos os itens principais do tutorial, o SpriteFont e o nosso j conhecido SpriteBatch que vo ser responsveis por desenhar nossos textos na tela.
//representa a fonte SpriteFont minhafont; //vai servir para desenhar a fonte na tela SpriteBatch drawfont;

Pronto, agora s precisamos importar a fonte que vai ser usada no nosso pequeno exemplo, por propsitos prticos escolhi a fonte Arial, que simples e existe na maioria dos PCs com Microsoft Windows. Para importar a fonte precisamos adicionar ao nosso projeto um arquivo que vai descrever a fonte usada, sua formatao e o conjunto de caracteres que vai ficar disponvel para utilizao, para adicionar esse arquivo ao projeto v ao Solution Explorer clique com o boto direito sobre o nome do projeto, procure a opo Add e depois clique em New Item. Na janela que vai aparecer, procure pelo item Sprite Font, d um nome a ele (no meu projeto eu coloquei arial.spritefont) e depois clique em Add, o arquivo ser adicionado ao projeto. Esse arquivo vai descrever a fonte e sua formatao atravs de XML3[1]. Esse arquivo tambm possui um Asset Name (um nome que vai ser usado dentro do cdigo), para mudar esse nome clique com o boto direito sobre o arquivo, e depois clique em Properties, procure a propriedade Asset Name e coloque o nome que voc quiser, eu resolvi colocar o nome arial, por agora, acompanhe a minha escolha. Agora vamos modificar a formatao da fonte, d um clique duplo no arquivo arial.spritefont, ele ser aberto e contm os campos responsveis pela formatao da fonte. Perceba que o arquivo j vem comentado em ingls, para facilitar e tornar as coisas mais didticas eu fiz uma traduo/adaptao dos comentrios, ou seja, traduzi fazendo umas modificaes. Veja como ficou o meu arquivo, os comentrios prximos a cada tag so alto explicativos, quando no forem eu falarei a respeito:
<?xml version="1.0" encoding="utf-8"?> <!--

Este arquivo em XML contm a descrio da fonte e ser lido pelo XNA Framework content pipeline. Siga os comentrios para personalizar a aparncia da fonte no seu jogo e para selecionar os caracteres que esto disponveis para utilzao. --> <XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics"> <Asset Type="Graphics:FontDescription"> <!-Modifique esta string para selecionar a fonte que ser importada. O nome da fonte deve ser o mesmo que usado no Windows. --> <FontName>Arial</FontName> <!-O tamanho um valor de ponto flutuante (float) e medido em pontos, modifique este valor para alterar o tamanho da fonte. --> <Size>24</Size> <!-O espao entre as letras um valor em ponto flutuante e medido em pixels. Modifique esse valor para aumentar o espao entre os caracteres. --> <Spacing>2</Spacing> <!-A tag Style controla o estilo da fonte. As opes vlidas so "Regular", "Bold", "Italic", e "Bold, Italic", e so "case sensitive" (sensveis a maiscula e minscula). --> <Style>Regular</Style> <!-A tag CharacterRegions controla a faixa de caracteres disponveis na descrio da fonte. O padro para a faixa de caracteres iniciar no valor 32 (espao no ASCII) e terminar no 126 (o til), os caracteres que estiverem nessa faixa sero montados e disponibilizados para utilizao no seu jogo. Esse intervalo padro cobre o conjunto bsico de caracteres latino e est ordenado de acordo com o padro Unicode. --> <CharacterRegions> <CharacterRegion> <Start>&#32;</Start> <End>&#126;</End> </CharacterRegion> </CharacterRegions> </Asset> </XnaContent>

Para saber o nome da fonte que deve ser usado no seu jogo, consulte a pasta Fontes dentro do Painel de Controle do Microsoft Windows, l voc encontra todas as fontes que esto instaladas na sua mquina. O XNA suporta os mesmos formatos de fontes que a classe System.Drawing.Font do .NET Framework, possuindo suporte a fontes True Type, OpenType (limitado) mas, no suportando bitmap fonts (.fon). A minha formatao foi simples, usei a fonte Arial (True Type), tamanho 24, 2 de espao entre as letras e estilo Regular, j o campo CharacterRegions exige uma explicao mais detalhada. Esse campo informa quais caracteres sero montados e disponibilizados para utilizao no seu projeto, como j foi dito nos comentrios o intervalo padro fornecido com o arquivo 32-126 que cobre todos os caracteres latinos bsicos, repare que voc pode aumentar ou diminuir esse intervalo, aumentando ou diminuindo respectivamente o numero de caracteres que sero montados e disponibilizados no seu jogo.

Vale ressaltar que o nmero de caracteres necessrios a determinados projetos vai variar de regio para regio, na china, por exemplo, o nmero de caracteres muito maior e usar todos os caracteres em um projeto iria requerer muito espao e aumentaria o tempo gasto na execuo. Uma soluo personalizar quais caracteres sero importados para o seu projeto, um texto sobre isso pode ser encontrado nesse link: http://mtbeta.msdn.microsoft.com/pt-br/library/bb447751.aspx?altlang=pt-br Caso voc tente utilizar um caractere que no esteja disponvel na faixa de valores fornecida no seu SpriteFont, ser gerado um Exception, dependendo da situao onde voc tentou usar o texto. Caso voc tenha passado o texto a um mtodo, por exemplo, poderia ser disparado um ArgumentException que ocorre quando um argumento invlido passado a um mtodo, para evitar esse tipo de situao tente garantir que os caracteres que voc precisa esto dentro da faixa de valores fornecida no SpriteFont, para prevenir surpresas em projetos grandes, utilize tratamento de erros. No nosso caso, voc pode usar tranquilamente a faixa de valores fornecida. Encapsulando a fonte. Depois de alterar o nosso arquivo arial.spritefont com a formatao escolhida, vamos importar o conjunto de caracteres necessrios ao nosso projeto, fazemos isso no mtodo LoadGraphicsContent, onde carregamos todos os recursos necessrios ao projeto e inicializamos o SpriteBatch com o dispositivo grfico.
//carregando os recursos "externos" protected override void LoadGraphicsContent(bool loadAllContent) { if (loadAllContent) { //olha o asset name da fonte ai gente! :-) minhafont = recursos.Load<SpriteFont>("arial"); drawfont = new SpriteBatch(grafico.GraphicsDevice); } }

Agora vamos ao mtodo UpDate que vai atualizar os dados do nosso projeto, incrementando o contador e verificando se j se passaram 5 (cinco) segundos. Essa rotina tambm vai preencher as nossas strings e atualizar as posies onde os textos sero desenhados.
protected override void Update(GameTime gameTime) { //pegando o status do teclado para testar a saida KeyboardState teclado = Keyboard.GetState(); //o texto ser o tempo total gasto no jogo meu_texto = gameTime.TotalGameTime.ToString(); //a posio ser centralizada float X = grafico.GraphicsDevice.Viewport.Width / 2; float Y = grafico.GraphicsDevice.Viewport.Height / 2; //mostre a resoluo da tela na tela resolucao = grafico.GraphicsDevice.Viewport.Width.ToString() + " x " + grafico.GraphicsDevice.Viewport.Height.ToString(); posicao = new Vector2(X,Y); //nova linha com transparncia posicao2 = new Vector2(X,Y); //simplifique o nome para melhorar o cdigo int segundo_atual = gameTime.TotalRealTime.Seconds;

//incremente o contador a cada cinco segundos if(((segundo_atual % 5) == 0) && (segundo_atual != 0) && (segundo_atual != segundo_anterior) ) { segundo_anterior = segundo_atual; total++; } //sair se pressionar esc if (teclado.IsKeyDown(Keys.Escape)) { this.Exit(); } }

Nesse cdigo temos pouca coisa nova, na verdade apenas alguns itens so novos aqui. O objeto GameTime fornece o tempo de execuo do jogo e usamos o mtodo ToString para converter o tempo para a nossa string que ser exibida na tela.
//o texto ser o tempo total gasto no jogo meu_texto = gameTime.TotalGameTime.ToString();

Depois eu uso o objeto que representa nosso dispositivo grfico para descobrir qual a resoluo em que estou trabalhando, voc tambm poderia usar esse objeto para determinar a resoluo que deseja trabalhar, no meu caso eu apenas guardei os valores para usar depois, quando eu for desenhar o texto na tela, centralizado. Repare que como eu vou centralizar o texto, j armazenei o valor dividido por 2 (dois).
//a posio ser centralizada float X = grafico.GraphicsDevice.Viewport.Width / 2; float Y = grafico.GraphicsDevice.Viewport.Height / 2;

Agora vamos ao cdigo que vai incrementar o contador a cada cinco segundos. A idia simples, testamos os segundos, caso eles sejam divisveis por 5 (cinco), ento passaram mais cinco segundos depois do ultimo incremento. S que temos um problema, durante um segundo, o valor vai ser divisvel por cinco, isso faz com que o contador seja incrementado vrias vezes a cada mltiplo de cinco, para solucionar isso, basta guardar o valor do ultimo mltiplo em algum lugar e testar se ele j no foi utilizado. Por exemplo, suponhamos que tenha passado 3 segundos, logo, 3 / 5 = 0 e resta 3, ou seja, no divisvel por cinco. Agora suponhamos que passaram 15 segundos, logo 15 / 5 = 3 e resta 0 (zero), ou seja, divisvel por cinco e devemos incrementar o total. Mas lembre que o valor do campo segundos, permanecer sendo 15 por um segundo e no prximo loop, ainda seria divisvel por 5, incrementando erroneamente o total. Para resolver basta fazer o seguinte, guardamos o valor quinze logo depois de descobrirmos que ele divisvel e no prximo loop testamos se o valor igual ao ultimo que foi usado. Lembre que 0 (zero) / 5 = 0 e resta 0, logo tambm divisvel mas, no segundo 0 (zero) no podemos contar que passou cinco segundos :-) O cdigo ficou assim:
//simplifique o nome para melhorar o cdigo int segundo_atual = gameTime.TotalRealTime.Seconds; //incremente o contador a cada cinco segundos if(((segundo_atual % 5) == 0) && (segundo_atual != 0) && (segundo_atual != segundo_anterior) ) { segundo_anterior = segundo_atual; total++; }

Isso vai fazer com que o objeto total seja incrementado a cada cinco segundos, voc poderia colocar qualquer coisa no lugar dele, poderia chamar um mtodo que faz chover no seu jogo a cada cinco segundos ou fazer com que o sol fosse embora a cada meia hora de jogo. Agora vamos ver como desenhar isso na tela.
protected override void Draw(GameTime gameTime) { //limpe a tela com a cor preta grafico.GraphicsDevice.Clear(Color.Black); //centralizar posicao.X = posicao.X - (minhafont.MeasureString(meu_texto).X)/2; posicao2.X = posicao2.X -(minhafont.MeasureString(meu_texto).X)/2; //pular uma linha e deixar um espao de 10 posicao2.Y = posicao2.Y + (minhafont.MeasureString(meu_texto).Y + 10); drawfont.Begin(); //desenhe a resoluo com o contador drawfont.DrawString(minhafont, resolucao + " " + total.ToString(), posi_resolucao, teste); //desenhe a frase amarela drawfont.DrawString(minhafont, meu_texto, posicao, Color.Yellow); //desenhe com transparncia drawfont.DrawString(minhafont, meu_texto, posicao2, teste); drawfont.End(); }

O objeto SpriteFont possui um mtodo que retorna o tamanho da string, tanto o tamanho vertical como o horizontal, assim, podemos centralizar a string na tela usando os valores da resoluo que j tnhamos guardado.
//centralizar posicao.X = posicao.X - (minhafont.MeasureString(meu_texto).X)/2; posicao2.X = posicao2.X -(minhafont.MeasureString(meu_texto).X)/2;

Depois s usar o mtodo DrawString do nosso SpriteBatch para desenhar a string na tela, esse mtodo recebe como argumentos o SpriteFont onde encapsulamos o conjunto de caracteres da nossa fonte, a string a ser desenhada, um objeto do tipo Vector2 que corresponde a posio onde o texto ser desenhado e a cor de desenho.
//desenhe a frase amarela drawfont.DrawString(minhafont, meu_texto, posicao, Color.Yellow);

Pronto, agora s testar o resultado ficar assim:

Repare que no meu cdigo eu tambm usei uma linha onde a cor de desenho possua transparncia. Ai vai o cdigo completo:
#region namespaces necessrios using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; #endregion namespace fonts { class novo_jogo { static public void Main() { jogo teste = new jogo(); teste.Run(); } } } //herdando da classe Game class jogo : Game { //dispositivo grafico e recursos GraphicsDeviceManager grafico; ContentManager recursos; //string para mostrar texto na tela. string meu_texto; string resolucao; //posicao do texto na tela Vector2 posicao; //nova linha para o tempo com transparncia Vector2 posicao2; //posicao do texto da resolucao na tela Vector2 posi_resolucao = Vector2.Zero; //para verificar se passaram cinco segundos int segundo_anterior = 0; //contador dos multiplos de cinco, //ser incrementado a cada cinco segundos int total = 0;

//cor para desenhar os textos com transparncia Color teste = new Color(Color.Yellow.R, Color.Yellow.G, Color.Yellow.B, 150); //representa a fonte SpriteFont minhafont; //vai servir para desenhar a fonte na tela SpriteBatch drawfont; //construtor da classe public jogo() { grafico = new GraphicsDeviceManager(this); recursos = new ContentManager(Services); } //inicializando membros que no precisam do //dispositivo grafico carregado para inicializar protected override void Initialize() { this.posi_resolucao.X += 10; this.posi_resolucao.Y += 10; base.Initialize(); } //carregando os recursos "externos" protected override void LoadGraphicsContent(bool loadAllContent) { if (loadAllContent) { //olha o asset name da fonte aqui minhafont = recursos.Load<SpriteFont>("arial"); drawfont = new SpriteBatch(grafico.GraphicsDevice); } } protected override void Update(GameTime gameTime) { //pegando o status do teclado para testar a saida KeyboardState teclado = Keyboard.GetState(); //o texto ser o tempo total gasto no jogo meu_texto = gameTime.TotalGameTime.ToString(); //a posio ser centralizada float X = grafico.GraphicsDevice.Viewport.Width / 2; float Y = grafico.GraphicsDevice.Viewport.Height / 2; //mostre a resoluo da tela na tela resolucao = grafico.GraphicsDevice.Viewport.Width.ToString() + " x " + grafico.GraphicsDevice.Viewport.Height.ToString(); posicao = new Vector2(X,Y); //nova linha com transparncia posicao2 = new Vector2(X,Y); //simplifique o nome para melhorar o cdigo int segundo_atual = gameTime.TotalRealTime.Seconds; //incremente o contador a cada cinco segundos if(((segundo_atual % 5) == 0) && (segundo_atual != 0) && (segundo_atual != segundo_anterior) ) { segundo_anterior = segundo_atual; total++; }

//sair se pressionar esc if (teclado.IsKeyDown(Keys.Escape)) { this.Exit(); } } protected override void Draw(GameTime gameTime) { //limpe a tela com a cor preta grafico.GraphicsDevice.Clear(Color.Black); //centralizar posicao.X = posicao.X - (minhafont.MeasureString(meu_texto).X)/2; posicao2.X = posicao2.X - (minhafont.MeasureString(meu_texto).X)/2;

//pular uma linha e deixar um espao de 10 posicao2.Y = posicao2.Y + (minhafont.MeasureString(meu_texto).Y + 10); drawfont.Begin(); //desenhe a resoluo com o contador drawfont.DrawString(minhafont, resolucao + " " + total.ToString(), posi_resolucao, teste); //desenhe a frase amarela drawfont.DrawString(minhafont, meu_texto, posicao, Color.Yellow); //desenhe com transparncia drawfont.DrawString(minhafont, meu_texto, posicao2, teste); drawfont.End(); } }

Bom, vimos como realizar um evento de tempos em tempos no nosso jogo, tambm aprendemos como desenhar texto na tela e vimos que podemos inclusive usar transparncia no texto usando o que j aprendemos sobre cores. Espero que este texto seja til e at a prxima parte do tutorial.