Estrutura Modular Leve para Desenvolvimento em Unity

Esta estrutura simplificada para Unity foi concebida para agilizar o desenvolvimento de jogos, especialmente protótipos e projetos de pequena escala, minimizando o código repetitivo e promovendo uma arquitetura limpa e modular. Ela oferece um conjunto de módulos essenciais para gerenciamento de UI, entrada, recursos, áudio, pools de objetos e persistência de dados, todos orquestrados através de um sistema de singletons e um barramento de eventos.

Estrutura de Diretórios

A organização dos arquivos segue uma convenção clara para facilitar a navegação e manutenção do projeto:

Assets/Scripts/FrameworkBase/
├─ core/                 # Gerenciamento de Singletons
│  ├─ SingletonPadrao.cs
│  └─ SingletonMonoComportamento.cs
├─ UI/                   # Gerenciamento de UI e Painéis Base
│  ├─ GerenciadorDeUI.cs
│  └─ PainelBase.cs
├─ Eventos/              # Sistema de Eventos
│  └─ GerenciadorDeEventos.cs
├─ Entrada/              # Interface Unificada de Entrada
│  └─ GerenciadorDeEntrada.cs
├─ CicloDeVida/          # Ponto Central para Ciclo de Vida Mono
│  └─ ControladorMono.cs
├─ Recursos/             # Carregamento de Recursos (Síncrono/Assíncrono/StreamingAssets)
│  └─ GerenciadorDeRecursos.cs
├─ Pools/                # Pool de Objetos
│  ├─ PoolDeObjetos.cs
│  └─ DadosDoPool.cs
├─ Audio/                # Gerenciamento de Música e Efeitos Sonoros
│  └─ GerenciadorDeAudio.cs
├─ Persistencia/         # Armazenamento de Dados (PlayerPrefs/JSON)
│  ├─ PersistenciaPP.cs
│  ├─ PersistenciaJSON.cs
│  └─ UtilitarioDeSerializacao.cs
└─ Constantes/           # Definições Centralizadas de Constantes/Eventos
   └─ ConstantesDoJogo.cs

Visão Geral da Arquitetura

O conceito central é a interação de módulos desacoplados através de singletons e um sistema de evantos. O ControladorMono atua como o ponto de entrada principal para atualizações e corrotinas.

+-----------------------+        +------------------+
|  ControladorMono      | <----> |  Loop de Update  |
| (SingletonMono)       |        +------------------+
+----------+------------+
           |
           v
+----------+------------+     +------------------+     +------------------+
|  GerenciadorDeUI      | <--> |  GerenciadorDe   | <--> | GerenciadorDe    |
| (Camadas de Painel)   |     |  Eventos         |     | Entrada          |
+----------+------------+     | (Barramento de   |     | (Eventos de      |
           |                  |  Eventos)        |     | Entrada)         |
           v                  +------------------+     +------------------+
+----------+------------+
|  GerenciadorDe        |
|  Recursos             |
| (Carregamento)        |
+----------+------------+
           |
           v
+----------+------------+     +------------------+     +------------------+
|  PoolDeObjetos        |     | GerenciadorDe    |     | Persistencia     |
| (Reutilização de      |     | Audio            |     | De Dados         |
| Objetos)              |     | (BGM/Efeitos)    |     | (PlayerPrefs/Json)|
+-----------------------+     +------------------+     +------------------+

A estrutura hierárquica da UI no Canvas é organizada em camadas para garantir a ordem de renderização:

Canvas Principal (tag: CanvasPrincipal)
├─ camadaInferior    # sort order = 0
├─ camadaMedia       # sort order = 20
├─ camadaSuperior    # sort order = 40
└─ sistema           # sort order = 60

Módulos Principais e Suas Responsabilidades

  • Classes Base de Singleton:
    • SingletonPadrao<T>: Singletons que não herdam de MonoBehaviour.
    • SingletonMonoComportamento<T>: Singletons que herdam de MonoBehaviour, com criação automática e DontDestroyOnLoad.
  • Gerenciamento de UI:
    • GerenciadorDeUI: Responsável por encontrar/carregar automaticamente o CanvasPrincipal e o EventSystem, gerenciar camadas de painéis, filas de animação e adaptação de tela.
    • PainelBase: A classe base para todos os painéis de UI, oferecendo mecanismos para coletar controles, atribuir callbacks de botões/toggles e gerenciar animações específicas do painel.
  • Sistema de Eventos:
    • GerenciadorDeEventos: Fornece um barramento de eventos type-safe para assinar, disparar e remover ouvintes.
  • Gerenciamento de Entrada:
    • GerenciadorDeEntrada: Unifica a verificação de entrada, permitindo ativar/desativar o processamento de entrada e disparar eventos para cliques do mouse e a tecla Escape.
  • Centro Mono:
    • ControladorMono: Atua como um ponto centralizado para a execução do Update e para o gerenciamento de corrotinas.
  • Carregamento de Recursos:
    • GerenciadorDeRecursos: Lida com o carregamento síncrono/assíncrono de recursos via Resources e a leitura de arquivos de texto de StreamingAssets.
  • Pool de Objetos:
    • PoolDeObjetos / DadosDoPool: Implementa um sistema de pool para reutilização eficiente de objetos, organizado por nome, minimizando a alocação de memória e o GC.
  • Gerenciamento de Áudio:
    • GerenciadorDeAudio: Controla a reprodução de músicas de fundo (BGM) e efeitos sonoros (SFX), incluindo gerenciamento de volume e reciclagem de fontes de áudio.
  • Persistência de Dados:
    • PersistenciaPP: Permite a serialização de objetos complexos (incluindo IList/IDictionary) para PlayerPrefs usando reflexão.
    • PersistenciaJSON: Gerencia a serialização de objetos para arquivos .json no persistentDataPath do aplicativo.
    • UtilitarioDeSerializacao: Estende o JsonUtility do Unity para suportar a serialização de List<T> e Dictionary<TKey, TValue>.
  • Constantes Centralizadas:
    • ConstantesDoJogo: Define nomes de eventos, enums e outras constantes importantes em um único local.

Benefícios e Escolhas de Design

  • Modularidade Leve: Cada módulo tem uma responsabilidade bem definida, com baixo acoplamento, facilitando substituições e expansões.
  • Entradas Globais Unificadas: O barramento de eventos, a abstração de entrada e o carregamento de recursos são centralizados, simplificando a lógica do projeto.
  • Camadas de UI Padronizadas: A organização em camadaInferior, camadaMedia, camadaSuperior e sistema garante a ordem de renderização e suporta o fechamento de painéis empilhados com a tecla Escape.
  • PainelBase: Base para painéis de UI, automatizando a coleta de controles, a vinculação de callbacks de botões/toggles e o gerenciamento de animações.
  • Filas de Animação: Gerencia a reprodução sequencial de animações de painéis, com a opção de pular animações.
  • Pool de Objetos e Reutilização de Recursos: Reduz a necessidade de instanciar e destruir objetos frequentemente, minimizando a pressão do garbage collector.
  • Persistência de Dados Flexível: Oferece opções de armazenamento via PlayerPrefs e JSON, com suporte para estruturas de dados complexas.

Início Rápido (Cinco Passos)

  1. Prepare o Canvas Principal e o Sistema de Eventos:
    • Coloque um MainCanvas e um EventSystem na cena e atribua as tags CanvasPrincipal e SistemaDeEventosPrincipal, respectivamente.
    • Alternativamente, forneça prefabs nomeados CanvasPrincipal.prefab e SistemaDeEventos.prefab em Resources/UI/ para carregamento automático.
  2. Crie Prefabs de Painéis:
    • Os recursos de painel devem ser armazenados em Resources/UI/ por padrão (caminho configurável no GerenciadorDeUI), com o nome SeuNomeDePainel.
    • O script do painel deve herdar de PainelBase. É recomendado adicionar um CanvasGroup e atribuí-lo ao campo grupoCanvas para controle de interação.
  3. Defina o Ponto de Entrada do Jogo:
    • Em qualquer MonoBehaviour ou SingletonMonoComportamento, chame GerenciadorDeUI.Instancia.ExibirPainel<T>("NomeDoPainel") para mostrar um painel.
  4. Abstraia Entrada e Eventos (Opcional):
    • Ative a verificação de entrada com GerenciadorDeEntrada.Instancia.AtivarVerificacaoDeEntrada(true).
    • Utilize o GerenciadorDeEventos para a comunicação entre módulos.
  5. Gerencie Recursos e Áudio:
    • Use GerenciadorDeRecursos para o carregamento unificado.
    • Utilize GerenciadorDeAudio para reprodução de BGM e SFX.

Padrões Comuns e Exemplos de Uso

Exibir/Ocultar Painéis

// Exibir (carrega Resources/UI/MeuPainel por padrão)
GerenciadorDeUI.Instancia.ExibirPainel<MeuPainel>("MeuPainel");

// Especificar camada e prefixo de caminho de recurso personalizado
GerenciadorDeUI.Instancia.ExibirPainel<OutroPainel>("OutroPainel", GerenciadorDeUI.CamadaUI.Topo, null, "PrefixosUIPersonalizados/");

// Ocultar/Destruir
GerenciadorDeUI.Instancia.OcultarPainel("MeuPainel");

// Obter um painel existente
MeuPainel painelExemplo = GerenciadorDeUI.Instancia.ObterPainel<MeuPainel>("MeuPainel");

Coleta de Controles e Callbacks em Painéis

public class MeuPainel : PainelBase
{
    protected override void AoClicarBotao(string nomeBotao)
    {
        if (nomeBotao == "BotaoFechar")
        {
            GerenciadorDeUI.Instancia.OcultarPainel("MeuPainel");
        }
    }

    void ExemploToggle()
    {
        Toggle meuToggle = ObterControle<Toggle>("MeuToggle");
        // ... usar meuToggle
    }
}

Barramento de Eventos (Type-Safe)

// Assinar
GerenciadorDeEventos.Instancia.AdicionarOuvinte<int>("EventoDeTeste", ProcessarDadosEvento);

// Disparar
GerenciadorDeEventos.Instancia.DispararEvento<int>("EventoDeTeste", 123);

// Remover
GerenciadorDeEventos.Instancia.RemoverOuvinte<int>("EventoDeTeste", ProcessarDadosEvento);

void ProcessarDadosEvento(int valor)
{
    Debug.Log($"Evento recebido com valor: {valor}");
}

Entrada Unificada

GerenciadorDeEntrada.Instancia.AtivarVerificacaoDeEntrada(true); // Habilita a escuta de entrada
// A tecla ESC já é tratada pelo GerenciadorDeUI para fechar o painel superior.

Fila de Animações da UI (Requer DOTween)

protected override void AoExibir()
{
    Sequence sequenciaAnimacao = DOTween.Sequence();
    sequenciaAnimacao.Append(grupoCanvas.DOFade(1, 0.2f));
    AdicionarAnimacaoUI(sequenciaAnimacao, interativo: false);
}

Carregamento de Recursos

GameObject objetoUI = GerenciadorDeRecursos.Instancia.Carregar<GameObject>("UI/MeuPainel"); // Síncrono

GerenciadorDeRecursos.Instancia.CarregarAssincrono<AudioClip>("sons/clique", clip =>
{
    // Usar o clip de áudio carregado
}); // Assíncrono

string textoLido = GerenciadorDeRecursos.Instancia.CarregarDeStreamingAssets("caminho/do/arquivo.txt"); // Leitura de texto

Pool de Objetos

PoolDeObjetos.Instancia.ObterObjeto("Prefabs/Tiro", obj =>
{
    // Configurar e usar 'obj'
}, pai: algumaTransformacao, assincrono: true);

PoolDeObjetos.Instancia.ArmazenarObjeto("Tiro", objetoRetornado); // Devolver ao pool

Áudio

GerenciadorDeAudio.Instancia.ReproduzirBGM("musica/bgm_principal");
GerenciadorDeAudio.Instancia.ReproduzirSFX("sons/efeito_explosao");
GerenciadorDeAudio.Instancia.AlterarVolumeSFX(0.7f);
GerenciadorDeAudio.Instancia.PararBGM();

Persistência de Dados (Objetos Complexos via PlayerPrefs)

DadosDoJogador dados = new DadosDoJogador { Nivel = 10, Nome = "Herói" };
PersistenciaPP.Instancia.SalvarDados(dados, "Jogador"); // Escrever

object objetoCarregado = PersistenciaPP.Instancia.CarregarDados(typeof(DadosDoJogador), "Jogador");
DadosDoJogador dadosCarregados = objetoCarregado as DadosDoJogador; // Ler

Armazenamento JSON

DadosDoJogador dadosJson = new DadosDoJogador { Nivel = 15, Nome = "Mago" };
PersistenciaJSON.Instancia.SalvarJson(dadosJson, "dados_mago");

DadosDoJogador magoCarregado = PersistenciaJSON.Instancia.CarregarJson<DadosDoJogador>("dados_mago");
PersistenciaJSON.Instancia.ExcluirJson("dados_mago");

Auxílio para Adaptação de Tela

float proporcaoTela = GerenciadorDeUI.ProporcaoAspecto;
float escalaLargura = GerenciadorDeUI.EscalaLargura;
float escalaAltura = GerenciadorDeUI.EscalaAltura;

Sugestões e Considerações de Uso

  • Garanta que uma Camera.main esteja presente na cena. O GerenciadorDeUI configurará o Canvas para ScreenSpaceCamera e o vinculará à câmera principal.
  • Caso não posicione manualmente o CanvasPrincipal e o EventSystem na cena, certifique-se de que os prefabs correspondentes estejam em Resources/UI/ com os nomes esperados.
  • Recomenda-se que o nó raiz do painel seja um RectTransform para o correto funcionamento das configurações de anchorMin/Max e sizeDelta.
  • Para animações de painéis cmoplexas, utilize AdicionarAnimacaoUI para gerenciar interatividade e sequenciamento, evitando conflitos de animação.
  • Definir semBotaoEsc = true em um PainelBase impede que ele seja fechado pela lógica de detecção da tecla Escape.
  • Ao reutilizar objetos de um pool, garanta que o estado do objeto seja redefinido (posição, escala, ativação, etc.) antes de devolvê-lo. O framework já reinicia a localScale para Vector3.one ao retirar um objeto.
  • As regras de nomeação para persistência de dados (chave_Tipo_TipoCampo_NomeCampo) via PlayerPrefs são sensíveis; alterar nomes de campos pode resultar em falhas de leitura.
  • A tipagem dos eventos no GerenciadorDeEventos deve ser consistente. Se um evento com o mesmo nome for registrado com um tipo diferente, o tipo original será substituído.

Sugestões para Extensão

  • Centralize nomes de eventos e constantes importantes em ConstantesDoJogo.
  • Estenda o framework adicionando novos módulos, como um GerenciadorDeCenas ou GerenciadorDeRede, seguindo o padrão SingletonPadrao<T> ou SingletonMonoComportamento<T>.
  • Ao integrar bibliotecas de animação de UI mais avançadas, mantenha a integração através de PainelBase para gerenciamento unificado de filas.

Perguntas Frequentes

  • P: É possível não ter o CanvasPrincipal/EventSystem na cena?
    • R: Sim, o GerenciadorDeUI pode carregar automaticamente prefabs com esses nomes de Resources/UI/.
  • P: Como posso impedir que um painel seja fechado pela tecla Escape?
    • R: Defina semBotaoEsc = true na classe do seu PainelBase.
  • P: Como carregar múltiplos painéis e reproduzir suas animações em sequência?
    • R: Cada painel deve usar AdicionarAnimacaoUI para enfileirar suas animações, e o GerenciadorDeUI orquestrará a reprodução sequencial através de sua fila interna.

Considerações Futuras

  • Avaliar a integração de um sistema de salvamento de dados robusto como o Easy Save 3, como alternativa ou complemento à implementação de PlayerPrefs.
  • Explorar a inclusão de um sistema de Injeção de Dependência para aumentar ainda mais a modularidade e testabilidade.

Tags: Unity C# framework Singletons UI

Publicado em 8-1 08:31