[Tutorial] Renderizando objetos e movendo-os com XNA [Via Classe]

Este tutorial é uma continuação do tutorial:

http://www.insidedotnet.com.br/andrebaltieri/index.php/2009/11/15/tutorial-renderizando-objetos-e-movendo-os-com-xna/

Introdução

 No tutorial anterior, criamos uma imagem na tela e fizemos com que ela se movesse. No entanto, podemos encapsular este código dentro do uma classe e reutilizá-lo no futuro.

Neste artigo trataremos a criação de uma classe para o PacMan e sua utilização. Sendo assim, adicione uma nova classe no seu projeto com o nome de Sprite.cs.

Criando as variáveis

Para nossa classe Sprite, precisaremos criar algumas variáveis, sendo elas:

texture – Utilizada para manipular a textura da sprite.

size – Tamanho da sprite.

position – Posição da sprite na tela.

speed – velociade com que a sprite se movimentará.

rotation – Rotação da sprite.

effect – Efeitos da sprite.

Deste modo, teremos o seguinte código:

using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
 
namespace XNASprites
{
    ///
    /// Classe para manipulação de Sprites
    ///
    public class Sprite
    {
        #region Variáveis
        private Texture2D texture;
        private Vector2 size;
        private Vector2 position;
        private int speed = 1;
        private float rotation = 0;
        private SpriteEffects effect = SpriteEffects.None;
        #endregion
    }
}

Criando as propriedades

Sabemos que na orientação à objetos, para cada variável a ser acessada externamente, temos que ter uma propriedade referente a mesma. Sendo assim, temos a adição das propriedades na nossa classe:

#region Propriedades
///
/// Manipula a textura da Sprite
///
public Texture2D Texture
{
    get { return this.texture; }
    set { this.texture = value; }
}
///
/// Manipula o tamanho da Sprite.
///
public Vector2 Size
{
    get { return this.size; }
    set { this.size = value; }
}
///
/// Manipula a posição da Sprite na tela.
///
public Vector2 Position
{
    get { return this.position; }
    set { this.position = value; }
}
///
/// Manipula a velocidade na qual a Sprite se move na tela.
///
public int Speed
{
    get { return this.speed; }
    set { this.speed = value; }
}
///
/// Manipula a rotação da Sprite sobre seu eixo central.
///
public float Rotation
{
    get { return this.rotation; }
    set { this.rotation = value; }
}
///
/// Manipula os efeitos a serem aplicados sobre a Sprite.
///
public SpriteEffects Effect
{
    get { return this.effect; }
    set { this.effect = value; }
}
#endregion

Criando os métodos construtores

Basicamente teremos a criação de um construtor simples e um composto, informando os valores das propriedades.

Â

#region Métodos Construtores
///
/// Método Construtor simples
///
public Sprite()
{
    // Método construtor simples
}
///
/// Método construtor composto
///
///
Textura da Sprite
///
Tamanho da Sprite
///
Posição da Sprite na tela
///
Velocidade com que a Sprite se moverá
///
Rotação da Sprite sobre seu eixo central
///
Efeitos a serem aplicados na Sprite
public Sprite(Texture2D pTexture, Vector2 pSize, Vector2 pPosition, int pSpeed, float pRotation, SpriteEffects pEffect)
{
    this.texture = pTexture;
    this.size = pSize;
    this.position = pPosition;
    this.speed = pSpeed;
    this.rotation = pRotation;
    this.effect = pEffect;
}
#endregion

Sobrescrevendo o método Draw

O método Draw() é responsável por renderizar os objetos na tela, e pode ser extendido. Neste caso, deixaremos que ele se encarregue de desenhar a sprite na tela.

#region Overridables
///
/// Desenha a sprite na tela
///
///
Lote de Sprites
public void Draw(SpriteBatch spriteBatch)
{
    spriteBatch.Draw(texture,
        new Rectangle((int)position.X, (int)position.Y, (int)size.X, (int)size.Y),
        null,
        Color.White, rotation, new Vector2((int)size.X / 2, (int)size.Y / 2), effect, 0);
}
#endregion

Quando chamamos o método Draw() temos 7 sobrecargas. Optei pela sobrecarga número 5, que é a mais completa, e apesar de não utilizarmos todos os seus parâmetros agora, nos próximos artigos ficará mais fácil adaptar este método ao que queremos fazer.

No método temos os seguintes parâmetros (Nesta ordem):

  1. Texture2D Texture – Textura da Sprite.
  2. Rectangle Destination – Retangulo onde a sprite será desenhada.
  3. Rectangle Source – Retângulo de origem da imagem. (Opcional)
  4. Color Color – Coloração da Sprite.
  5. Float Rotation – Rotação da Sprite.
  6. Vector2 Origin – Ponto pivot da sprite. (Usado para rotação também)
  7. SpriteEffects Effects – Efeitos aplicados à sprite.
  8. Float LayerDepth – Profundidade da Sprite. Podemos criar níveis de renderização de sprites utilizando esta propriedade.

Note que informamos o rectangle source como NULL, pois ele é opcional e não utilizaremos ele agora.

No parâmetro Destination, criamos um novo retângulo utilizando as posições e o tamanho da sprite, ou seja, criamos um retângulo na mesma posição e do mesmo tamanho da sprite.

No parâmetro Origin, criamos um novo vetor e informamos que ele se situa no ponto central da sprite atravéz da divisão da largura e altura da sprite por 2. Sendo assim, temos o ponto pivot sobre o meio da sprite, e quando aplicarmos a rotação na mesma, ela girará sobre seu centro.

Como não estamos trabalhando com background nem nada, atribui o LayerDepth fixo como 0.

Utilizando a classe Sprite

Bem, com a nossa classe criada, nos resta saber como podemos utiliza-la no nosso jogo.

Primeiramente, precisamo criar uma variável do tipo sprite, para armazenar nossa sprite.

Sendo assim, logo após a variável SpriteBatch, no topo do arquivo Game1.cs, crie a constante utilizada para armazenar o nome do asset a ser carregado na sprite e a variável sprite.

GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
 
public const string SPRITE_ASSET = "pacman";
Sprite minhaSprite;

Criadas as variáveis, precisamos carregar a textura e inicializá-las. No método LoadContent(), adicione a seguinte linha:

// TODO: use this.Content to load your game content here
minhaSprite = new Sprite(Content.Load(SPRITE_ASSET), new Vector2(64f, 64f), new Vector2(100f, 100f), 2, 0, SpriteEffects.None);

Agora o que precisamos fazer é no método Draw() chamar a função Draw() da nossa sprite.

protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.CornflowerBlue);
 
    // TODO: Add your drawing code here
    spriteBatch.Begin();
    minhaSprite.Draw(spriteBatch);
    spriteBatch.End();
    base.Draw(gameTime);
}

Aplicando movimento à Sprite

O que iremos fazer agora é criar um método para mover, rotacionar e aplicar efeitos sobre sprite. Este método será chamado dentro do método Update(), pois será atualizado frame a frame.

#region Keyboard Handler
///
/// Atualiza a posição da Sprite na tela
///
///
Estado do teclado
public void UpdatePacmanMovement(KeyboardState pKeyboardState)
{
    if (pKeyboardState.IsKeyDown(Keys.Left))
    {
        minhaSprite.Position = new Vector2(minhaSprite.Position.X - minhaSprite.Speed, minhaSprite.Position.Y);
        minhaSprite.Rotation = 0;
        minhaSprite.Effect = SpriteEffects.FlipHorizontally;
    }
    if (pKeyboardState.IsKeyDown(Keys.Right))
    {
        minhaSprite.Position = new Vector2(minhaSprite.Position.X + minhaSprite.Speed, minhaSprite.Position.Y);
        minhaSprite.Rotation = 0;
        minhaSprite.Effect = SpriteEffects.None;
    }
    if (pKeyboardState.IsKeyDown(Keys.Up))
    {
        minhaSprite.Position = new Vector2(minhaSprite.Position.X, minhaSprite.Position.Y - minhaSprite.Speed);
        minhaSprite.Rotation = -90;
        minhaSprite.Effect = SpriteEffects.None;
    }
    if (pKeyboardState.IsKeyDown(Keys.Down))
    {
        minhaSprite.Position = new Vector2(minhaSprite.Position.X, minhaSprite.Position.Y + minhaSprite.Speed);
        minhaSprite.Rotation = 45;
        minhaSprite.Effect = SpriteEffects.None;
    }
}
#endregion

Note que temos um parâmetro de entrada do tipo KeyboardState, que é de onde extrairemos a informação de qual tecla está sendo pressionada.

Vamos as explicações.

Basicamente, temos um calculo de velocidade onde somamos ou subtraímos a velocidade que informamos à sprite (Propriedade Speed) da posição X ou Y da Sprite (Propriedade Position).

Feito isso, no caso das teclas pressionadas serem para baixo (Down) ou cima (Up), aplicamos uma rotação de -90 graus (Para Up) ou 45 graus (Para Down). As teclas pressionadas sejam direita (Right) ou esquerda (Left) aplicamos o valor 0, que significa sem rotação. Deste modo, temos o Pacman olhando para baixo e para cima.

Na terceira linha temos o efeito de espelho aplicado a sprite. Necessitamos deste efeito para quando a seta da esquerda for pressionada (Left), fazendo com que o Pacman ande para esquerda e deste modo, temos que inverter a sprite para que ela fique virada para esquerda.

Para finalizar, no método Update(), vamos chamar a função UpdatePacmanMovement().`

protected override void Update(GameTime gameTime)
{
    // Allows the game to exit
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
        this.Exit();
 
    // TODO: Add your update logic here
    KeyboardState ks = new KeyboardState();
    ks = Keyboard.GetState();
    UpdatePacmanMovement(ks);
    base.Update(gameTime);
}

Sem muito segredo, criamos um KeyboardState e obtemos o estado das teclas através do método GetState(). Então, passamos o KeyboardState para função UpdatePacmanMovement().

O resultado pode ser conferido na imagem abaixo.

Download do Projeto

http://www.insidedotnet.com.br/andrebaltieri/Artigos/2009/11/xna_sprite_class/XNASprites.zip

Espero que tenham gostado!

Um ótimo fim de semana e até o próximo artigo, que será sobre animação de sprites =D

Abraços!

2 Comments to “[Tutorial] Renderizando objetos e movendo-os com XNA [Via Classe]”

  1. Kleber Andrade 21 novembro 2009 at 8:41 #

    Muito bom André, bem interessante o encapsulamento. Esta de parabéns. Depois eu vou fazer um link do seu tutorial no meu blog, se você concordar.
    Abraços, t+

  2. Pedro Paro 7 janeiro 2010 at 17:36 #

    Cara, uma correção talvez?

    Ali onde vc adiciona a linha no LoadContente()

    // TODO: use this.Content to load your game content here
    minhaSprite = new Sprite(Content.Load(SPRITE_ASSET), new Vector2(64f, 64f), new Vector2(100f, 100f), 2, 0, SpriteEffects.None);

    não seria….

    new Sprite(Content.Load(SPRITE_ASSET), new Vector2(64f, 64f), new Vector2(100f, 100f), 2, 0, SpriteEffects.None);

    ?


Leave a Reply