[XNA] Animação de Sprites – Parte 3
Eai galera, esta é a penúltima parte do tutorial, mas já com esta etapa você já poderão construir seus objetos ou personagens animados. Neste tutorial iremos criar uma classe chamada AnimatedSprite que irá gerenciar objetos animados, ou seja objetos criados com aquela classe do tutorial passado.
O que está classe realmente faz então, esta classe armazena um dicionário de nomes para chamar cada animação que poderá ser adicionada a ele. O método Update navega entre os quadros da animação atual conforme um intervalo de tempo determinado por cada animação. E o método Draw desenha o objeto.
A classe que gerencia as animações (AnimatedSprite) pode ser vista abaixo.
public class AnimatedSprite
{
#region [ Fields ]
/// <summary>
/// Sprite sheet da imagem animada
/// </summary>
protected Texture2D texture;
/// <summary>
/// Posição do objeto animado no mundo
/// </summary>
public Vector2 Position { get; set; }
/// <summary>
/// Dicionário contendo as animações do personagem
/// </summary>
public Dictionary<string, Animation> Animations
{
get { return this.animations; }
}
private Dictionary<string, Animation> animations = new Dictionary<string, Animation>();
/// <summary>
/// Indice do frame da animação em execução
/// </summary>
public int FrameIndex
{
get { return this.frameIndex; }
}
private int frameIndex = 0;
/// <summary>
/// Nome da animação
/// </summary>
private string animationKey;
public string AnimationKey
{
get { return this.animationKey; }
}
/// <summary>
/// Cor de desenho da imagem
/// </summary>
protected Color color = Color.White;
/// <summary>
/// Rotação da imagem
/// </summary>
protected float rotation = 0.0f;
/// <summary>
/// Origem do desenho
/// </summary>
protected Vector2 origin;
/// <summary>
/// Escala da imagem
/// </summary>
protected float scale = 1.0f;
/// <summary>
/// Efeitos de imagem
/// </summary>
public SpriteEffects Flip
{
get { return this.flip; }
set { this.flip = value; }
}
protected SpriteEffects flip = SpriteEffects.None;
/// <summary>
/// Layer de desenho
/// </summary>
protected float layerDepth = 0.0f;
/// <summary>
/// Tempo decorrido do frame atual da animação
/// </summary>
private float timeElapsed = 0.0f;
/// <summary>
/// Lista com os quadros do sprite sheet
/// </summary>
public List<Rectangle> Frames
{
get { return this.frames; }
}
private List<Rectangle> frames = new List<Rectangle>();
/// <summary>
/// Largura do quadro
/// </summary>
private int frameWidth = 0;
/// <summary>
/// Altura do quadro
/// </summary>
private int frameHeight = 0;
/// <summary>
/// Tamanho do frame
/// </summary>
public Vector2 Size
{
get { return new Vector2(frameWidth, frameHeight); }
}
#endregion
#region [ Constructor ]
/// <summary>
/// Construtor do objeto animado
/// </summary>
/// <param name="texture">Imagem (Sprite Sheet)</param>
/// <param name="columns">Quantidade de colunas</param>
/// <param name="rows">Quantidade de linhas</param>
public AnimatedSprite(Texture2D texture, int columns, int rows)
{
this.texture = texture;
this.Position = new Vector2();
this.frameWidth = texture.Width / columns;
this.frameHeight = texture.Height / rows;
// Cria todos os quadros da imagem
for (int i = 0; i < rows; i++)
for (int j = 0; j < columns; j++)
frames.Add(new Rectangle(j * frameWidth,
i * frameHeight, frameWidth, frameHeight));
this.origin = new Vector2(frameWidth / 2, frameHeight / 2);
}
#endregion
#region [ Update ]
/// <summary>
/// Atualiza a animação do objeto
/// </summary>
/// <param name="gameTime">Tempo de jogo</param>
public void Update(GameTime gameTime)
{
timeElapsed += (float)gameTime.ElapsedGameTime.TotalSeconds;
if (timeElapsed > animations[AnimationKey].Interval)
{
if (animations[AnimationKey].IsLooping)
{
frameIndex = (frameIndex + 1) % animations[AnimationKey].FramesCount;
}
else
{
frameIndex = (int)MathHelper.Min(frameIndex + 1, animations[AnimationKey].FramesCount - 1);
}
timeElapsed = 0.0f;
}
}
#endregion
#region [ Draw ]
/// <summary>
/// Desnha o objeto animado
/// </summary>
/// <param name="spriteBatch"></param>
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(texture,
Position,
Animations[AnimationKey].Frames[frameIndex],
color,
rotation,
origin,
scale,
flip,
layerDepth);
}
#endregion
#region [ Add Animations ]
/// <summary>
/// Adiciona novas animações
/// </summary>
/// <param name="name">Nome chave da animação</param>
/// <param name="newAnimation">Animação definida</param>
public void AddAnimation(string name, Animation newAnimation)
{
animations.Add(name, newAnimation);
}
#endregion
#region [ Play Animation ]
/// <summary>
/// Inicia ou continua uma animação.
/// </summary>
public void PlayAnimation(string name)
{
// Se a animação for a mesma em execução, não reinicia a animação
if (name == AnimationKey)
return;
// Inicia uma nova animação
this.animationKey = name;
this.frameIndex = 0;
this.timeElapsed = 0.0f;
}
#endregion
}
A classe Game1.cs deve ficar da seguinte forma para testar nosso exemplo.
public class Game1 : Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
/// <summary>
/// Entrada atual do teclado
/// </summary>
///
KeyboardState keyState = Keyboard.GetState();
/// <summary>
/// Entrada antiga do teclado
/// </summary>
KeyboardState oldKeyState = Keyboard.GetState();
/// <summary>
/// Personagem animado
/// </summary>
AnimatedSprite player;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
// Cria o personagem
player = new AnimatedSprite(Content.Load<Texture2D>("claudius"), 6, 4);
// Posiciona o personagem na tela
player.Position = new Vector2(100, 100);
// Baixo
// Cria as animações para baixo
Animation stopedDownAnimation = new Animation(player.Frames, 0, 2);
player.AddAnimation("StopedDown", stopedDownAnimation);
// Cria as animações para baixo andando
Animation walkingDownAnimation = new Animation(player.Frames, 2, 4);
walkingDownAnimation.IsLooping = true;
player.AddAnimation("WalkingDown", walkingDownAnimation);
// Esquerda
// Cria as animações para esquerda
Animation stopeLeftAnimation = new Animation(player.Frames, 6, 2);
player.AddAnimation("StopedLeft", stopeLeftAnimation);
// Cria as animações para esquerda andando
Animation walkingLeftAnimation = new Animation(player.Frames, 8, 4);
walkingLeftAnimation.IsLooping = true;
player.AddAnimation("WalkingLeft", walkingLeftAnimation);
// Cima
// Cria as animações para cima parado
Animation stopedUpAnimation = new Animation(player.Frames, 12, 2);
player.AddAnimation("StopedUp", stopedUpAnimation);
// Cria as animações para cima andando
Animation walkingUpAnimation = new Animation(player.Frames, 14, 4);
walkingUpAnimation.IsLooping = true;
player.AddAnimation("WalkingUp", walkingUpAnimation);
// Direita
// Cria as animações para direita parado
Animation stopedRightAnimation = new Animation(player.Frames, 18, 2);
player.AddAnimation("StopedRight", stopedRightAnimation);
// Cria as animações para direita andando
Animation walkingRightAnimation = new Animation(player.Frames, 20, 4);
walkingRightAnimation.IsLooping = true;
player.AddAnimation("WalkingRight", walkingRightAnimation);
// Inicializa a animação do personagem parado a direita
player.PlayAnimation("StopedRight");
}
protected override void Update(GameTime gameTime)
{
oldKeyState = keyState;
keyState = Keyboard.GetState();
if (keyState.IsKeyDown(Keys.Right))
{
player.PlayAnimation("WalkingRight");
player.Position += new Vector2(60.0f, 0.0f) * (float)gameTime.ElapsedGameTime.TotalSeconds;
}
else if (keyState.IsKeyUp(Keys.Right) && oldKeyState.IsKeyDown(Keys.Right))
{
player.PlayAnimation("StopedRight");
}
else if (keyState.IsKeyDown(Keys.Left))
{
player.PlayAnimation("WalkingLeft");
player.Position += new Vector2(-60.0f, 0.0f) * (float)gameTime.ElapsedGameTime.TotalSeconds;
}
else if (keyState.IsKeyUp(Keys.Left) && oldKeyState.IsKeyDown(Keys.Left))
{
player.PlayAnimation("StopedLeft");
}
else if (keyState.IsKeyDown(Keys.Up))
{
player.PlayAnimation("WalkingUp");
player.Position += new Vector2(0.0f, -60.0f) * (float)gameTime.ElapsedGameTime.TotalSeconds;
}
else if (keyState.IsKeyUp(Keys.Up) && oldKeyState.IsKeyDown(Keys.Up))
{
player.PlayAnimation("StopedUp");
}
else if (keyState.IsKeyDown(Keys.Down))
{
player.PlayAnimation("WalkingDown");
player.Position += new Vector2(0.0f, 60.0f) * (float)gameTime.ElapsedGameTime.TotalSeconds;
}
else if (keyState.IsKeyUp(Keys.Down) && oldKeyState.IsKeyDown(Keys.Down))
{
player.PlayAnimation("StopedDown");
}
// Atualiza o personagem
player.Update(gameTime);
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
// Desenha o personagem
player.Draw(spriteBatch);
spriteBatch.End();
base.Draw(gameTime);
}
}
Para ver como ficou o resultado veja o vídeo abaixo.
Se quiser fazer download do código-fonte do tutorial clique aqui.
Abraços galera, os próximos tutoriais serão sobre Camera 2D para começarmos a montar um jogo de plataforma.
Eaew Kleber =D
mto legal o resultado…
já dá para montar um rpgzinho =D ou pelo menos um joguinho de labirinto. hehe!!
abraços T+
A idéia é justamente esta, além de animar personagens você pode animar objetos como por exemplo arvores, lagos e etc.
Logo mostrarei um exemplo criando a classe do Sonic e aplicando a idéia de Máquina de Estados Finitos para fazer suas animações usando estas classes.
Abraços,
Kleber tem como fazer com gif Animado??
Olá Diego então o XNA não oferece suporte nativo ao formato .gif. Entretanto, ele permite que nós possamos extender o pipeline de conteudo para suporta qualquer formato não-nativo. Existe um projeto que implementa este formato neste link http://xnagif.codeplex.com/, nunca testei mas de uma olhada.
Abraços,
Muito bem explicado, porem o xna não oferece suporte a java, oque é uma pena, mais c-Sharp não parece ser tão complicado assim, em vb.net eu consigo desenvolver um sprite apenas utilizando o picturebox, juntamente com um valor do tipo bitmap, com muito menos código que isso aew, achei que a plataforma xna, iria simplificar mais, porem estou vendo que os codigos para fazer algo simples continuam infinitamente grandes, isso creio que e, para mover um personagem em x e y, agora se eu quizer implementar golpes, poderes em meu personagem talvez com 2 mil linhas de código eu conseguiria né ??? =D!!!
Olá amigo, obrigado.
Sobre precisar de tantas linhas de código é assim mesmo, o XNA facilita muito se você comparar com a quantidade de linhas de código usados para desenvolver em DirectX. Mas é claro que toda a lógica você tem que implementar do seu personagem, isso em qualquer framework ou engine. É possível fazer uma animação com menos linhas de código, porém o código não fica nada reaproveitavel.
Com certeza eles serão muito reaproveitáveis, creio que definindo esses códigos ja da para criar uma engine bacana. Achei legal a sua atitude não existe muitas pessoas devotada a explicar alguns conceitos de desenvolvimento de games em XNA no Brasil principalmente. É muito escasso o desenvolvimento de games no Brasil, porem são pessoas assim com atitude como você que o Brasil precisa para quem sabe ter nossos próprios desenvolvedores de games, ja existem Brasileiros que programam em grandes empresas no desenvolvimento de games, fico muito grato pelo conhecimento que esta nos propondo, e espero que o Brasil possa ter pessoas assim como você com muito conhecimento com intuito de ajudar, aqueles novatos que estão apenas caminhando, pois quem se aventura no mundo dos games tem que suar bastante porque não é nada facil.
Obrigado amigo, acredito que hoje em dia tem muitas pessoas que disponibilizam o seu conhecimento e isso é muito legal. Então eu comecei a alguns anos fazendo isso com jogos pois realmente era uma área com poucas coisas em português.
Sobre criar uma engine se encontrar mais pessoas interessadas ou tiver um grande interesse em fazer isso, podemos começar uma engine que já tenho idéia e muitos códigos chamada “Hope Game Engine 2D”, o que você acha?
Abraços,
É verdade, estive observando em alguns blogues e existem muito material, estou me arriscando um pouco em c-sharp, encontrei um blog muito bacana assim como o seu, e o autor disponibiliza o codigo fonte que pode ser muito reaproveitavel, e para conhecimento segue o link, se quizer verificar, trata-se de um jogo de nave, muito complexo, no qual usa uma nave e a possibilidade em arrecadar mineiros, com isso o jogador tera que enfrentar a gravidade a medida em que se altera os planetas, e tambem existe a possibilidade de desabilitar novas naves, e o melhor de tudo que pode gravar o seu statos atual no jogo, muito bacana segue o link http://www.cubagames.com.br/criando-um-jogo-em-xna-%e2%80%93-parte-7-%e2%80%93-conclusao/ .. e sobre criar uma engine, é uma boa idéia..
Olá, estou começando a brincar com XNA agora, ja segui todos os seus tutoriais e comecei a pesquisar coisas novas, mas ja me deparei com mtos tutoriais para XNA 4.0 e pelo visto o 4.0 e 3.1 não se dão muito bem entre eles na compatibilidade, será que compensa eu continuar no 3.1 ou ja investir no 4.0 ?
Olá, então a diferença é pouca… ai você tem que ver o que mais lhe agrada, por exemplo, eu continuo usando somente a versão 3.1 por causa do VS 2010 que eu não gostei, e para usar o XNA 4.0 teria que ser no VS2010.
Abraços,
olá, tou testando o seu código mas não sei o que por na parte public List Frames tá me dando um erro, gostaria de uma ajuda
Olá amigo que tipo de erro esta dando?
Abraços,
Acho q ta faltando o . Ficaria então List Frames
“List” Frames
O Erro do public List Frames é esse aqui: “Using the generic Type ‘System.Collections.Generic.List requires ’1′ Type arguments “
Vou tentar verificar o erro até sabado! obrigado
Kleber, como eu faço colisoes entres AnimatedSprite?
No começo eu até tava levando, mas depois começo a ter tanta coisa, mas tanta coisa que me perdi geral e nem sabia mais oque erá oque XD, acho que vou ficar com a parte gráfica da coisa, programação não é pra mim y.y