Máquina de Estados

Já se perguntou como os personagens dos seus jogos favoritos conseguem realizar tantas ações diferentes de forma tão suave e organizada? Seja o herói correndo pela floresta, saltando sobre obstáculos ou enfrentando monstros ferozes, há uma lógica inteligente por trás de cada movimento. Essa lógica é orquestrada por algo chamado Máquina de Estados (State Machine).

Pense na Máquina de Estados como o cérebro do personagem no jogo. Ela decide o que o personagem deve fazer em cada momento, garantindo que cada ação – como andar, correr, pular ou atacar – seja executada no momento certo e na sequência correta. Sem essa organização, o personagem poderia se comportar de maneira imprevisível ou até mesmo ficar “confuso”, fazendo várias ações ao mesmo tempo de forma desordenada.

Neste artigo, vamos explorar de forma divertida e prática como implementar uma Máquina de Estados em Unity para controlar o comportamento de um personagem 3D. Vamos acompanhar nosso herói em suas aventuras e ver como cada “modo” de ação é gerenciado, garantindo que ele possa se mover, reagir e enfrentar desafios de maneira fluida e consistente. Preparado para descobrir a magia por trás das decisões dos personagens nos jogos? Vamos lá!

 

Vamos esclarecer as etapas da máquina de estados em termos mais simples e explicando cada parte direitinho. Vamos decompor isso em três elementos principais: Estados, Transições e Entradas/Triggers.

O que é um Estado?

Um estado é uma situação específica ou modo no qual o personagem ou objeto pode estar. Imagine que, no seu jogo, o personagem pode realizar diferentes ações, como:

  • Andar (Walking)
  • Correr (Running)
  • Pular (Jumping)
  • Atacar (Attacking)

Cada uma dessas ações é um estado diferente. O personagem só pode estar em um estado por vez. Por exemplo, ele não pode andar e pular ao mesmo tempo. Quando está em um estado, o personagem faz o que aquele estado permite.

Exemplo:

  • No estado de Andar (Walking), o personagem se move lentamente e executa uma animação de caminhada.
  • No estado de Pular (Jumping), ele sobe no ar e a animação de salto é executada.

O que é uma Transição?

Uma transição é a mudança de um estado para outro. Isso acontece quando algo acontece no jogo – pode ser uma ação do jogador, como pressionar um botão, ou um evento no jogo, como o personagem tocar o chão após um pulo. As transições garantem que o personagem só mude de estado quando faz sentido.

Por exemplo:

  • O personagem está no estado de Andar. Quando o jogador aperta a tecla de pulo (barra de espaço), o personagem transita para o estado de Pular.
  • Após o personagem terminar de pular (quando ele toca o chão), ele pode transitar de volta para o estado de Andar ou para o estado Parado (Idle), se o jogador parar de pressionar a tecla de movimento.

A transição só acontece quando certas condições são atendidas. Exemplo: o personagem só pode pular se ele estiver no chão, e não se já estiver no ar.

O que são Entradas ou Triggers?

As entradas (ou triggers) são os eventos que causam as transições entre estados. Elas podem ser ações do jogador, como pressionar uma tecla, ou podem ser condições no jogo, como o personagem tocar o chão ou atingir um inimigo.

Exemplos de entradas/triggers:

  • Tecla de Pular: Quando o jogador pressiona a barra de espaço, isso dispara a transição para o estado de Pular.
  • Colisão com o Chão: Quando o personagem toca o chão após um pulo, isso pode disparar a transição de volta para o estado de Andar ou Parado.

Essas entradas definem quando e por que o personagem muda de um estado para outro.

 

Resumo das Etapas da Máquina de Estados

  1. Estado:
    • O estado atual em que o personagem se encontra. Ele executa uma ação específica enquanto está nesse estado.
  2. Entrada/Trigger:
    • O evento ou ação que dispara uma transição. Pode ser o jogador apertar uma tecla, uma condição no jogo, etc.
  3. Transição:
    • A mudança de um estado para outro. O personagem deixa de estar no estado atual e entra em um novo estado. Isso ocorre em resposta a uma entrada ou condição.

Um Exemplo Simples em Ação

Vamos usar um exemplo prático no contexto de um personagem que pode andar, correr e pular.

  • O personagem começa no estado Parado (Idle).
    • Entrada: O jogador pressiona a tecla “W”.
    • Transição: O personagem muda para o estado de Andar (Walking).
  • Agora o personagem está no estado de Andar.
    • Entrada: O jogador pressiona a barra de espaço.
    • Transição: O personagem muda para o estado de Pular (Jumping).
  • O personagem está no estado de Pular.
    • Entrada: O personagem toca o chão (condição de colisão com o chão).
    • Transição: O personagem volta para o estado de Andar ou Parado, dependendo se o jogador ainda está pressionando a tecla “W”.

Como Tudo Funciona Junto

  1. Estado: O personagem está em um estado atual (Parado, Andando, Pulando, etc.).
  2. Entrada: O jogador ou o jogo provoca uma ação (pressionar tecla, colisão, etc.).
  3. Transição: O personagem muda de estado, de acordo com as condições ou as ações do jogador.
  4. O ciclo se repete conforme o personagem se move e interage no mundo do jogo.

Essa é a lógica que permite que o personagem responda de forma consistente e organizada no jogo, mantendo cada ação separada (andar, correr, pular, atacar) e garantindo que as transições ocorram de maneira suave e controlada.

Bora codificar isso tudo de uma forma bem simples e rudmentar, eu disse rudimentar mesmo. 

 


public class StateMachine : MonoBehaviour
{
public enum CharacterState { Idle, Walking, Running, Jumping }

public CharacterState currentState;
private Rigidbody rb;
public float walkSpeed = 5f;
public float runSpeed = 8f;
public float jumpForce = 7f;
private bool isGrounded;

// Start is called before the first frame update
void Start()
{
currentState = CharacterState.Idle;
rb = GetComponent<Rigidbody>();
}

// Update is called once per frame
void Update()
{
// Método que lida com a execução do estado atual
ExecuteCurrentState();

// Método que lida com a transição de estados
HandleStateTransitions();
}
// Método para executar a ação associada ao estado atual
private void ExecuteCurrentState()
{
if (currentState == CharacterState.Idle)
{
Idle();
}
else if (currentState == CharacterState.Walking)
{
Walk();
}
else if (currentState == CharacterState.Running)
{
Run();
}
else if (currentState == CharacterState.Jumping)
{
Jump();
}
}

// Métodos para cada estado
private void Idle()
{
// Código para o estado Idle
Debug.Log("Personagem está parado.");
}

private void Walk()
{
Debug.Log("Personagem está andando.");
// Movimentação ao andar
Vector3 move = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
transform.Translate(move * walkSpeed * Time.deltaTime);
}

private void Run()
{
Debug.Log("Personagem está correndo.");
// Movimentação ao correr
Vector3 move = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
transform.Translate(move * runSpeed * Time.deltaTime);
}

private void Jump()
{
if (isGrounded)
{
Debug.Log("Personagem está pulando.");
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
isGrounded = false;
}
}

// Método para lidar com as transições de estados
private void HandleStateTransitions()
{
// Se o personagem estiver no ar, não permitir transições até ele tocar o chão
if (currentState == CharacterState.Jumping && !isGrounded)
{
return; // Não processa outras transições enquanto está pulando
}

// Verificar se o jogador parou de se mover e voltar para o estado Idle
if (currentState == CharacterState.Walking && Input.GetAxis("Vertical") == 0)
{
currentState = CharacterState.Idle;
}
// Verificar se o jogador começou a andar
else if (currentState == CharacterState.Idle && Input.GetAxis("Vertical") != 0)
{
currentState = CharacterState.Walking;
}
// Verificar se o jogador começou a correr
else if (currentState == CharacterState.Walking && Input.GetKey(KeyCode.LeftShift))
{
currentState = CharacterState.Running;
}
// Verificar se o jogador parou de correr e voltou a andar
else if (currentState == CharacterState.Running && !Input.GetKey(KeyCode.LeftShift))
{
currentState = CharacterState.Walking;
}
// Verificar se o jogador tentou pular
else if (Input.GetKeyDown(KeyCode.Space) && isGrounded)
{
currentState = CharacterState.Jumping;
}
}

void OnCollisionEnter(Collision collision)
{
// Detecta se o personagem tocou o chão
if (collision.gameObject.CompareTag("Ground"))
{
isGrounded = true;
currentState = Input.GetAxis("Vertical") != 0 ? CharacterState.Walking : CharacterState.Idle;
}
}
}

Agora que entendemos o básico da Máquina de Estados e como ela organiza os comportamentos dos personagens, estamos prontos para codificar esse sistema de forma prática. Começamos com algo simples e, à medida que avançarmos, adicionaremos mais complexidade e funcionalidades ao código. Vamos continuar explorando juntos para criar interações cada vez mais sofisticadas!