segunda-feira, 12 de outubro de 2015

Programação de Games - FSM Parte 1

Bem vindos!

Este é o primeiro tutorial de uma série sobre programação de games.

O objetivo da série é apresentar de forma simples alguns conceitos e estratégias de programação que são utilizados em games mas que não são muito citados em outros tutoriais.

Nesta série utilizarei a engine Unity 5 por ser simples e amplamente utilizada no desenvolvimento de games. Caso você não tenha experiência com a engine, recomendo a leitura de alguns tutoriais no site da própria Unity, que tem materiais bem completos e simples de entender.

Vamos criar nosso primeiro jogo e com ele, nosso primeiro conceito - Máquina de estados finitos

BattleArena
Nosso game Battle Arena é um jogo de aventura e combate em 2D onde nosso herói é desafiado por diversos inimigos.


Cada inimigo tem um estilo de combate diferente, isso torna o jogo agradável e a jogabilidade cada vez mais difícil pois os inimigos ficam mais inteligentes conforme as fases vão evoluindo.
No decorrer dos tutoriais vamos desbravar a produção do game construindo inimigo por inimigo.

Mas antes dos inimigos, vamos construir nosso herói e entender um pouco da mecânica de uma Máquina de Estados Finitos.

Nos próximos passos vamos montar o personagem dentro do Unity e preparar suas animações. Vou detalhar estes passos com o Herói e, para os próximos personagens, vamos seguir as mesmas instruções de montagem se focando na funcionalidade ao invés das animações. Caso você já saiba como criar as animações utilizando o Animator Controller no Unity, pode pular para a parte 2 deste tutorial onde utilizaremos o conceito de Máquina de Estados Finitos para controlar o personagem.

Vamos criar nosso personagem utilizando o Character Generator uma ferramenta bem bacana que gera sprites de animações para nosso personagem. Após criar seu personagem da maneira que achar melhor salve-o no formato PNG.

Crie um projeto novo em 2D no Unity 5 e arraste a imagem gerada pelo Character Generator para o Unity, ele vai colocar a imagem na aba Project.
--IMAGEM TELA UNITY

Inicialmente o Unity coloca sua imagem como um objeto único (sprite) onde a imagem por completo representará um único objeto na tela, porém vamos mudar esta opção para que possamos utilizar todas as animações contidas na imagem para nosso personagem.

Selecione a imagem e, no menu "Inspector" mude a opção "Sprite Mode" para "Multiple". Coloque também a unidade de pixel do sprite para 30 para aumentarmos um pouco o tamanho do nosso personagem e clique no botão "Apply".

--IMAGEM INSPECTOR

Em seguida clique no botão "Sprite Editor" para acessar o editor de recortes do Unity. Nele poderemos recortar as imagens para servirem de animação para nosso personagem. O Unity contém uma forma ágil para recortar as imagens de nossa animação que você consegue acessar pelo meu "Slice".
--IMAGEM SPRITE EDITOR

Clique em Slice e escolha para a opção Pivot a opção "bottom" para ficar mais fácil de ajustar o posicionamento de cada animação. Ao clicar no botão "Slice" da janela, você poderá ver que os sprites foram devidamente recortados com quadrados simbolizando os recortes. Cada recorte poderá ser utilizado individualmente dentro do Unity. Vamos ajustar o posicionamento de cada imagem para as animações. Reparem no ponto Pivot das imagens abaixo:
-- IMAGEM PIVOT

Durante animações, este alinhamento pode fazer o personagem "dançar" na tela, dando um efeito incorreto para as animações. Para corrigirmos este alinhamento é necessário o ajuste do Pivot, para isso selecione a imagem recortada e, no menu inferior, em "Pivot", selecione a opção "Custom" utilize o slider para posicionar o ponto de Pivot para o meio das pernas do seu personagem (mantendo sempre alinhado com os pés). Repita a operação com todas as imagens que você achar necessário.
-- IMAGEM PIVOT AJUSTADO

Ao final da edição dos sprites, clique no botão "Apply" menu superior do sprite editor para gerar todos os recortes das imagens.

Criando as animações.
Para criar as animações, inicialmente vamos nos preocupar com o movimento e o ataque do personagem e, como ele vai andar em todas as direções, vamos criar todas as animações aproveitando o máximo a nossa imagem de sprites. Os nomes das animações são auto-explicativos, e vamos utilizar as letras T,L,R,B para indicar os tipos de posições para as animações
  • Herói de costa visto de cima (Top)
  • Herói virado para a esquerda (Left)
  • Herói virado para a direita (Right)
  • Herói virado para baixo (Bottom)
Criaremos o seguinte lote de animações
  • HeroiAndando_(T,L,B,R)
  • HeroiAtacando_(T,L,B,R)
  • HeroiParado_(T,L,B,R)
Para criar a animação HeroiAndando_T selecione as imagens que representam o Herói Andando de costas e arraste-as para sua cena (Scene). O Unity já vai criar a animação para você e a salvará como um arquivo heroi_.anim (ou o nome do seu sprite com a extensão anim). Para organizar nosso projeto vamos renomear a imagem para HeroiAndando_T e subsequentemente fazer isso para todas as outras animações.

Além do arquivo heroi.anim o unity cria um controlador da animação que devemos excluir pois vamos criar um único controlador para todas as animações mais para frente.

Prossiga com a criação de todas as animações para o herói andando, atacando e, para o herói parado, vamos criar uma animação utilizando as duas primeiras imagens do movimento de magia do nosso personagem. Desta forma mesmo quando o personagem está parado ele terá uma animação.

Após criadas todas as animações podemos continuar com a programação de nosso personagem e a máquina de estados finitos.
Mas o que é uma Máquina de Estados Finitos afinal?

É um sistema simples de execução de funções de forma linear, onde eu tenho um estado e, após uma execução de alguma ação sou levado a outro estado. E como isso é representado em nosso game?

Podemos utilizar a Máquina de Estados Finitos ou FSM (Finite State Machine) para controlar nosso personagem no game.

Imagine que nosso personagem inicialmente andará pela tela com as teclas do teclado ou com o direcional do joystick e, ao apertar um botão ele atacará o inimigo. Podemos representar estas ações com a imagem a seguir:



Na imagem acima, cada caixa representa um estado do jogador e as setas representam as ações para realizar a transição entre um estado e outro. Basicamente as ações transcritas funcionam da seguinte forma:
  • Inicialmente estou no estado "Parado"
  • Se executo a ação de "Andar", vou para o estado "Andando"
  • Se executo a ação de "Parar de Andar" vou para o estado "Parado"
  • Se executo a ação de "Atacar" e estou no estado "Parado" vou para o estado "Atacando"
  • Após o término do ataque, volto para o estado anterior, no caso "Parado" 
  • Se estou no estado "Andando" e executo a ação de "Atacar" vou para o estado "Atacando"
  • Após o término do ataque, volto para o estado anterior, no caso "Andando"
Maquina de estados 1 - Controle de Animações
Antes de iniciar vamos limpar nossa cena deixando somente a Main Camera. Após isso selecione uma imagem do personagem parado de frente e arraste para sua cena como a imagem a seguir.
-- IMAGEM SELECIONANDO A CENA

Nomeie nosso personagem como "Heroi".

Primeiramente vamos criar nosso controle de animação AnimatorController. Com o botão direito na sua pasta do projeto selecione
Create | Animation Controller

Nomeie o controlador para HeroiAC para identificarmos que este será o Animation Controller do nosso Herói.

Arraste o controlador HeroiAC para nosso Herói na cena e em seguida clique duas vezes no controlador para abrir o painel do AnimationController

O Animation Controller no Unity funciona como uma Máquina de Estados Finitos para controlar a animação. Nele colocamos os estados finitos de uma máquina de estados e em seguida colocamos parâmetros para controlar transições entre estes estados.

Se voltarmos ao nosso diagrama de estados podemos identificar os estados e transições:



No diagrama temos 3 estados distintos sendo.

  • Parado
  • Andando
  • Atacando 

Identificamos também duas variáveis de transição onde se uma delas estiver "ligada" eu executo a transição entre um estado e outro.

  • Andar
  • Atacar
Podemos representar estes itens no AnimationController adicionando os parâmetros Andar e Atacar como "bool"  além dos parâmetros vamos adicionar os estados no AnimationController, para isso criamos um estado com o botão direito e selecionamos 

CreateState | From New BlendTree

Após criar o estado renomeie-o como "Parado" acordo com nosso diagrama  e repita o processo para todos os outros estados. 

Em seguida vamos criar as transições, inicialmente entre o estado "Parado" e "Andando" para isso selecione o estado "Parado" e, com o botão direito clique em "Make Transition", em seguida clique no estado "Andando". Precisamos configurar a transição para obedecer a nossa variavel "Andar", para isso vamos criar uma condição (condition) em que o estado de Parado para Andando será ativado quando "Andar" será ligado (true)

--Imagem de criar a transição

Vamos fazer o mesmo processo para voltar do estado "Andando" para o estado "Parado", criando uma nova transição. Selecione o estado "Andando" com o botão direito e em seguida clique em "Make Transition". Adicione uma condição selecionando a variável "Andar" porém, desta vez vamos alterar o seu valor para deligado (false).

Para o estado de ataque, vamos criar as transições mas vamos colocar duas condições para que o controle de animação entenda que estamos saindo do estado de "Andando" ou "Parado" para o "Atacando" e consiga voltar para estes estados corretamente. 

Crie uma transição ("Make Transition") do estado "Andando" para o estado "Atacando" e nas suas condições, adicione as duas variáveis representando que se o personagem estiver andando e atacar irá realizar a transição para o estado de ataque.
  • Andar = true
  • Atacar  = true
Na transição de volta do estado "Atacando" para o estado "Andando" vamos utilizar as condições
  • Andar = true
  • Atacar = false
Onde representa que se eu ainda estou andando e "Paro de atacar" volto para o estado "Andando"

O mesmo fazemos com as transições do estado "Parado" para o estado "Atacando" e vice versa configurando as variáveis como acima porém com Andar = false. 

Além dos estados, precisamos configurar também quais as animações de cada estado serão ativadas de acordo com a posição do personagem, por exemplo: 
Se ele estiver andando para cima utilizaremos a animação "HeroiAndando_T" e para baixo utilizaremos a "HeroiAndando_B", por conta disso criamos os estados como "BlendTrees". 

BlendTrees são como um controle interno do estado que permite que possamos controlar quais das diversas animações de Top, Left, Right e Bottom de cada estado temos que utilizar. Se clicarmos duas vezes no estado "Andando" vamos acessar sua BlendTree que se utiliza de um parâmetro "blend" para transitar entre uma animação e outra. Vamos mudar o nome deste parâmetro para X e criarmos um novo parâmetro Y para termos o controle correto das nossas animações internas. 

Clique duas vezes no parâmetro "blend" no menu de parametros a esquerda e modifique a variável para "x". Esta variável diferente de nossas variaveis  "Andar" e "Atacar" é uma variável float que permite um controle de 0 a 1 para nossa transição.
Vamos adicionar mais uma variável "y" para configurarmos o controle de animação para saber qual animação quando o personagem andar horizontalmente ou verticalmente pela tela.  
No menu "parameters" clique no simbolo "+" e crie uma variável Float. Nomeie a váriavel como "y".
--IMAGEM Adicionar parametro

Agora que temos as variáveis x e y vamos colocá-la em nossa "BlendTree" selecionando no lado direito a opção Blend Type para "2D Freeform Cartesian". Após isso selecionamos as variáveis "x" e "y" para que a BlendTree use como referência. 

--Imagem de utilizar como referência.

Vamos adicionar a primeira animação "HeroiAndando_T" representando um movimento para cima.
Adicionamos um "Motion Field" clicando no "+" na opção "Motion". O "Motion Field" irá escolher a animação com base nos parâmetros selecionados. "Blend Tree" selecionado volte para a pasta Project, selecione a imagem "HeroiAndando_T" e arraste a imagem para o None (Motion) à direita no inspector. Configure os valores de x para 0 e y para 1. Isso significa que quando tivermos X=0 e Y=1 o nosso BlendTree liagará esta animação no nosso personagem. 

Vamos fazer o mesmo para todas as outras posições selecionando todas as imagens do HeroiAndando Com as informações conforme abaixo:

--- Imagem com as informações. 

Repare que o movimento irá obedecer uma cruz no plano cartesiano, onde cada imagem será ativada quando tivermos na ponta do plano cartesiano. 

Teremos que fazer a mesma configuração para todas as animações nos outros estados. desta forma completaremos a exibição de nosso personagem. Para isso podemos voltar para a raiz de nosso AnimatorController clicando em "Base Layer", desta forma voltaremos para a máquina de estados e podemos acessar os outros estados. 

Para testarmos as animações, podemos selecionar o personagem na cena e em seguida clicar novamente na BlendTree, o personagem vai aparecer ao lado esquerdo e podemos clicar no botão "Play" para ativar suas animações.

Ao movimentarmos os sliders das variáveis veremos as transições das animações do personagem. 
--Imagem do personagem andando ao mudar o slider. 

Por ter 2 frames, a animação do estado "Parado" é muito rápida, podemos controlar sua velocidade voltando para a raiz da BlendTree. Clique uma vez no estado "Parado" e em seguida defina o parâmetro "speed" para 0.1

-- Imagem do speed

Ajustando as Transições.
Na máquina de estados do AnimatorController o Unity tenta suavizar as animações de transições entre estados por meio de ajustes do tempo de animação. Como estamos trabalhando com trocas de animações por imagens estes tempos de transição devem ser imediatos ou podem causar efeitos de lags quando transitamos entre um estado e outro.. 
--Imagem do tempo de animação

Para ajustarmos os tempos de transição, selecionamos a transição entre um estado e outro, desligamos a flag "Has Exit Time" para que independente de onde a animação estiver executando a troca será realizada, expandimos a aba "Settings" e colocamos o valor de 0 em Transition Duration. Desta forma os tempos de transição estarão corretos e nossa animação correrá corretamente. 
-- Imagem do ajuste de tempo de animação

Temos que realizar esta tarefa em todas as tranisções de estados, para que não exista diferenças entre animações. Falarei um pouco mais destas transições de animações em um outro tutorial, por hora vamos apenas desabilita-las. 

Adicionando um Fundo
Vamos adicionar um fundo para que nosso personagem não flutue andando por aí. Para isso baixe o arquivo abaixo e coloque ele na sua pasta Project. Assim como fizemos com o conjunto de imagens do Heroi, vamos definir o parâmetro PixelUnit do sprite para 30 e clicar no botão Apply.

Ao colocar o fundo em sua cena. A ordem do fundo ficará errada fazendo com que o Heroi desapareça abaixo dele. Para corrigirmos este problema basta configurarmos as camadas do jogo (layers). 
Clique no botão "Layers" e em seguida clique em "Edit Layers...".
Expanda a aba "Sorting Layers" e crie uma camada chamada "Background". Posicione a camada "Background" para que fique antes de "Default".
Agora selecione o fundo e no componente "Sorting Layer" selecione a camada "Background". Desta forma o fundo ficará posicionado corretamente. 

No próximo tutorial vamos dar sequência em controlar o personagem com máquinas de estado finitos assim como fizemos com as animações.

Clique aqui para acessar a segunda parte do nosso tutorial