[Tens de ter uma conta e sessão iniciada para poderes visualizar esta imagem]

Tive de criar esse tutorial , para que vocês possam entender os 2 últimos tutoriais de XNA.

Limites de um sprite

Como vimos no começo dos tutoriais de XNA, quando desenhamos uma sprite na tela sua posição inicial (canto superior esquerdo) está em alguma posição (X,Y) a sprite também tem uma largura (Width) e altura (Height). A altura e largura do sprite quase nunca mudam, a não ser que você troque as sprites durante uma animação por exemplo de ataque. Porém, a posição da sprite está em constante mudança, seja pelos comandos de entrada do usuário ou por alguma inteligência artificial. A figura abaixo, ilustra os pontos principais de uma sprite desenhada.

[Tens de ter uma conta e sessão iniciada para poderes visualizar esta imagem]

Para dar continuidade aos estudos, baixe o pré-projeto para este tutorial clicando no icone de download abaixo. O pré-projeto já inclui a imagem do fantasma e o código onde o fantasma é desenhado e pode ser movimentado com o teclado.

[Tens de ter uma conta e sessão iniciada para poderes visualizar esta imagem]

Abaixo é possível analisar o código do pré-projeto. A única coisa ainda não explicada detalhadamente são as entradas do teclado, mas já utilizamos em outros tutorais.


Código:

public class Game1 : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
    // Variável para armazenar a imagem
    Texture2D img = null;
    // Variável para armazenar a posição da sprite (ou posição do personagem)
    // Inicializa a variável na posição (0,0)
    Vector2 pos1 = Vector2.Zero;
    // Constante de velocidade (5 pixels)
    const float velocidade = 5;
 
    public Game1()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    }
 
    protected override void LoadContent()
    {
        spriteBatch = new SpriteBatch(GraphicsDevice);
        // Carrega a imagem do fantasma para variável "img"
        img = Content.Loa<Texture2D>(@"fantasma");
    }
 
    protected override void Update(GameTime gameTime)
    {
        // Recebe o estado atual do teclado (estado deste exato momento)
        KeyboardState teclado = Keyboard.GetState();
        // Se a seta esquerda estiver pressionada
        if (teclado.IsKeyDown(Keys.Left))
        {
            // Decrementa a posição X em 10 pixels
            pos1.X -= velocidade;
        }
        // Se a seta direita estiver pressionada
        if (teclado.IsKeyDown(Keys.Right))
        {
            // Incrementa a posição X em 10 pixels
            pos1.X += velocidade;
        }
        // Se a seta cima estiver pressionada
        if (teclado.IsKeyDown(Keys.Up))
        {
            // Decrementa a posição Y em 10 pixels
            pos1.Y -= velocidade;
        }
        // Se a seta baixo estiver pressionada
        if (teclado.IsKeyDown(Keys.Down))
        {
            // Incrementa a posição Y em 10 pixels
            pos1.Y += velocidade;
        }
        // chama o método da superclasse passando o parâmentro de tempo do jogo
        base.Update(gameTime);
    }
 
    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);
        // Inicia o bloco de desenho
        spriteBatch.Begin();
        // Desenho o fantasma que se movimenta com o teclado
        spriteBatch.Draw(img, pos1, Color.White);
        // Finaliza o bloco de desenho
        spriteBatch.End();
        // chama o método da superclasse passando o parâmentro de tempo do jogo
        base.Draw(gameTime);
    }
}


Detectando colisão com a tela

Inicialmente iremos começar a detectar colisões de um objeto retangular com a janela do jogo, sendo assim, é necessário conhecer os limites da janela e os limites do objeto (sprite). O ponto superior esquerdo da janela, como já vimos no primeiro tutorial de como desenhar na tela, é o (0, 0). Para sabermos a largura da tela podemos utilizar as propriedades PreferredBackBufferWidth e PreferredBackBufferHeight do objeto do tipo GraphicsDeviceManager. Essas propriedades permitem determinar respectivamente a largura e altura da janela. Assim a posição da tela pode ser definida como a figura abaixo.

[Tens de ter uma conta e sessão iniciada para poderes visualizar esta imagem]

Agora que sabemos como situar a sprite na tela e também conhecemos os limites da janela, podemos detectar se a sprite colidiu com algum dos pontos da janela da seguinte forma:

Código:

// Testa o limite direito da tela
if (pos1.X + img.Width > graphics.PreferredBackBufferWidth)
{
    // Se colidiu faz alguma coisa:
    // fixa a posição do fantamas no máximo da janela em X
    pos1.X = graphics.PreferredBackBufferWidth - img.Width;
}
// Testa o limite inferior da tela
if (pos1.Y + img.Height > graphics.PreferredBackBufferHeight)
{
    // Se colidiu faz alguma coisa:
    // fixa a posição do fantamas no máximo da janela em Y
    pos1.Y = graphics.PreferredBackBufferHeight - img.Height;
}
// Testa o limite esquerdo da tela
if (pos1.X < 0)
{
    // Se colidiu faz alguma coisa:
    // fixa a posição do fantamas no início da janela em X
    pos1.X = 0;
}
// Testa o limite superior da tela
if (pos1.Y < 0)
{
    // Se colidiu faz alguma coisa:
    // fixa a posição do fantamas no início da janela em Y
    pos1.Y = 0;
}


O código acima é colocado dentro do método Update(GameTime gameTime), após, realizar o movimento do fantasma e antes de chamar o método update da superclasse.

Colisões entre objetos (retângulos)

O sistema de colisão por retângulos (Rectangle) é um sistema bem simples. Este tipo de colisão não é uma das melhores formas, pois, pode ocorrer das imagens não ocuparem o retângulo por completo, dando uma falsa impressão de colisão (problemas visual). Porém é um método prático e rápido de ser implementado. Neste tipo de colisão é verificado se algum vértice de um retângulo está dentro de um outro retângulo. A figura abaixo mostra três possíveis casos, sendo o primeiro de não colisão, o segundo de colisão e o terceiro de colisão porém com falsa impressão de colisão.

[Tens de ter uma conta e sessão iniciada para poderes visualizar esta imagem]

Este tutorial não irá ficar se explicando a parte matemática, apesar da mesma neste situação ser muito simples. Neste caso iremos investigar os principais atributos, propriedades e métodos da estrutura Rectangle para que possamos utilizá-lo em nossos jogos.

[Tens de ter uma conta e sessão iniciada para poderes visualizar esta imagem]

Exemplo de colisão entre retângulos

Agora vamos fazer um teste de colisão de objetos usando retângulos, para isso, crie uma nova variável do tipo Vector2 para armazenarmos a posição de um segundo fantasma. Declare também uma flag do tipo Boolean para sabermos se aconteceu ou não uma colisão entre os objetos. Veja o código de declarações das novas variáveis.

Código:

// Posição do fantasma parado
Vector2 pos2 = new Vector2(350, 170);
// Flag para quando detectar uma colisão
Boolean colidiu = false;


Logo em seguida, vamos fazer uma mudança no método Draw para que possamos desenhar o novo fantasma e também adicionar a nossa flag de colisão para caso aconteça uma colisão a tela mude de cor. Veja como ficaria o código do método Draw.

Código:

protected override void Draw(GameTime gameTime)
{
    if (colidiu)
    {
        // Se aconteceu uma colisão, pintamos a tela de salmon
        GraphicsDevice.Clear(Color.Salmon);
    }
    else
    {
        // Caso não aconteça uma colisão pintamos com a cor padrão
        GraphicsDevice.Clear(Color.CornflowerBlue);
    }
    // Inicia o bloco de desenho
    spriteBatch.Begin();
    // Desenho o fantasma que fica parado na tela
    spriteBatch.Draw(img, pos2, Color.White);
    // Desenho o fantasma que se movimenta com o teclado
    spriteBatch.Draw(img, pos1, Color.White);
    // Finaliza o bloco de desenho
    spriteBatch.End();
    // chama o método da superclasse passando o parâmentro de tempo do jogo
    base.Draw(gameTime);
}



Por fim, só precisamos lembrar que a cada atualização a posição do personagem pode mudar, ou seja, é necessário atualizar os retângulos. Após atualizar a posição dos retângulos então usar o método Intersect e verificar se houve colisão, e se isso acontecer então mudaremos nossa variável colidiu para verdadeiro. Veja como este código ficaria (código adicionado ao método Update depois que verificamos a colisão com o cenário.

Código:

// Retângulo do personagem que se movimenta
// É necessário fazer um conversão (cast) para int, pois Vector2 é do tipo float
Rectangle rect1 = new Rectangle((int)pos1.X, (int)pos1.Y, (int)img.Width, (int)img.Height);
// Retângulo do personagem que fica parado
// É necessário fazer um conversão (cast) para int, pois Vector2 é do tipo float
Rectangle rect2 = new Rectangle((int)pos2.X, (int)pos2.Y, (int)img.Width, (int)img.Height);
// Verifica se o retângulo 1 colidiu com o segundo retângulo
if (rect1.Intersects(rect2))
{
    // se aconteceu a colisão mudamos para verdadeiro
    colidiu = true;
}
else
{
    // se não aconteceu a colisão mudamos para verdadeiro
    colidiu = false;
}


Alguns resultados do que vai acontecer, podem ser vistos nas figuras abaixo. A primeira figura exibe os personagens sem colisão, enquanto na segunda figura, os personagens se colidiram então a cor de fundo muda para Salmon.

[Tens de ter uma conta e sessão iniciada para poderes visualizar esta imagem]