Apostila de Unity para Aplicações: além dos jogos
Boa parte do Unity que roda hoje não é jogo: é o gêmeo digital de uma fábrica, o configurador de um carro, o simulador de treino de uma empresa de energia, a visualização de um edifício antes de ele existir. Esse tipo de projeto usa o mesmo motor, mas tem outras regras — sessões de oito horas, dados vindos de sistemas reais, modelos CAD com milhões de polígonos, interfaces densas, e um cliente que mede sucesso por decisão tomada, não por diversão. Esta apostila trata dessas regras.
Pressupõe o básico do editor (GameObject, componente, prefab, cena). Se ainda não tem isso, comece por Unity Essencial e, para a trilha de jogos, Unity do zero ao mercado. Para C# em si, a apostila de C#. Publicar no navegador está em Serious Games na Web. As versões citadas são as do Unity 6, a série atual à data desta apostila.
Unity fora dos jogos: onde e por quê
Objetivo: saber em que tipos de aplicação o Unity é a escolha certa, em quais é um exagero caro, e o que muda em relação a fazer um jogo.
1.1 Os territórios reais
| Território | O que se constrói | Por que um motor de jogo |
|---|---|---|
| Gêmeo digital (digital twin) | Réplica 3D de uma fábrica, linha, edifício ou rede, alimentada por dados reais | Renderizar muitos objetos em tempo real e reagir a dados a cada segundo |
| Visualização AEC (arquitetura, engenharia, construção) | Passeios por projetos, revisão de BIM, estudos de insolação | Iluminação de qualidade e navegação livre sem pré-renderizar |
| Configuradores de produto | Escolher cor, acabamento e opcionais de carro, móvel, máquina | Materiais realistas trocados instantaneamente, em várias plataformas |
| Treino e simulação | Procedimentos de manutenção, segurança, operação de equipamentos | Física, interação e cenários repetíveis sem risco real |
| AR/VR corporativo | Instruções sobrepostas ao equipamento, revisão de projeto em escala 1:1 | O ecossistema de XR mais maduro (OpenXR, XR Interaction Toolkit) |
| Produção virtual e broadcast | Cenários virtuais em tempo real, grafismo de TV | Renderização em tempo real sincronizada com câmera |
1.2 Quando não usar Unity
Se a aplicação é essencialmente formulários, listas e texto com um visualizador 3D pequeno num canto, Unity é a ferramenta errada: você paga o tamanho do runtime, o arranque lento, uma acessibilidade mais pobre que a nativa e uma UI mais difícil de fazer do que em web ou nativo. Nesses casos, faça a app em web/nativo e embuta o 3D (Three.js, ou Unity como biblioteca — módulo 7). Regra prática: Unity quando o 3D é o produto; outra coisa quando o 3D é um acessório.
1.3 O que muda em relação a um jogo
| Dimensão | Jogo | Aplicação |
|---|---|---|
| Sessão | Minutos a poucas horas | Um turno inteiro; app aberta o dia todo em quiosque ou sala de controle |
| Fonte da verdade | O próprio jogo | Sistemas externos (ERP, SCADA, BIM, APIs) — o Unity é só uma vista |
| Conteúdo | Modelado para o motor | Vem de CAD/BIM, pesado e cheio de detalhe inútil (módulo 5) |
| Frame rate | Máximo possível, sempre | Alto quando há interação, quase zero quando parado (módulo 6) |
| Interface | HUD mínimo | Painéis densos, tabelas, filtros, localização (módulo 3) |
| Critério de sucesso | Diversão, retenção | Correção dos dados, decisão tomada, tempo de treino reduzido |
1.4 Licenças, em uma frase cada
- Personal: gratuita até um teto de receita/financiamento anual da empresa; o teto muda, confira na página oficial de planos.
- Pro / Enterprise: por assento, obrigatória acima do teto e necessária para alguns recursos (por exemplo, o suporte a visionOS via PolySpatial exigia licença paga à data desta apostila).
- Unity Industry: o pacote para uso industrial, que inclui ferramentas de importação de CAD (Pixyz) e licenciamento próprio para distribuir aplicações não-jogo.
A taxa por instalação (Runtime Fee) anunciada em 2023 foi cancelada em setembro de 2024; o modelo voltou a ser por assento. Isto importa porque muito conteúdo online ainda discute a taxa como se estivesse em vigor.
Vagas de "Unity developer" em indústria, AEC, automotivo e saúde raramente pedem game design — pedem C# de qualidade de software empresarial, integração com APIs e dados, e otimização de conteúdo pesado. Quem vem de jogos e mostra um projeto com dados reais e arquitetura limpa se destaca muito mais do que com o quinto jogo de plataforma.
✏️ Exercício 1 — Unity ou não?
Para cada pedido, decida se Unity é boa escolha: (a) app de inspeção com checklist de 40 perguntas e uma foto; (b) sala de controle que mostra 3 000 sensores de uma refinaria sobre o modelo 3D; (c) configurador de cozinha planejada com troca de acabamentos em tempo real; (d) catálogo de produtos com um botão "ver em 3D" num dos itens.
Gabarito: (a) Não — formulário é trabalho de web/nativo. (b) Sim — o 3D e os dados em tempo real são o produto. (c) Sim — materiais realistas trocados ao vivo são o motivo de usar um motor. (d) Não para a app inteira; o visualizador pode ser um componente web (glTF + Three.js ou <model-viewer>) ou, se já existir, Unity como biblioteca.
Arquitetura de aplicação
Objetivo: sair do padrão "um MonoBehaviour faz tudo", separar regra de negócio de apresentação e montar uma estrutura que aguente dois anos de manutenção.
2.1 O problema do MonoBehaviour-para-tudo
Tutoriais ensinam a colocar lógica, estado e acesso a dados no mesmo componente pendurado num GameObject. Num jogo pequeno funciona. Numa aplicação, o resultado é previsível: regra de negócio que só roda com a cena aberta, impossível de testar sem Play Mode, FindObjectOfType espalhado, e uma mudança na API do servidor que obriga a editar vinte componentes visuais.
2.2 Três camadas
| Camada | Contém | Depende do Unity? |
|---|---|---|
| Domínio | Entidades e regras: "um alarme é crítico se a temperatura passar de X por mais de Y segundos" | Não. C# puro, testável fora do editor |
| Aplicação / serviços | Casos de uso, acesso a dados, clientes de API, estado da sessão | Pouco (só onde precisa de UnityWebRequest, por exemplo) |
| Apresentação | MonoBehaviours, UI, materiais, animações — mostrar o estado e capturar o input | Sim |
A regra que sustenta tudo: as dependências apontam para dentro. A apresentação conhece o domínio; o domínio nunca conhece a apresentação. Na prática, isso se impõe com Assembly Definitions (.asmdef): um assembly App.Domain sem referência a UnityEngine (opção "No Engine References") torna fisicamente impossível importar coisa do motor ali dentro — e ainda acelera a compilação.
2.3 MVP / MVVM com componentes finos
// Domínio — C# puro, sem using UnityEngine public sealed class Sensor { public string Id { get; } public double Valor { get; private set; } public double Limite { get; } public bool EmAlarme => Valor > Limite; public event System.Action<Sensor> Mudou; public Sensor(string id, double limite) { Id = id; Limite = limite; } public void Atualizar(double v) { Valor = v; Mudou?.Invoke(this); } } // Apresentação — só traduz estado em visual public sealed class SensorView : MonoBehaviour { [SerializeField] Renderer indicador; [SerializeField] Color normal = Color.green, alarme = Color.red; Sensor _sensor; public void Ligar(Sensor s) { _sensor = s; s.Mudou += Redesenhar; Redesenhar(s); } void OnDestroy() { if (_sensor != null) _sensor.Mudou -= Redesenhar; } void Redesenhar(Sensor s) => indicador.material.color = s.EmAlarme ? alarme : normal; }
(1) Sempre desinscrever eventos no OnDestroy: um evento de domínio segurando um componente destruído vaza memória e lança MissingReferenceException horas depois — em app de sessão longa, isso aparece. (2) renderer.material cria uma cópia do material por objeto; com 3 000 sensores são 3 000 materiais e o batching morre. Para trocar só a cor, use MaterialPropertyBlock ou propriedades por instância (módulo 6).
2.4 Composição e injeção de dependência
Alguém precisa criar o cliente de API, o repositório e os serviços e entregá-los a quem precisa. Em vez de singletons estáticos, use um composition root: um único ponto que monta o grafo de objetos no arranque. Bibliotecas de DI feitas para Unity, como VContainer (leve, rápida) ou Zenject/Extenject (mais antiga, mais recursos), fazem isso e injetam nas MonoBehaviours da cena. Em projetos pequenos, um composition root escrito à mão num único MonoBehaviour de bootstrap é perfeitamente suficiente.
2.5 Estado da aplicação e cenas aditivas
- Máquina de estados de alto nível: Login → Carregando projeto → Navegação → Modo inspeção → Relatório. Cada estado sabe entrar e sair; nada de flags booleanas espalhadas.
- Cenas aditivas: uma cena "Core" persistente (serviços, câmera, UI raiz) e cenas de conteúdo carregadas e descarregadas com
SceneManager.LoadSceneAsync(..., LoadSceneMode.Additive). Trocar de projeto não reinicia os serviços. - ScriptableObjects servem bem como configuração (limites, URLs por ambiente, paletas) editável no Inspector. Cuidado ao usá-los como estado mutável em tempo de execução: no editor, alterações feitas em Play Mode persistem no asset.
✏️ Exercício 2 — Onde mora cada coisa?
Classifique em domínio, aplicação ou apresentação: (a) regra "manutenção vencida se passaram 180 dias da última"; (b) chamada HTTP que busca o histórico de manutenções; (c) piscar o equipamento em laranja quando a manutenção está vencida; (d) guardar o projeto aberto por último para reabrir no dia seguinte.
Gabarito: (a) Domínio — regra pura, testável sem Unity. (b) Aplicação — serviço de acesso a dados. (c) Apresentação — tradução visual do estado. (d) Aplicação — estado da sessão, persistido por um serviço (que por baixo pode usar PlayerPrefs ou um arquivo em Application.persistentDataPath).
Interface: UI Toolkit e uGUI
Objetivo: escolher o sistema de UI certo para painéis densos, ligar a UI aos dados sem código repetitivo, e tratar localização e acessibilidade desde o início.
3.1 Os dois sistemas
| uGUI (Canvas) | UI Toolkit | |
|---|---|---|
| Modelo | GameObjects com componentes (Image, Text, Button) | Árvore de elementos descrita em UXML, estilizada com USS (parecido com HTML/CSS) |
| Brilha em | UI no mundo 3D, UI em XR, efeitos e animações por componente | Interfaces densas de aplicação: painéis, formulários, listas longas, temas |
| Listas longas | Precisa de virtualização feita à mão | ListView/MultiColumnListView virtualizados nativamente |
| Ligação a dados | Código manual | Data binding de runtime (Unity 6) |
| Maturidade | Estável há muitos anos, vasta documentação | O sistema recomendado para novas UIs de tela; algumas lacunas em UI no espaço 3D |
Para aplicações, a escolha típica é UI Toolkit para a interface de tela (painéis, menus, tabelas) e uGUI (ou UI Toolkit com render texture) para rótulos presos a objetos 3D e para XR. Misturar os dois no mesmo projeto é normal.
3.2 UXML + USS, em miniatura
<!-- PainelSensor.uxml --> <ui:UXML xmlns:ui="UnityEngine.UIElements"> <ui:VisualElement class="painel"> <ui:Label name="titulo" class="painel__titulo" /> <ui:Label name="valor" class="painel__valor" /> <ui:Button name="reconhecer" text="Reconhecer alarme" /> </ui:VisualElement> </ui:UXML> /* PainelSensor.uss */ .painel { padding: 12px; background-color: rgb(21, 34, 44); border-radius: 8px; } .painel__valor { font-size: 28px; -unity-font-style: bold; } .painel--alarme .painel__valor { color: rgb(220, 60, 60); }
var raiz = GetComponent<UIDocument>().rootVisualElement; var valor = raiz.Q<Label>("valor"); raiz.Q<Button>("reconhecer").clicked += () => servicoAlarmes.Reconhecer(sensor.Id); // mudar o estado visual trocando classe, não cor no código: raiz.EnableInClassList("painel--alarme", sensor.EmAlarme);
Trocar classes USS (EnableInClassList) em vez de definir cores em C# mantém o visual num só lugar, permite temas (claro/escuro, alto contraste) e deixa o designer ajustar a interface sem tocar em código — o mesmo princípio de BEM na web.
3.3 Localização
Aplicações corporativas quase sempre precisam de mais de um idioma. O pacote Localization (com.unity.localization) gerencia tabelas de strings e de assets por idioma, formatação de números e datas por cultura e troca de idioma em tempo de execução. Duas regras: nunca concatenar frases ("Há " + n + " alarmes" quebra em idiomas com outra ordem ou outras regras de plural — use strings com marcadores e as regras de plural do pacote) e reservar espaço: textos em alemão costumam ser bem mais longos que em inglês.
3.4 Acessibilidade
É o ponto fraco do Unity em comparação com web e nativo. Desde o Unity 2023.2 há APIs de acessibilidade para expor uma hierarquia a leitores de tela em Android e iOS, mas o suporte é mais limitado e exige trabalho explícito. Em aplicações de uso obrigatório (treinamento de funcionários, serviço público), trate isso como requisito: contraste, tamanho de texto ajustável, navegação completa por teclado/controle, legendas, e alternativa textual para informação que só existe em cor. Se o requisito legal for forte, é mais um argumento para a UI principal ser web/nativa com Unity embutido (módulo 7).
✏️ Exercício 3 — Escolha o sistema
Uma app de manutenção tem: (a) tabela com 5 000 ordens de serviço, filtrável; (b) etiqueta flutuante sobre cada máquina no 3D; (c) menu de mão em VR. Que sistema de UI para cada?
Gabarito: (a) UI Toolkit, MultiColumnListView virtualizada — só as linhas visíveis existem. (b) uGUI em World Space (ou sprites/texto 3D), já que precisa acompanhar o objeto em perspectiva. (c) uGUI com o XR Interaction Toolkit, que tem suporte pronto a raios e toque em UI de Canvas.
Dados, rede e tempo real
Objetivo: consumir APIs sem congelar a interface, receber dados de IoT em tempo real, respeitar a regra da thread principal e não guardar segredos onde não se deve.
4.1 A regra da thread principal
Quase toda a API do Unity (Transform, GameObject, materiais, UI) só pode ser tocada na thread principal. Você pode — e deve — fazer parsing de JSON grande, cálculos e I/O noutras threads, mas o resultado tem de voltar à thread principal antes de mexer na cena. No Unity 6, o tipo Awaitable torna isso explícito:
async Awaitable CarregarHistoricoAsync(string url) { using var req = UnityWebRequest.Get(url); req.SetRequestHeader("Authorization", "Bearer " + sessao.Token); await req.SendWebRequest(); // não bloqueia o frame if (req.result != UnityWebRequest.Result.Success) { ui.MostrarErro(req.error); return; } string json = req.downloadHandler.text; await Awaitable.BackgroundThreadAsync(); // sai da thread principal var historico = JsonConvert.DeserializeObject<List<Leitura>>(json); await Awaitable.MainThreadAsync(); // volta antes de tocar na cena grafico.Desenhar(historico); }
JsonConvert vem do pacote Newtonsoft (com.unity.nuget.newtonsoft-json); o JsonUtility embutido é mais rápido mas não serializa dicionários, propriedades nem tipos polimórficos — para APIs reais, quase sempre falta alguma coisa.
Se o usuário fechar o painel antes de a requisição terminar, o await continua e, quando voltar, o painel já foi destruído. Passe um CancellationToken (o destroyCancellationToken de cada MonoBehaviour existe exatamente para isso) e trate o cancelamento. Em app de sessão longa, requisições órfãs são uma fonte clássica de erros intermitentes.
4.2 Tempo real: escolher o transporte
| Transporte | Quando usar | Observação |
|---|---|---|
| Polling REST | Dados que mudam a cada minutos; poucos clientes | Simples; custa requisições vazias |
| WebSocket | Atualizações frequentes vindas do seu backend | Um canal aberto, o servidor empurra as mudanças |
| MQTT | IoT e sensores industriais; muitos produtores, tópicos hierárquicos | Padrão de fato em IoT; cliente .NET como MQTTnet. Ver Robótica, IoT e Embarcados |
| OPC UA | Integração direta com automação industrial (CLPs, SCADA) | Normalmente passa por um gateway que traduz para MQTT/REST, em vez de o cliente 3D falar direto com a rede de automação |
4.3 Não afogue a cena
Receber 3 000 sensores a 10 Hz são 30 000 mensagens por segundo. Nunca aplique cada mensagem diretamente na cena. Acumule num buffer thread-safe (por exemplo, ConcurrentQueue) na thread de rede, e drene uma vez por frame na thread principal, guardando só o valor mais recente de cada sensor. A cena atualiza no ritmo da tela, não no ritmo da rede.
4.4 Segredos e autenticação
- Um build é um arquivo que o usuário tem na mão. Chaves de API embutidas no código ou em ScriptableObjects podem ser extraídas com ferramentas de descompilação em minutos. Nada de segredo de servidor no cliente.
- O fluxo correto: o usuário autentica (OAuth2/OIDC com o provedor da empresa), o cliente recebe um token de curta duração, e todas as chamadas vão para o seu backend, que guarda as credenciais sensíveis. Veja API Design para o desenho desse backend.
✏️ Exercício 4 — Onde está o bug?
Um desenvolvedor recebe mensagens MQTT num callback e, dentro dele, faz transform.position = novaPosicao. No editor às vezes funciona, às vezes lança exceção, e no build fica pior. O que está errado e como corrigir?
Gabarito: O callback do cliente MQTT roda numa thread de rede, e a API do Transform só pode ser usada na thread principal. Correção: o callback só enfileira a mensagem numa ConcurrentQueue; um Update drena a fila (ficando com o último valor por objeto) e aplica as posições.
Conteúdo 3D de engenharia
Objetivo: transformar modelos CAD e BIM — pesados, detalhados e feitos para fabricar, não para renderizar — em conteúdo que roda em tempo real sem perder a informação que importa.
5.1 Por que CAD não entra direto
Um modelo CAD descreve superfícies matemáticas exatas (NURBS) para fabricação. Para renderizar, é preciso tesselar em triângulos — e a tesselação ingênua de uma máquina inteira produz dezenas de milhões de polígonos, inclusive de parafusos internos que ninguém verá. Um BIM de edifício traz, além disso, milhares de objetos separados (cada um virando um draw call) e metadados valiosos que a importação pode perder.
5.2 O pipeline de preparação
| Etapa | O que faz | Por que |
|---|---|---|
| Importar | Ler STEP, IGES, formatos nativos de CAD, IFC (BIM), ou intermediários (FBX, glTF, USD) | Ferramentas como Pixyz (incluída no Unity Industry) leem os formatos de engenharia diretamente |
| Tesselar com controle | Escolher tolerância de corda/ângulo por peça | Peça grande e curva precisa mais triângulos que parafuso |
| Remover o oculto | Apagar peças internas e faces nunca visíveis | Frequentemente a maior redução de todas |
| Decimar e gerar LOD | Versões mais leves para distância | LODGroup troca automaticamente conforme o tamanho na tela |
| Fundir (merge) | Juntar peças estáticas com o mesmo material | Menos draw calls — mas perde-se a seleção individual |
| Preservar metadados | Guardar ID, nome, propriedades de cada peça | É o que liga o 3D aos dados (módulo 9) |
Fundir tudo dá desempenho; manter tudo separado dá interatividade (clicar numa válvula e ver os dados dela). A solução habitual: fundir para renderizar, mas manter um mapa de IDs — por exemplo, um índice de triângulo → ID da peça, ou um ID por vértice gravado num canal de UV — para descobrir qual peça foi clicada sem que ela exista como objeto separado.
5.3 Formatos de intercâmbio
- FBX: o mais comum no Unity; bom para geometria e animação, ruim para metadados ricos.
- glTF 2.0: padrão aberto e leve, excelente para web e para carregar em tempo de execução (pacotes como glTFast).
- OpenUSD: descrição de cena com camadas e composição, cada vez mais usado em pipelines industriais e de gêmeo digital; o suporte no Unity existe via pacotes e evolui.
- IFC: o padrão aberto do BIM; traz a semântica (parede, laje, porta) e as propriedades.
5.4 Conteúdo que não vai no build: Addressables
Uma aplicação que abre projetos diferentes não pode embutir todos no executável. O sistema Addressables empacota conteúdo em bundles carregáveis sob demanda, de disco ou de um servidor, com gestão de dependências e de memória. Para modelos que chegam só em execução (upload do cliente), carregar glTF em runtime é o caminho mais direto. A gestão de memória e a tela de carregamento com Addressables estão detalhadas no capítulo 8 de Serious Games na Web.
✏️ Exercício 5 — Priorize a otimização
Um modelo de fábrica tem 48 milhões de triângulos, 22 000 objetos e 900 materiais. Em que ordem você atacaria, e por quê?
Gabarito: (1) Remover peças ocultas/internas — costuma cortar mais do que qualquer outra etapa e não degrada nada visível. (2) Reduzir materiais (900 → dezenas, por consolidação e atlas), que é pré-requisito para fundir. (3) Fundir objetos estáticos por material, mantendo um mapa de IDs para seleção — derruba os draw calls. (4) Decimar e gerar LODs para o que restar pesado. Medir com o Profiler/Frame Debugger entre cada etapa, porque o gargalo muda.
Performance de aplicação
Objetivo: entender por que performance de aplicação é diferente de performance de jogo — bateria, calor, memória em sessões longas e arranque — e usar as ferramentas certas para cada uma.
6.1 Não renderize o que não mudou
Um jogo redesenha a tela 60 vezes por segundo mesmo com o jogador parado. Uma aplicação aberta o dia todo num tablet, fazendo isso, drena a bateria, aquece o aparelho (que então reduz o clock e fica mais lento) e gasta GPU à toa numa estação de trabalho compartilhada. Duas ferramentas:
// frame rate alvo global Application.targetFrameRate = 60; // renderizar só 1 de cada N frames, mantendo o loop (input, lógica) a 60 Hz using UnityEngine.Rendering; OnDemandRendering.renderFrameInterval = ocioso ? 12 : 1; // ~5 fps parado, 60 interagindo
A estratégia: renderização plena durante interação (arrastar, girar, animação, dado chegando) e intervalo alto quando nada muda. Volte a 1 no primeiro evento de input ou mudança de dado.
Frames renderizados por dia de uso. É uma aproximação do trabalho da GPU, não do consumo exato de energia, que depende do aparelho — mas a proporção é o que decide se vale a pena.
6.2 Muitos objetos iguais
- GPU Instancing / SRP Batcher: milhares de objetos com a mesma malha e material desenhados em poucas chamadas. Para variar cor por objeto sem quebrar o lote, use
MaterialPropertyBlockou propriedades por instância no shader. - GPU Resident Drawer (Unity 6, URP/HDRP): automatiza boa parte do instancing para cenas com muitos objetos estáticos — exatamente o perfil de gêmeos digitais e AEC.
- Occlusion culling: não desenhar o que está atrás de paredes; decisivo em interiores de edifícios.
6.3 Memória em sessões longas
Num jogo, um vazamento de 5 MB por minuto pode passar despercebido numa sessão de 30 minutos. Numa app aberta oito horas, são 2,4 GB e um crash no meio do turno. Suspeitos habituais: materiais criados por renderer.material e nunca destruídos, texturas carregadas em runtime sem Destroy, handles de Addressables sem Release, eventos não desinscritos (módulo 2), e alocações de lixo por frame que fazem o garbage collector disparar. O Memory Profiler permite comparar dois snapshots — tirar um no início, usar a app por uma hora, tirar outro e ver o que cresceu é o teste mais valioso desta apostila.
6.4 Arranque
Usuários de aplicação toleram muito menos espera que jogadores. Reduza o que a primeira cena carrega (cena de bootstrap mínima, conteúdo pesado por Addressables depois), evite trabalho pesado em Awake/Start de dezenas de objetos, pré-aqueça shaders (coleções de variantes) para não haver engasgos na primeira interação, e mostre algo útil — o último projeto aberto, por exemplo — enquanto o resto carrega.
✏️ Exercício 6 — Diagnóstico
Um tablet com a app de inspeção fica quente e, depois de 40 minutos, a interface começa a engasgar, mesmo com a cena parada. O Profiler mostra GPU ocupada o tempo todo e o frame rate caindo aos poucos. Hipótese e correção?
Gabarito: A app renderiza a 60 fps mesmo parada; o aparelho aquece, entra em limitação térmica e reduz o clock, e o desempenho cai. Correção: OnDemandRendering.renderFrameInterval alto quando ocioso, voltando a 1 no input; e, se ainda assim estiver pesada durante a interação, reduzir resolução de render/efeitos em mobile.
Plataformas e formas de entrega
Objetivo: conhecer as maneiras de colocar uma aplicação Unity nas mãos do usuário — executável, embutida numa app nativa, no navegador, em XR ou transmitida de um servidor — e o custo de cada uma.
7.1 O mapa de entrega
| Forma | Quando | Custo principal |
|---|---|---|
| Executável desktop (Windows/macOS/Linux) | Estações de trabalho, salas de controle, quiosques | Instalação e atualização em máquinas geridas pela TI |
| App mobile | Inspeção em campo, vendas, AR | Lojas, assinatura de código, térmica e bateria (módulo 6) |
| Unity as a Library | O 3D é uma tela dentro de uma app nativa maior | Integração e ciclo de vida entre dois mundos |
| Web | Acesso por link, sem instalação | Tamanho de download, memória, limites do navegador |
| XR (Quest, visionOS, HoloLens…) | Revisão em escala real, treino imersivo, AR no equipamento | Conforto, frame rate rígido, interação própria |
| Streaming de render | Modelos pesados demais para o aparelho do usuário | Servidor com GPU por sessão ativa; latência de rede |
7.2 Unity as a Library
Desde o Unity 2019.3 é possível exportar o projeto como uma biblioteca e embuti-lo numa app nativa Android (módulo/AAR) ou iOS (framework). A app nativa cuida de login, navegação, formulários e acessibilidade; o Unity aparece numa tela para o 3D. A comunicação nos dois sentidos passa por mensagens — do nativo para o Unity com UnitySendMessage (nome do objeto, nome do método, uma string), e do Unity para o nativo por plugins nativos. Limites importantes: só uma instância do runtime por processo, e descarregar o Unity não devolve toda a memória. É a resposta certa ao "catálogo com um botão ver em 3D" do exercício 1, quando o 3D justifica o motor.
7.3 Web
Um build Web permite abrir a aplicação por um link. Os problemas de tamanho, compressão, servidor e memória são os mesmos dos jogos e estão tratados em Serious Games na Web. Para aplicações, o ponto extra é a integração com a página (a app Unity dentro de um portal web existente, trocando dados com ele), que é assunto de uma apostila própria de Unity para web interativa.
7.4 XR
- OpenXR como camada de plataforma e o XR Interaction Toolkit para interação (raios, agarrar, teleporte, UI) cobrem a maioria dos headsets com o mesmo código.
- AR Foundation para AR em telemóveis (ARKit/ARCore): planos, âncoras, imagens rastreadas — base de instruções de manutenção sobrepostas ao equipamento. Mais em Realidade Aumentada.
- visionOS via PolySpatial, com modos de janela, volume e espaço imersivo — cada um com restrições diferentes de renderização.
Queda de frame rate em VR causa desconforto e enjoo. O orçamento é rígido (tipicamente 72–90 Hz ou mais, conforme o headset) e o modelo CAD que "roda bem" em desktop quase nunca roda em standalone sem o pipeline do módulo 5 levado mais longe.
7.5 Streaming de render
Quando o modelo é pesado demais para o aparelho, o Unity pode rodar num servidor com GPU e enviar vídeo ao navegador, recebendo o input de volta (o pacote Unity Render Streaming, baseado em WebRTC, é o ponto de partida). Resolve o limite do cliente, mas cria dois novos: custo de uma GPU por usuário ativo e latência sensível à rede. Faz sentido para configuradores premium e revisões de projeto com poucos usuários simultâneos; raramente faz sentido para milhares.
✏️ Exercício 7 — Escolha a entrega
(a) Técnicos em campo, com app de ordens de serviço já existente em Kotlin, querem ver o modelo 3D do equipamento. (b) Diretoria quer revisar o projeto de um edifício de 60 GB num tablet comum. (c) Operadores de uma sala de controle, 8 horas por dia, em PCs geridos pela TI.
Gabarito: (a) Unity as a Library dentro da app Kotlin — não reescrever o que já funciona. (b) Streaming de render (poucos usuários, modelo impossível no tablet) ou um modelo drasticamente simplificado; a escolha depende do custo aceitável de servidor. (c) Executável desktop, distribuído pela TI, com renderização sob demanda e teste de memória de 8 horas.
Qualidade: testes, CI e versionamento
Objetivo: aplicar a um projeto Unity a disciplina de engenharia que o cliente de uma aplicação espera — testes automatizados, builds reproduzíveis e controle de versão sem pesadelos de merge.
8.1 A pirâmide de testes no Unity
| Nível | Onde roda | O que testa |
|---|---|---|
| Testes de domínio | Fora do motor (NUnit puro, ou EditMode) | Regras de negócio — rápidos, a maioria dos testes |
| EditMode (Unity Test Framework) | No editor, sem entrar em Play | Serviços, conversões, validação de assets e cenas |
| PlayMode | Com o motor rodando | Integração: carregar cena, clicar, verificar resultado — mais lentos, poucos |
A arquitetura do módulo 2 é o que torna isto possível: se a regra está num MonoBehaviour, só um teste PlayMode (lento e frágil) a alcança. Se está no domínio, um teste de milissegundos resolve.
[Test] public void Sensor_AcimaDoLimite_EntraEmAlarme() { var s = new Sensor("T-101", limite: 80); s.Atualizar(85); Assert.IsTrue(s.EmAlarme); }
8.2 Testes de conteúdo
Em aplicações, muitos bugs não estão no código, estão nos assets: um prefab sem componente obrigatório, um material com shader errado, uma cena sem câmera. Testes EditMode que varrem os assets e verificam regras do projeto (toda peça importada tem ID de metadado, toda textura de UI tem compressão certa) pegam esses erros antes do cliente.
8.3 Builds automatizados
O Unity roda sem interface em modo batch (-batchmode -nographics -executeMethod ...), o que permite builds e testes em CI. O projeto open source GameCI oferece ações prontas para GitHub Actions e imagens Docker com o editor. Um pipeline mínimo: a cada pull request, rodar EditMode + PlayMode; a cada merge na branch principal, gerar os builds de cada plataforma e guardar os artefatos. A licença do Unity precisa ser ativada no runner — planeje isso antes.
8.4 Git sem sofrimento
- Asset Serialization = Force Text e Visible Meta Files (padrão nas versões recentes): cenas e prefabs viram YAML legível, mergeáveis em princípio.
- Commitar sempre os
.meta: é neles que vive o GUID de cada asset; perder um.metaquebra todas as referências àquele asset. - Git LFS para binários grandes (modelos, texturas, áudio).
- UnityYAMLMerge (Smart Merge, que vem com o editor) configurado como ferramenta de merge melhora muito os conflitos em cenas e prefabs — mas a melhor defesa é estrutural: cenas pequenas e aditivas, prefabs aninhados, e duas pessoas não editando a mesma cena ao mesmo tempo.
- O
.gitignorepadrão de Unity excluiLibrary/,Temp/,Logs/,obj/e builds. Mais sobre o fluxo em Git & GitHub.
✏️ Exercício 8 — O pull request quebrado
Depois de um merge, metade dos prefabs aparece com "Missing Prefab" e materiais cor-de-rosa, mas nenhum arquivo foi apagado. O que provavelmente aconteceu?
Gabarito: Arquivos .meta foram perdidos ou regenerados (não commitados por alguém, ou ignorados por um .gitignore errado). O Unity gerou GUIDs novos para os assets, e todas as referências antigas, que apontavam para os GUIDs originais, quebraram. Correção: recuperar os .meta originais do histórico; prevenção: revisar o .gitignore e verificar no CI que todo asset tem o seu .meta.
Gêmeo digital de ponta a ponta
Objetivo: juntar tudo num caso integrado — ligar um modelo 3D a dados reais, mostrar o presente, rever o passado, e fazer isso sem colocar em risco a rede industrial.
9.1 O que é (e o que não é) um gêmeo digital
Um modelo 3D bonito de uma fábrica não é um gêmeo digital. Passa a ser quando existe uma ligação de dados contínua entre o objeto físico e a representação virtual, de forma que o virtual reflete o estado real (e, nos casos mais maduros, permite simular o que aconteceria com uma mudança antes de aplicá-la). O 3D é a interface; o valor está na ligação.
9.2 A arquitetura em camadas
Equipamento (CLP, sensores) → gateway (OPC UA → MQTT) → broker/plataforma (MQTT, séries temporais, API) → cliente Unity: serviço de dados (thread de rede, fila) → modelo de domínio (estado de cada ativo) → apresentação (cores, rótulos, gráficos).
O ponto que liga o 3D aos dados é o ID do ativo: a mesma chave precisa existir no modelo 3D (preservada na importação — módulo 5), no sistema de dados e no domínio. Sem uma tabela de correspondência confiável, o projeto trava aqui — e em muitos projetos reais, reconciliar a nomenclatura do CAD com a do sistema de manutenção é a tarefa mais demorada de todas.
9.3 Presente: interpolação e dados atrasados
- Interpolar valores contínuos (posição de um robô, nível de um tanque) entre amostras, em vez de saltar — com o cuidado de não inventar dado: indique quando a última leitura é antiga.
- Estado de dado envelhecido: um sensor sem leitura há 5 minutos não está "normal", está desconhecido. Mostre isso explicitamente (cinzento, tracejado) — o pior erro de um gêmeo digital é exibir um estado antigo com cara de atual.
9.4 Passado: histórico e reprodução
Operadores e engenheiros querem voltar no tempo: "o que aconteceu às 3h14 quando a linha parou?". Isso pede consultar a série temporal por janela e reproduzir na mesma cena, com uma linha do tempo. A arquitetura do módulo 2 paga aqui: se a apresentação só lê o estado do domínio, alimentar esse domínio a partir do histórico em vez do tempo real não exige mudar nenhum componente visual.
9.5 Segurança: a rede industrial não é a internet
A rede de automação (OT) controla equipamentos físicos. O cliente 3D deve, por padrão, só ler, e através de um gateway numa zona de rede separada — nunca falando diretamente com CLPs. Qualquer comando que altere o mundo físico (parar uma bomba, mudar um setpoint) é uma decisão de engenharia de segurança, com autenticação forte, confirmação, registro de auditoria e, em geral, passando pelo sistema de controle existente, não por um atalho no aplicativo de visualização. O mesmo princípio de "camada de segurança" aparece em Physical AI.
Sénior — "O cliente quer um gêmeo digital. Por onde você começa?"
Não pelo 3D. Começa-se pela pergunta de negócio (que decisão o gêmeo vai melhorar? reduzir paradas? treinar operadores?), depois pelo inventário dos dados (que sinais existem, com que frequência, em que sistema, com que nomes), e pela correspondência de IDs entre CAD e dados. Só então se decide o nível de detalhe visual necessário — frequentemente muito menor do que o cliente imaginava. Um piloto numa única linha, com três ou quatro indicadores que alguém usa de fato, vale mais do que a fábrica inteira modelada sem dados.
Mercado, portfólio e fontes
Objetivo: entender quem contrata Unity para aplicações, o que avaliam, e montar um portfólio que prove as competências deste tipo de projeto.
10.1 Quem contrata
| Setor | Projetos típicos | Competência diferencial |
|---|---|---|
| Indústria e energia | Gêmeos digitais, treino de operação e segurança | Dados em tempo real, MQTT/OPC UA, CAD |
| AEC | Visualização e revisão de projetos, BIM | IFC, iluminação, otimização de modelos grandes |
| Automotivo e produtos | Configuradores, HMI, marketing | Materiais realistas, HDRP/URP, entrega multiplataforma |
| Saúde e educação | Simuladores, anatomia, treino de procedimentos | Interação precisa, avaliação, acessibilidade |
| Agências e estúdios de XR | Experiências de marca, eventos, apps AR | XR, prazos curtos, performance em mobile |
10.2 Perguntas de entrevista
"Como você estruturaria uma app Unity para ser testável?"
Separar domínio (C# puro, assembly sem referência ao motor) de apresentação (MonoBehaviours finos), injetar dependências num composition root, e cobrir o domínio com testes rápidos fora de Play Mode, deixando PlayMode para poucos testes de integração.
"Recebemos um CAD de 40 milhões de triângulos. O que você faz?"
Perguntar para que ele vai ser usado e em que dispositivo; depois remover o oculto, consolidar materiais, fundir estáticos preservando IDs, decimar e gerar LOD, medindo entre cada etapa. E garantir que os metadados necessários para ligar a dados sobrevivem ao processo.
"A app trava depois de horas de uso. Como investiga?"
Suspeita de vazamento de memória: dois snapshots no Memory Profiler com uma hora de uso entre eles, comparar o que cresceu. Suspeitos: materiais instanciados, texturas de runtime, handles de Addressables sem Release, eventos não desinscritos, requisições órfãs sem cancelamento.
10.3 Projetos de portfólio
- Mini gêmeo digital: um modelo simples (uma sala, uma máquina) ligado a um broker MQTT com um simulador de sensores em Python; estados normal/alarme/desconhecido, histórico com reprodução. É o projeto que mais conversa com as vagas industriais.
- Pipeline de CAD: pegar um modelo aberto de engenharia, documentar antes/depois (triângulos, draw calls, tempo de frame) e a seleção de peças preservada após a fusão.
- Configurador de produto: troca de materiais e opcionais, UI Toolkit com tema, localização em dois idiomas, build desktop e mobile.
- Unity as a Library: uma app nativa simples com uma tela Unity embutida, trocando mensagens nos dois sentidos.
- Projeto com engenharia visível: qualquer um dos anteriores com testes, CI (GameCI) e README explicando a arquitetura — o que diferencia um desenvolvedor de aplicações de alguém que só sabe o editor.
10.4 Fontes para continuar
- Documentação oficial: manual do Unity 6 (UI Toolkit, Addressables, Unity Test Framework, Awaitable, OnDemandRendering), documentação do XR Interaction Toolkit e do AR Foundation.
- E-books gratuitos da Unity: os guias de padrões de arquitetura em C# e de otimização de performance, e os de UI Toolkit.
- Industrial: documentação do Pixyz/Unity Industry; os padrões OPC UA (OPC Foundation) e MQTT (OASIS).
- Engenharia: GameCI (documentação e exemplos de pipeline), VContainer.
Quatro ideias sustentam Unity para aplicações: (1) o 3D é a interface, não a fonte da verdade — os dados vivem noutros sistemas e o Unity os mostra; (2) arquitetura antes de cena — domínio em C# puro, apresentação fina, dependências para dentro, porque é isso que torna a app testável e mantível; (3) conteúdo de engenharia precisa de pipeline — CAD e BIM não entram direto, e a identidade de cada peça tem de sobreviver à otimização; (4) performance de aplicação é sobre o longo prazo — renderizar só quando algo muda, não vazar memória em oito horas, arrancar rápido. E a regra que evita o erro mais caro: Unity quando o 3D é o produto.