Usando o Unity em jogos 2D

Pra alguns, usar o Unity para jogos 2D é o equivalente a usar um canhão pra matar mosca. Eu discordo. Durante o Global Game Jam deste ano nós fizemos justamente isso e acho que o restante das funcionalidades do Unity (o editor visual, o desenvolvimento orientado a componentes, a facilidade pra testar e a física integrada) ajudou muito. É claro que precisamos lidar com um trabalho extra pra trabalhar com imagens e especialmente animar o personagem.

A solução implementada nesse projeto foi criar um material para cada quadro de animação e um array de materiais contendo as animações. Assim, a gente podia chamar uma animação pelo nome no código e o gerenciador de sprites se responsabilizava trocar o material, alternando a textura do objeto exibido na tela. É uma solução boa e funcionou bem, principalmente se levarmos em conta que foi implementada em um projeto e, ao todo, durou menos de 48 horas.

Mas essa não é uma solução livre de problemas. Criar um material para CADA quadro e ainda ter que arrastar todos os quadros na ordem certa pra lista de animação é um processo bem trabalhoso. Além disso, imagens separadas gastam mais espaço na memória e spritesheets são bastante usados em jogos, então uma implementação que contemple seu uso seria interessante.

Eu pesquisei rapidamente algumas soluções de 2D para o Unity, mas o que eu achei ou era simples demais, complicado demais ou pago. Sim, eu sou um chato e não gostei de nada que encontrei (exceto por uma ferramenta paga que parece muito bom, mas não quis arriscar a compra E acabei por perder o link). Então resolvi fazer minha própria implementação de um sistema de animacão 2D pro Unity.

As animações usam o um spritesheet que contém todos os quadros de animação de um objeto. Como entrada, o componente recebe a largura e altura de cada quadro de animação (nesse formato todos os quadros devem ter largura e altura iguais) e índice do quadro que se deseja exibir. Com isso, o tiling da textura é ajustado para exibir somente o equivalente a um quadro e quando o índice do quadro é alterado os offsets são alterados para buscar exatamente a parte da textura que deve ser exibida. Isso, é claro, fica transparente ao usuário. As únicas entradas necessárias são mesmo o número de tiles em cada eixo e o quadro atual. O resto é feito de forma automática.

Esse já é um resultado legal, mas eu queria também uma forma simples de manipular o índice do quadro, podendo definir uma lista de quadros que formam uma animação. Assim, eu poderia dizer, por exemplo, que os quadros de 0 a 3 formam a animação “movimento para a esquerda” e chamar esta animação pelo nome quando a seta esquerda for pressionada.

E como eu gosto de deixar as coisas o mais genérico possível, fiz dois modelos de animação: sequencial e personalizado. No primeiro caso, basta dizer qual o primeiro e o último quadro e a animação vai sendo tocada trocando cada quadro em ordem (uma animação entre os quadros 1 e 5, por exemplo, tocaria os quadros 1, 2, 3, 4 e 5). Na segunda forma, basta passar uma string com os quadros de animação separados por vírgula e eles serão tocados na ordem definida (por exemplo, ao passar a string “5,3,4,2″, os quadros serão exibidos na sequencia 5, 3, 4 e 2). Há também um controle que define a velocidade de animação e se ela deve se repetir ao chegar no final.

Chamar uma animação via código fica muito simples. No exemplo abaixo, eu chamo as animações “5,4,3,2″ (alterna entre os quadros que mostram essa sequência de números na tela) e “2-5″ (que mostra os números de 2 a 5), conforme configurados na imagem acima.

void Update()
{
    if (Input.GetKeyDown(KeyCode.Space))
    {
        spriteSheet.PlayCustomAnimation("5,4,3,2");
        spriteSheet.PlaySequencialAnimation("2-5");
    }
}

Pra fechar, também um incluí uma forma de chamar um método sempre que uma animação acabar. Isso é interessante pra encadear animações diferentes ou realizar qualquer ação que pode ser disparada ao término de uma animação (um exemplo seria uma animação do personagem tomando dano, que quando acaba deve chamar novamente à animação normal).

Usando os delegates do C#, essa funcionalidade é implementada de forma muito fácil. Basta definir um método que recebe como parâmetro um SpriteAnimation e atribuir à animação este método. No exemplo abaixo, primeiro o definimos um método que imprime o nome da animação que acaba de terminar e depois buscamos na lista de animações aquela que queremos que execute o método, atribuindo o método em questão à sua lista de delegates.

// Método executado quando a animação termina
private void AnimationEndExample(SpriteAnimation animation)
{
    Debug.Log(string.Format("Animation: {0} is finished.", animation.name));
}
 
// Busca a animação desejada na lista de animações e atribui o delegate acima
SpriteAnimation anim1 = spriteSheet.FindAnimation("my_animation", 
    spriteSheet.customAnimations);
anim1.onAnimationEnd += 
    new SpriteAnimation.OnAnimationEnd(AnimationEndExample);

Estou disponibilizando um projeto com alguns exemplos a mais e os scripts necessários para usar este gerenciador de animações. Quem se interessar, pode fazer o download neste link.

Até a próxima.

Bookmark the permalink.

2 Comments

  1. vou testar assim que me derem um espaço hehehehe :D

  2. Pingback: Criando um FPS old school no Unity » Diego Barboza

Comments are closed