Uma State Machine (Máquina de Estados) é um padrão essencial para controlar mudanças de estado de forma organizada e previsível. Em jogos, isso é comumente utilizado para gerenciar estados de personagens, inimigos e menus. No backend, podemos aplicá-la para gerenciar workflows, como estados de pedidos em um e-commerce.

Neste artigo, exploraremos uma State Machine desacoplada utilizando ScriptableObjects no Unity para jogos e como essa mesma estrutura pode ser aplicada no desenvolvimento backend. Além disso, veremos como ela se integra ao nosso Event Bus, abordado no artigo anterior.


Estruturando a State Machine no Unity

O objetivo da nossa implementação é permitir que estados sejam definidos de forma modular e reutilizável, sem acoplamento direto entre eles.

1. Criando a Base da State Machine

Primeiro, criamos a State Machine, responsável por gerenciar transições entre estados:

using UnityEngine;

namespace NoxStudios.Core.StateMachine
{
    public class StateMachine : MonoBehaviour
    {
        private BaseState _currentState;

        private void Update() => _currentState?.OnUpdate();
        private void FixedUpdate() => _currentState?.OnFixedUpdate();

        public void SetState(BaseState newState, bool reset = false)
        {
            if(newState == null || (_currentState == newState && !reset)) return;
            
            _currentState?.ExitState();
            _currentState = newState;
            newState.EnterState();
        }
    }
}

Essa classe controla os ciclos de vida dos estados e garante que a transição seja feita de forma ordenada.

2. Criando a Base para os Estados

Agora, definimos a classe base dos estados, que todos os estados concretos irão herdar:

using UnityEngine;

namespace NoxStudios.Core.StateMachine
{
    public abstract class BaseState : ScriptableObject
    {
        public bool IsComplete { get; set; }

        public virtual void EnterState() { }
        public virtual void OnUpdate() { }
        public virtual void OnFixedUpdate() { }
        public virtual void ExitState() { }
    }
}

Na Unity, o uso de Scriptable Objects permite reutilizar estados entre diferentes entidades, garantindo melhor organização e desacoplamento.

3. Criando um Estado Concreto

Aqui, criamos um exemplo de estado, o IdleState, que representa um personagem parado:

using UnityEngine;
using NoxStudios.Core.StateMachine;

[CreateAssetMenu(menuName = "NoxStudios/StateMachine/IdleState", fileName = "IdleStateSO")]
public class IdleStateSO : BaseState
{
    public override void EnterState() => Debug.Log("Entrou no Idle State");
    public override void OnUpdate() => Debug.Log("Update do Idle State");
    public override void ExitState() => Debug.Log("Saiu do Idle State");
}

Os estados podem representar diversas situações, como andar, pular, atacar, entre outros.

4. Integração com o Event Bus

Para garantir um fluxo de eventos desacoplado, usamos nosso Event Bus para comunicar entradas do jogador com a State Machine:

using NoxStudios.Core.EventBus;
using NoxStudios.Core.EventBus.Events;
using UnityEngine;

namespace NoxStudios.Player
{
    public class PlayerInputHandler : MonoBehaviour
    {
        public void OnMove(InputAction.CallbackContext context) => HandleInput(context);
        public void OnJump(InputAction.CallbackContext context) => HandleInput(context);
        public void OnAttack(InputAction.CallbackContext context) => HandleInput(context);

        private static void HandleInput(InputAction.CallbackContext context)
        {
            if (context.canceled)
            {
                EventBus<PlayerInputEvent>.Publish(new PlayerInputEvent(true));
                return;
            }
            EventBus<PlayerInputEvent>.Publish(new PlayerInputEvent(context));
        }
    }
}

Agora, a State Machine do Player reage aos eventos publicados pelo Event Bus e troca de estado conforme necessário.


Aplicação da State Machine no Backend

A ideia de uma State Machine pode ser aplicada em backend para workflows complexos, como o estado de um pedido em um sistema de e-commerce.

1. Definição dos Estados

Criamos uma interface para garantir um comportamento consistente entre os estados:

public interface IOrderState
{
    void HandleOrder(OrderContext context);
}

Agora, criamos estados concretos para um pedido:

public class PendingState : IOrderState
{
    public void HandleOrder(OrderContext context)
    {
        Console.WriteLine("Pedido pendente");
        context.SetState(new ProcessedState());
    }
}

public class ProcessedState : IOrderState
{
    public void HandleOrder(OrderContext context)
    {
        Console.WriteLine("Pedido processado");
        context.SetState(new ShippedState());
    }
}

public class ShippedState : IOrderState
{
    public void HandleOrder(OrderContext context)
    {
        Console.WriteLine("Pedido enviado");
    }
}

2. Gerenciador de Estados (Contexto)

O OrderContext gerencia as transições de estado:

public class OrderContext
{
    private IOrderState _currentState;

    public OrderContext(IOrderState initialState)
    {
        _currentState = initialState;
    }

    public void SetState(IOrderState newState)
    {
        _currentState = newState;
        _currentState.HandleOrder(this);
    }
}

3. Uso da State Machine no Backend

Podemos utilizá-la para processar pedidos:

var order = new OrderContext(new PendingState());
order.SetState(new ProcessedState());

Isso garante que cada pedido siga um fluxo estruturado, assim como os estados de um personagem em um jogo.


Conclusão

A implementação de uma State Machine desacoplada com integração ao Event Bus garante flexibilidade tanto para jogos quanto para sistemas backend. No Unity, isso permite uma arquitetura mais organizada, enquanto no backend, torna workflows complexos mais previsíveis.

Se você deseja criar sistemas escaláveis e bem estruturados, vale a pena considerar o uso de máquinas de estado e Event Bus para desacoplar eventos e transições de estado.

Matheus Mendes

Competências