Vamos aprender XNA? – Parte 2 – Animated Sprites

Publicado em 02/03/2011

7


Olá pessoal, tudo certo?

Depois de alguns dias tratando de assuntos variados, volto a tratar de XNA. Se você não sabe do que estou falando, talvez queira ler o primeiro post dessa série.

O tema de hoje são “Animated Sprites”. Para maior comodidade, considere fazer o download do código fonte.

Vamos aos fatos…

Um pouco mais sobre o método Draw

Como explicado no post anterior, o método Draw é o responsável por “colocar as coisas na tela”. Segue o código gerado pelo template padrão do XNA Game Studio:


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

            

Esse método recebe sempre um parâmetro: gameTime. Este parâmetro representa o tempo decorrido desde o início da execução do jogo. Ele é usado para que possamos fazer com que nossas animações tenham a mesma velocidade, mesmo em computadores diferentes (com velocidades diferentes).

No final do método ocorre uma chamada para a implementação da classe base. Por hora, saiba apenas que isso é realmente necessário e essa chamada deve sempre ocorrer (não remova essa chamada em hipótese alguma).

Frame e Framerate

Conforme apresentado no post anterior e na descrição do método Draw, podemos notar que, por padrão, XNA vai limpar e redesenhar a tela toda em cada iteração do Game Loop. Toda cena (imagem na tela) resultante de uma execução do método Draw pode ser designada como “frame”.

Em uma analogia simples, um jogo 2D em XNA é como um daqueles “cartoon flipbooks” onde cada página possui o mesmo desenho da página anterior, porém com uma pequena alteração. Quando você vê uma página após a outra, em sequência, terá a “ilusão” de alguma animação.

O número de frames desenhados em um segundo é chamado de framerate e geralmente é designado como FPS (Frames-per-second). Por exemplo, dizer que um jogo está “rodando” em 60 fps significa dizer que 60 frames estão sendo desenhados a cada segundo. Trazendo para XNA, significa dizer que o Update/Draw está sendo executado 60 vezes em cada segundo.

Content Pipeline

Todos os recursos – como gráficos, sons e efeitos – são carregados no XNA através de um mecanismo chamado Content Pipeline.

Essencialmente, o Content Pipeline pega os arquivos desses recursos e os converte, durante a compilação, para um formato interno do XNA. Na prática, isso permite que nossos códigos não precise conter lógica de carga para recursos de diversos formatos. Além disso, se algo passa pela “importação” do Content Pipeline certamente será “corretamente” tratado durante a execução.

Adicionando recursos ao Content Pipeline

Adicionar recursos ao Content Pipeline é muito simples. Observe o Solution Explorer:

image

Para adicionar recursos ao Content Pipeline, basta adicionar os arquivos relacionados a recursos no projeto direcionado para isso.

Quando você cria um novo projeto XNA, o template do Visual Studio sempre criará dois arquivos: um para seu código, outro para seus recursos. O projeto dos recursos tem o mesmo nome do projeto para códigos e o sufixo “Content”.

Lembre-se que você pode adicionar imagens, vídeos, áudio. Por uma questão de organização, recomendo que você crie uma “pastinha” para cada tipo de conteúdo. Observe:

image

Para conferir se seu arquivo será “entendido” pelo Content Pipeline, verifique as propriedades do arquivo adicionado.

image

A seção identificada como XNA Framework Content Pipeline terá suas propriedades preenchidas apenas se o recurso foi “reconhecido”.

Recuperando um “recurso” da Content Pipeline durante a execução do jogo

Assumindo que você tenha uma imagem carregada e reconhecida no Content Pipeline, estamos prontos para a desenhar na tela. Para isso, precisamos escrever um código para acessar esse recurso.

O tipo utilizado para “armazenar” uma imagem, em XNA, é Texture2D. Para avançar, criamos uma variável desse tipo na classe e carregamos o recurso em LoadContent. Observe:


              
Texture2D texture;
protected override void LoadContent()
{
    spriteBatch = new SpriteBatch(GraphicsDevice);
    texture = Content.Load(@"Images\smurf_sprite");
}

            

Para pintar, modificamos o método Draw. Observe:


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

    spriteBatch.Begin();
    spriteBatch.Draw(texture, Vector2.Zero, Color.White);
    spriteBatch.End();

    base.Draw(gameTime);
}

            

Conforme indicado no post anterior, todo desenho ocorre pelo objeto spriteBatch. Observe o resultado:

image

Como você pode ver, desenhar alguma coisa com XNA começa por uma chamada ao método Begin e termina com o método End do objeto spriteBatch. Para desenhar a textura, passamos como parâmetro:

  • uma textura (Texture2D) que deverá ser desenhada (carregada em LoadContent);
  • uma posição (Vector2) em coordenadas 2D. Em XNA, a coordenada (0, 0) corresponde ao canto superior esquerdo da janela;
  • uma cor (Color) que será “aplicada” na textura. Especificando branco, a imagem não será alterada.

Observe o mesmo exemplo, com vermelho sendo passado no terceiro argumento:

image

Centralizando a figura no centro da tela de jogo, observe o código:


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

    Vector2 pos = new Vector2(
        (Window.ClientBounds.Width - texture.Width) / 2,
        (Window.ClientBounds.Height - texture.Height) / 2
        );

    spriteBatch.Begin();
    spriteBatch.Draw(texture, pos, Color.White);
    spriteBatch.End();

    base.Draw(gameTime);
}

            

Resultado …

image

Há muitas outros parâmetros que permitem “personalizar” o comportamento do Draw.

Animando (Animated Sprites)

Até aqui, todos nossos códigos mostraram como “pintar” uma textura simples na tela. Considere agora esse desenho:

smurf_sprite

Esse desenho é um “Animated Sprite Sheet”. Temos diversos quadros de animação. O segredo para “animar” é desenhar sequencialmente cada quadro. Há classes “especiais” no XNA que nos ajudam a fazer isso, mas hoje vamos fazer “na unha”.

Para começar, definimos algumas propriedades do nosso “Sprite Sheet”. Observe:


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

    Point frameSize = new Point(128, 128);
    Point currentFrame = new Point(0, 0);
    Point sheetSize = new Point(4, 4);
        // ...
}

            

            

Foram apenas três propriedades. Observe:

  • frameSize – tamanho de cada cédula de desenho. Em nosso “sheet” cada cédula tem 128×128 (a imagem total tem 512x 512);
  • currentFrame – indica a célula atual. Utilizo um point para fazer com que X seja a coluna e Y seja a linha;
  • sheetSize – indicação da quantidade de colunas e linhas.

Eis nosso método Draw (lembre-se, só desenho):


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

    Vector2 pos = new Vector2(
        (Window.ClientBounds.Width - frameSize.X) / 2,
        (Window.ClientBounds.Height - frameSize.Y) / 2
        );


    spriteBatch.Begin();
    spriteBatch.Draw(texture, pos,
        new Rectangle(currentFrame.X * frameSize.X,
            currentFrame.Y * frameSize.Y,
            frameSize.X,
            frameSize.Y),
        Color.White, 0, Vector2.Zero, 1, SpriteEffects.None, 0);
    spriteBatch.End();

    base.Draw(gameTime);
}

            

O retângulo corresponde a célula da sheet que desejo desenhar.

Agora, o método Update (lembre-se, todo “cálculo” aqui):


              
protected override void Update(GameTime gameTime)
{
    // Allows the game to exit
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
        this.Exit();

    currentFrame.X++;
    if (currentFrame.X >= sheetSize.X)
    {
        currentFrame.X = 0;
        currentFrame.Y++;
        if (currentFrame.Y >= sheetSize.Y)
            currentFrame.Y = 0;
    }

    base.Update(gameTime);
}

            

A lógica é muito simples. Em cada frame, seleciono a próxima célula da sheet. Quando todas as células foram percorridas, retorno para a primeira célula.

Uma tela de nosso jogo:

image

Você não está vendo a animação, mas ela acontece (baixe o código fonte para ver a coisa acontecendo)

Ajustando o Framerate global

Como já mencionado, framerate é o termo utilizado para descrever quantos frames são desenhados em um segundo. No XNA, o valor default é 60 FPS para PCs e XBox 360 e 30 FPS no Windows Phone 7.

Podemos mudar o framerate padrão do XNA no construtor do jogo (é o lugar para isso). Observe:


              
public Game1()
{
    graphics = new GraphicsDeviceManager(this);
    Content.RootDirectory = "Content";

    this.TargetElapsedTime = new TimeSpan(0, 0, 0, 0, 50);
}

            

Isso diz ao XNA para chamar a cada 50 milisegundos, o que significa 20 fps. Essa simples alteração faz nossa animação rodar bem mais “devagar”.

Vale observar que você não deve mexer nessa propriedade. Você deveria deixar o jogo rodar em 60fps.

Ajustando a velocidade de animação de um sprite na unha

No código que segue, a forma “correta” de ajustar “na unha” a velocidade de animação. Observe:


              
int timeSizeLastFrame = 0;
protected override void Update(GameTime gameTime)
{
    // Allows the game to exit
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
        this.Exit();

    timeSizeLastFrame += gameTime.ElapsedGameTime.Milliseconds;
    if (timeSizeLastFrame > 50)
    {
        timeSizeLastFrame -= 50;
        currentFrame.X++;
        if (currentFrame.X >= sheetSize.X)
        {
            currentFrame.X = 0;
            currentFrame.Y++;
            if (currentFrame.Y >= sheetSize.Y)
                currentFrame.Y = 0;
        }
    }

    base.Update(gameTime);
}

            

Cansei.

Por hoje, era isso.

Smiley piscando

Etiquetado:,
Publicado em: Post