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 deMonoBehaviour.SingletonMonoComportamento<T>: Singletons que herdam deMonoBehaviour, com criação automática eDontDestroyOnLoad.
- Gerenciamento de UI:
GerenciadorDeUI: Responsável por encontrar/carregar automaticamente oCanvasPrincipale oEventSystem, 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 doUpdatee para o gerenciamento de corrotinas.
- Carregamento de Recursos:
GerenciadorDeRecursos: Lida com o carregamento síncrono/assíncrono de recursos viaResourcese a leitura de arquivos de texto deStreamingAssets.
- 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 (incluindoIList/IDictionary) paraPlayerPrefsusando reflexão.PersistenciaJSON: Gerencia a serialização de objetos para arquivos.jsonnopersistentDataPathdo aplicativo.UtilitarioDeSerializacao: Estende oJsonUtilitydo Unity para suportar a serialização deList<T>eDictionary<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,camadaSuperioresistemagarante 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
PlayerPrefse JSON, com suporte para estruturas de dados complexas.
Início Rápido (Cinco Passos)
- Prepare o Canvas Principal e o Sistema de Eventos:
- Coloque um
MainCanvase umEventSystemna cena e atribua as tagsCanvasPrincipaleSistemaDeEventosPrincipal, respectivamente. - Alternativamente, forneça prefabs nomeados
CanvasPrincipal.prefabeSistemaDeEventos.prefabemResources/UI/para carregamento automático.
- Coloque um
- Crie Prefabs de Painéis:
- Os recursos de painel devem ser armazenados em
Resources/UI/por padrão (caminho configurável noGerenciadorDeUI), com o nomeSeuNomeDePainel. - O script do painel deve herdar de
PainelBase. É recomendado adicionar umCanvasGroupe atribuí-lo ao campogrupoCanvaspara controle de interação.
- Os recursos de painel devem ser armazenados em
- Defina o Ponto de Entrada do Jogo:
- Em qualquer
MonoBehaviourouSingletonMonoComportamento, chameGerenciadorDeUI.Instancia.ExibirPainel<T>("NomeDoPainel")para mostrar um painel.
- Em qualquer
- Abstraia Entrada e Eventos (Opcional):
- Ative a verificação de entrada com
GerenciadorDeEntrada.Instancia.AtivarVerificacaoDeEntrada(true). - Utilize o
GerenciadorDeEventospara a comunicação entre módulos.
- Ative a verificação de entrada com
- Gerencie Recursos e Áudio:
- Use
GerenciadorDeRecursospara o carregamento unificado. - Utilize
GerenciadorDeAudiopara reprodução de BGM e SFX.
- Use
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.mainesteja presente na cena. OGerenciadorDeUIconfigurará o Canvas paraScreenSpaceCamerae o vinculará à câmera principal. - Caso não posicione manualmente o
CanvasPrincipale oEventSystemna cena, certifique-se de que os prefabs correspondentes estejam emResources/UI/com os nomes esperados. - Recomenda-se que o nó raiz do painel seja um
RectTransformpara o correto funcionamento das configurações deanchorMin/MaxesizeDelta. - Para animações de painéis cmoplexas, utilize
AdicionarAnimacaoUIpara gerenciar interatividade e sequenciamento, evitando conflitos de animação. - Definir
semBotaoEsc = trueem umPainelBaseimpede 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
localScaleparaVector3.oneao retirar um objeto. - As regras de nomeação para persistência de dados (
chave_Tipo_TipoCampo_NomeCampo) viaPlayerPrefssão sensíveis; alterar nomes de campos pode resultar em falhas de leitura. - A tipagem dos eventos no
GerenciadorDeEventosdeve 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
GerenciadorDeCenasouGerenciadorDeRede, seguindo o padrãoSingletonPadrao<T>ouSingletonMonoComportamento<T>. - Ao integrar bibliotecas de animação de UI mais avançadas, mantenha a integração através de
PainelBasepara gerenciamento unificado de filas.
Perguntas Frequentes
- P: É possível não ter o
CanvasPrincipal/EventSystemna cena?- R: Sim, o
GerenciadorDeUIpode carregar automaticamente prefabs com esses nomes deResources/UI/.
- R: Sim, o
- P: Como posso impedir que um painel seja fechado pela tecla Escape?
- R: Defina
semBotaoEsc = truena classe do seuPainelBase.
- R: Defina
- P: Como carregar múltiplos painéis e reproduzir suas animações em sequência?
- R: Cada painel deve usar
AdicionarAnimacaoUIpara enfileirar suas animações, e oGerenciadorDeUIorquestrará a reprodução sequencial através de sua fila interna.
- R: Cada painel deve usar
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.