domingo, 3 de abril de 2011

XNA: Manipulação de Elementos 2D - Parte 2

´bDando continuidade ao post sobre manipulação de elementos 2D, neste serão apresentadas maneiras para movimentação de sprites na tela, leitura de comandos do usuário através do teclado e verificação de colisão de objetos.


Movimentação do Sprite na tela

Para movimentarmos nosso sprite pela tela, criaremos um método na classe que o representa chamado Mover e também adicionaremos um atributo chamado velocidade a esta classe, este atributo determinará quantos pontos o sprite deve se mover nas posições X e Y quando o método Mover for chamado, e ainda um atributo para representar o tamanho da tela, chamado tamanhoDaTela que será usado pra verificar se o sprite alcançou uma das bordas da tela.

Alteraremos o construtor de nossa classe para configurar o atributo velocidade, tamanhoDaTela e também o atributo tamanho que será definido pela largura e altura da imagem de nosso sprite. O Método mover irá somar a posição atual do sprite com a velocidade definida o que causará o movimento.

Após as alterações nossa classe ficará da seguinte maneira:

class Ball
{
    private Texture2D textura;
    private Vector2 tamanho;
    private Vector2 posicao;
    private Vector2 velocidade;
    private Vector2 tamanhoDaTela;        

    public Texture2D Textura { get { return textura; } set { textura = value; } }
    public Vector2 Tamanho { get { return tamanho; } set { tamanho = value; } }
    public Vector2 Posicao { get { return posicao; } set { posicao = value; } }
    public Vector2 Velocidade { get { return velocidade; } set { velocidade = value; } }
    public Vector2 TamanhoDaTela { get { return tamanhoDaTela; } set { tamanhoDaTela = value; } }

    public Ball(Texture2D textura, Vector2 posicao, Vector2 velocidade, Vector2 tamanhoDaTela)
    {
        Textura = textura;           
        Posicao = posicao;
        Velocidade = velocidade;
        // Configurando o tamanho de acordo com as medidas da imagem do sprite
        tamanho = new Vector2(Textura.Width, Textura.Height);
        TamanhoDaTela = tamanhoDaTela;
    }

    public void Draw(SpriteBatch spriteBatch)
    {
        spriteBatch.Draw(textura, posicao, Color.White);
    }

    public void Mover()
    {
        posicao += velocidade;
    }
} 

Na classe Game1, alteraremos o método LoadContent para adequá-lo ao novo construtor de nosso sprite. Usamos a referência graphics para obter o tamanho de nossa tela:

protected override void LoadContent()
{
    spriteBatch = new SpriteBatch(GraphicsDevice);

    // Carregando nosso sprite
    // Enviamos a imagem, posição, velocidade e tamanho da tela para o sprite      
    ball = new Ball(Content.Load("ball"), new Vector2(0, 0), new Vector2(2, 2), new Vector2(graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight));
}

E adicionamos uma chamada para o método Mover do sprite, no método Update da classe Game1:

protected override void Update(GameTime gameTime)
{
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
        this.Exit();

    // TODO
    ball.Mover();

    base.Update(gameTime);
}

Feitas as alterações podemos executar o projeto e veremos nosso sprite se movendo pela tela, porém percebemos que o sprite não para ao alcançar as bordas da tela, simplesmente some. Vamos melhorar esta movimentação de modo com que ao alcançar uma das bordas o sprite inverta sua movimentação, causando a impressão de colisão com a borda.

Para isso vamos alterar o método Mover da classe de nosso sprite, fazendo com este verifique se alcançou uma das bordas e caso o tenha feito inverta sua direção de movimento.

public void Mover()
{
    // Verificando se alcançou a borda esquerda
    if (posicao.X + velocidade.X < 0)
    {
        velocidade.X *= -1;
    }

    // Verificando se alcançou a borda direita
    if (posicao.X + velocidade.X + tamanho.X > tamanhoDaTela.X)
    {
        velocidade.X *= -1;
    }

    // Verificando se alcançou a borda superior
    if (posicao.Y + velocidade.Y < 0)
    {
        velocidade.Y *= -1;
    }

    // Verificando se alcançou a borda inferior
    if (posicao.Y + velocidade.Y + tamanho.Y > tamanhoDaTela.Y)
    {
        velocidade.Y *= -1;
    }

    posicao += velocidade;
}

Podemos agora executar o projeto e verificaremos que ao alcançar uma das bordas, nosso sprite inverte a movimentação.


Movimentação do sprite utilizando o Teclado

Na classe Game1 vamos criar uma nova instancia da classe que representa nosso sprite, esta irá se mover de acordo com comandos, utilizando as setas do teclado.

public class Game1 : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;

    Ball ball; // Nosso sprite
    Ball ball2; // Novo sprite controlado pelo teclado

Inicializamos o novo objeto no método LoadContent, este objeto se diferenciará do outro na posição inicial e na velocidade.

protected override void LoadContent()
{
     // Create a new SpriteBatch, which can be used to draw textures.
     spriteBatch = new SpriteBatch(GraphicsDevice);

    // TODO: use this.Content to load your game content here
    // Carregando nosso sprite
    ball = new Ball(Content.Load("ball"), new Vector2(0, 0), new Vector2(2, 2), new Vector2(graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight));
    ball2 = new Ball(Content.Load("ball"), new Vector2(100, 100), new Vector2(5, 5), new Vector2(graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight));
}

Alteramos o método UnloadContent para liberar os recursos utilizados pelo novo objeto:

protected override void UnloadContent()
{
     ball.Textura.Dispose();
     ball2.Textura.Dispose();
}


Desenharemos o novo objeto utilizado o método Draw:

protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.CornflowerBlue);

    spriteBatch.Begin();
    ball.Draw(spriteBatch);
    ball2.Draw(spriteBatch);
    spriteBatch.End();

    base.Draw(gameTime);
}

A leitura de teclas é realizada no método Update, onde verificaremos se uma das teclas está pressionada e então moveremos o sprite de acordo com a tecla. Utilizamos o método GetState da classe KeyBoard que nos retorna o estado atual do teclado e então podemos verificar quais teclas estão pressionadas.

O método Update fica então a seguinte forma:

{
    // Allows the game to exit
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
        this.Exit();

    // Movimentando o novo sprite
    Vector2 posicao = ball2.Posicao;
    Vector2 velocidade = ball2.Velocidade;
    KeyboardState keyboard = Keyboard.GetState();

    if (keyboard.IsKeyDown(Keys.Up))
    {
        posicao.Y -= velocidade.Y;
    }
    if (keyboard.IsKeyDown(Keys.Down))
    {
        posicao.Y += velocidade.Y;
    }
    if (keyboard.IsKeyDown(Keys.Left))
    {
        posicao.X -= velocidade.X;
    }
    if (keyboard.IsKeyDown(Keys.Right))
    {
        posicao.X += velocidade.X;
    }

    ball2.Posicao = posicao;

    // TODO: Add your update logic here
    ball.Mover();

    base.Update(gameTime);
}

Com isso podemos executar nosso projeto e mover o segundo sprite utilizando as setas do teclado.


Detecção de colisão

Em nosso projeto os sprites estão simplesmente 'atravessando' um ao outro quando se encontram. A detecção de colisão nos permite saber o exato momento em que objetos se encontram para então realizarmos alguma ação.

Existem diversos algoritmos para detecção de colisão em diferentes formas de objetos, em nosso exemplo estamos utilizando círculos, então a detecção será feita verificando a distância dos centros dos círculos com a soma de seus raios.

Criaremos as propriedades de centro e raio na classe que representa nosso sprite:

public Vector2 Centro { get { return posicao + (tamanho / 2); } }
public double Raio { get { return tamanho.X / 2; } }

Então criamos um método para verificar a colisão dos objetos:

public Boolean VerificarColisao(Ball ball)
{
    if (Vector2.Distance(Centro, ball.Centro) < (Raio + ball.Raio))
    {
        return true;
    }
    else
    {
        return false;
    }
}

Verificaremos a colisão dos objetos no método Update da classe Game1. Quando uma colisão acontecer iremos inverter a movimentação do objeto que não estamos controlando. Adicionaremos os comandos abaixo, antes da instrução base.Update(gameTime):

if (ball2.VerificarColisao(ball))
{
    ball.Velocidade *= -1;
}

Com isso podemos executar o projeto e verifica nosso sistema de colisão funcionando, apesar de não ser muito eficiente a colisão é detectada com sucesso.


Terminamos aqui este tutorial sobre manipulação básica de objetos 2D.

Nenhum comentário:

Postar um comentário