Apostila profissional · Jogos & Aprendizagem

Serious games na web:
do build leve
ao dado de aprendizagem

Um guia completo — do básico ao muito avançado — para publicar jogos Unity no navegador com poucos megabytes e tempo de carregamento honesto, empacotá-los para o LMS (SCORM) e registrar o que o aluno fez (xAPI).

12 capítulosníveis básico → muito avançado
24 exercícioscom gabaritos nos principais
Código prontoC#, .jslib, JS, manifesto e servidor
3 demos ao vivocarregamento, espera e statement xAPI

↓ role para começar — a barra no topo é o seu progresso

Capítulo 01 Básico

Serious games e o porquê da web

Um jogo que ensina só cumpre a missão se chegar ao aluno e se sobrar alguma evidência de que ele aprendeu. A web resolve o primeiro problema e cria dois novos.

Este guia trata de um fluxo específico e muito comum em educação corporativa, escolas, saúde e treinamento técnico: um jogo feito em Unity, publicado como build Web, entregue por um LMS (ou por um link) e capaz de devolver dados sobre o que o jogador fez. Cada elo desse fluxo tem armadilhas próprias, e a maioria delas só aparece quando o jogo já está pronto.

1.1 O que é um serious game

O termo vem de Clark Abt, que em Serious Games (1970) descreveu jogos com finalidade educacional explícita e cuidadosa, e não apenas de entretenimento. Michael & Chen (2005) popularizaram a expressão para jogos digitais que educam, treinam e informam. A fronteira com termos vizinhos costuma confundir:

TermoO que éExemplo
GamificationElementos de jogo (pontos, níveis, ranking) aplicados a uma atividade que não é um jogoCurso em vídeo com medalhas por módulo
Serious gameUm jogo completo, desenhado para um propósito além do entretenimentoSimulador de evacuação de emergência
Game-based learning (GBL)Uso de jogos (sérios ou comerciais) como método de ensino, com objetivos de aprendizagem definidosAula que usa um jogo de estratégia para discutir logística
SimulaçãoModelo de um sistema real para prática; pode ou não ter regras de jogoSimulador de cirurgia ou de voo

Se você quer aprofundar a diferença entre essas categorias, veja Gamification. Aqui o foco é técnico: colocar o jogo no ar e medir.

1.2 Por que publicar na web

Vantagem 1

Zero instalação

Escolas e empresas costumam ter computadores bloqueados, Chromebooks e políticas que impedem instalar executáveis. Um link abre em qualquer navegador moderno.

Vantagem 2

Integra com o LMS

Moodle, Canvas, Blackboard e plataformas corporativas sabem executar conteúdo web empacotado (SCORM) e registrar conclusão e nota.

Vantagem 3

Atualização instantânea

Corrigiu um erro no enunciado de uma fase? Publicou a nova versão e todos jogam a correção, sem pedir que ninguém reinstale nada.

Vantagem 4

Alcance e evidência

Um link no portfólio ou no e-mail do treinamento é jogado em segundos. E como tudo passa pela web, é natural enviar dados de uso a um servidor.

O preço da web

O navegador impõe limites de tamanho (o jogador espera o download antes de jogar), de memória (a aba pode ser encerrada), de recursos (sem threads, sem sockets) e de contexto (o jogo roda dentro de um iframe do LMS, com regras de segurança próprias). Os capítulos 2 a 8 tratam desses limites; os capítulos 9 a 11 tratam dos dados.

1.3 O fluxo completo, do editor ao dado

Pipeline deste guia
1. Unity (projeto) → build Web (cap. 2)
2. Build enxuta: código, assets, compressão (caps. 4 a 7)
3. Carregamento inteligente: Addressables, loading, memória (cap. 8)
4. Empacotamento: SCORM (cap. 9) ou hospedagem própria + cmi5/xAPI (cap. 10)
5. LMS / link → aluno joga
6. Eventos → LRS ou LMS → análise (cap. 11)
Na prática · mercado de trabalho

Essa combinação aparece em vagas e projetos de desenvolvedor de serious games, learning engineer / analista de learning analytics, desenvolvedor de EdTech, treinamento corporativo e compliance, simulação em saúde e segurança e museus e comunicação científica. Quem domina só Unity entrega um jogo; quem domina o pipeline inteiro entrega um jogo que a organização consegue distribuir, rastrear e provar que funciona. Para a carreira em Unity em si, veja Unity e Game Design.

Exercício 1.1 — Classifique

Classifique cada caso como gamification, serious game, GBL ou simulação, e justifique com a pergunta "a atividade principal é um jogo?": (a) app de idiomas com sequência diária e ranking; (b) jogo de gerenciar uma UTI em que decisões clínicas erradas têm consequências; (c) turma jogando Minecraft para discutir urbanismo; (d) treinamento de segurança em que você percorre uma fábrica virtual e marca riscos.

Ver gabarito

(a) Gamification: a atividade principal é estudar; o jogo é uma camada. (b) Serious game (com componente de simulação): a atividade principal é jogar, com propósito clínico. (c) GBL: um jogo comercial usado como método, com objetivo de aprendizagem definido pelo professor. (d) Simulação, que vira serious game se tiver regras, metas e feedback de jogo. Fronteiras assim são graduais; o importante é saber qual delas você está construindo.

Exercício 1.2 — Ficha do projeto

Escreva para um jogo que você tenha ou queira fazer: quem é o aluno, em que dispositivo e rede ele vai jogar (escola? casa? celular?), qual LMS ou canal entrega o jogo, e qual decisão alguém tomará com os dados. Guarde essa ficha: os capítulos 4, 7 e 11 vão usá-la.

Ver exemplo resolvido

"Alunos do 9º ano, em Chromebooks da escola, com Wi-Fi compartilhado (~5 Mbps por turma). Entrega via Moodle da rede municipal. Decisão: a professora quer saber em quais conceitos de fração a turma mais erra, para escolher o tema da aula seguinte." Repare que o contexto (rede fraca, Moodle) já dita orçamento de tamanho e formato de empacotamento.

Capítulo 02 Básico

Unity na web: o que sai do build e o que não existe

Antes de otimizar, conheça a peça. Uma build Web é um pequeno site: cada arquivo tem um papel e um custo.

2.1 WebGL, WebAssembly e o nome da plataforma

A build Web da Unity roda no navegador combinando duas tecnologias: WebGL 2 (a API gráfica do navegador) e WebAssembly (o código do seu jogo e do motor, compilado via IL2CPP para um binário compacto). Nas versões mais antigas a plataforma se chamava WebGL; no Unity 6 ela aparece como Web, nos Build Profiles. Neste guia, "build Web" e "build WebGL" significam a mesma coisa. Os nomes exatos de menus e opções mudam entre versões: use este texto como mapa e confirme na documentação da sua versão.

2.2 O que sai da pasta Build

ArquivoPapelCostuma pesar
index.htmlPágina que cria o canvas e chama o loader. É o template que você pode personalizar.Poucos KB
Build/*.loader.jsCarrega os demais arquivos, mostra progresso, cria a instância do jogo (createUnityInstance).Dezenas de KB
Build/*.framework.jsCódigo JavaScript de suporte do runtime (Emscripten) e dos plugins .jslib.Centenas de KB
Build/*.wasmO código: motor Unity + seus scripts C# convertidos por IL2CPP.A maior parte do peso "de código"
Build/*.dataOs assets empacotados: cenas, texturas, malhas, áudio, shaders.A maior parte do peso "de conteúdo"
StreamingAssets/Arquivos copiados como estão, lidos por UnityWebRequest. Não entram no .data.Depende do que você colocou
TemplateData/Logo, barra de progresso, favicon e CSS do template padrão.Pequeno

Com compressão ligada (cap. 7), os arquivos da pasta Build ganham sufixo .gz ou .br. O servidor precisa então avisar o navegador de que estão comprimidos, o que é a causa mais frequente de "a build não abre depois de publicar".

2.3 O que muda em relação ao desktop

RecursoNa build WebO que fazer
Threads (System.Threading, Jobs em paralelo)Não há threads reais na configuração padrão (verifique o status na sua versão).Projete sem depender de paralelismo; use corrotinas e trabalho em fatias por frame.
RedeSem sockets nem System.Net de baixo nível; só o que o navegador oferece.Use UnityWebRequest (HTTP) e, se precisar de tempo real, WebSocket via plugin .jslib.
ArquivosSystem.IO opera num sistema de arquivos virtual, em memória; a persistência vai para o IndexedDB do navegador.Não confie em arquivo local para guardar progresso importante (cap. 8).
Compute shaders, HDRPNão suportados no WebGL 2.Use Built-in ou URP e efeitos simples.
Áudio, tela cheia, bloqueio do cursorO navegador só libera após um gesto do usuário (clique, toque, tecla).Desenhe uma tela inicial "Toque para começar"; ela também ajuda no áudio.
Coleta de lixo (GC)Só roda entre frames, não durante a execução de código gerenciado.Evite alocações repetidas em laços; pré-aloque, use StringBuilder e NativeArray.
Application.QuitNão fecha a aba.Ofereça "Voltar ao curso" por outro mecanismo (cap. 9).
Armadilha: o editor mente

Tudo isso funciona no Editor, que é um desktop. Um jogo que "roda perfeitamente" no Editor pode falhar na primeira vez que roda num navegador. Faça uma build Web na primeira semana do projeto, não na última, e teste sempre a build, não só o Editor.

Na prática · mercado de trabalho

Em entrevistas e revisões de código de jogos web, a pergunta "o que você não pode usar no WebGL?" é um filtro clássico. Saber a lista da tabela acima, com o motivo, mostra que você já teve um jogo rejeitado por um erro desses.

Exercício 2.1 — Baseline vazio

Crie um projeto novo (2D ou 3D), uma cena vazia, mude a plataforma para Web e faça a build. Anote o tamanho de cada arquivo da pasta Build (com compressão Gzip e depois Disabled). Você acabou de medir o "peso zero" do motor; todo o resto do projeto se soma a ele.

Exercício 2.2 — Lista de riscos

Pegue a ficha do exercício 1.2 e marque quais itens da tabela 2.3 ameaçam o seu jogo (por exemplo: você usa threads? salva arquivo local? depende de áudio ao iniciar?). Escreva a mitigação de cada um em uma linha.

Capítulo 03 Intermediário · Interativo

Anatomia do carregamento: para onde vai o tempo

"O jogo demora a abrir" não é um diagnóstico. O tempo se divide em cinco estágios, e cada um tem um remédio diferente.

3.1 Os cinco estágios

Do clique no link até o primeiro frame jogável, acontece sempre a mesma sequência. Role o bloco abaixo: o painel escuro fica fixo e destaca o estágio que cada cartão descreve.

Estágio 1 · rede

Download: o custo que você controla pelo tamanho

Vale para todo mundo, mas dói na rede fraca. Reduzir bytes (caps. 5 e 6), comprimir bem (cap. 7) e usar uma CDN atacam este estágio. Na visita seguinte o cache do navegador pode zerá-lo.

Estágio 2 · decodificação

Descompressão: o erro de configuração mais comum

Com o cabeçalho Content-Encoding correto, o navegador descomprime em código nativo, rápido. Com o servidor mal configurado, o jogo ou falha ou usa o decompressor JavaScript, mais lento e mais pesado (cap. 7).

Estágio 3 · CPU

Compilação do WebAssembly: pesa em celulares

Quanto maior o .wasm, mais trabalho. Servir o arquivo como application/wasm permite compilar enquanto ele ainda baixa. Este é o motivo de cortar código do motor (cap. 5) ajudar duas vezes: menos bytes e menos compilação.

Estágio 4 · memória

Dados: o que baixa vira memória da aba

O conteúdo do .data é expandido em memória. Um .data enorme pode estourar a aba em celulares mesmo depois de baixado. O caching de dados evita baixar de novo, mas não evita desempacotar (cap. 8).

Estágio 5 · primeiro frame

A primeira cena é a sua vitrine

Cena inicial leve, shaders pré-aquecidos e carregamento assíncrono do resto fazem o jogo parecer rápido. Percepção de espera também é design (cap. 8).

3.2 Uma tabela de remédios

SintomaEstágio provávelPrimeira hipótese
Barra de progresso demora, depois abre bem1 (download)Build grande demais para a rede; falta CDN; sem cache
Erro de "não foi possível ler o arquivo comprimido" ao abrir2 (descompressão)Servidor sem Content-Encoding para .br/.gz
Barra chega ao fim e a tela fica parada3 ou 4Compilação do wasm em CPU fraca ou desempacotar .data grande
Abre, mas trava nos primeiros segundos de jogo5Compilação de shaders, cena inicial pesada, GC
Aba fecha ou "Out of memory"4 / memória.data gigante, texturas grandes, memória inicial mal calibrada

3.3 Como medir de verdade

Na prática · mercado de trabalho

Relatar "reduzi o tempo até jogar de 38 s para 11 s em rede de 5 Mbps, cortando o wasm em 40%" é o tipo de resultado que separa um portfólio de jogos web de um portfólio de projetos que só abrem na máquina do autor.

Exercício 3.1 — Waterfall

Publique a build do exercício 2.1 em qualquer hospedagem, abra o DevTools na aba Network, limpe o cache e recarregue com rede lenta. Identifique: qual arquivo é o maior? o navegador recebeu Content-Encoding? quanto tempo passou entre o fim do download e o primeiro frame?

Ver o que esperar

Numa build vazia, o .wasm costuma ser o maior arquivo. Se Content-Encoding aparecer como br ou gzip, o servidor está certo. O intervalo entre o fim do download e o primeiro frame é a soma dos estágios 2 a 5: em máquinas rápidas é curto; em celulares fracos costuma ser bem visível.

Exercício 3.2 — Diagnóstico

Para cada relato, aponte o estágio e a primeira coisa que você verificaria: (a) "abre em 5 s na escola, mas leva 1 minuto na casa da aluna"; (b) "no iPhone a página recarrega sozinha depois de carregar"; (c) "o jogo abre e trava por 4 s ao entrar na primeira fase".

Ver gabarito

(a) Estágio 1: a rede mudou, o resto é igual; reduza bytes e use CDN. (b) Estágio 4 / memória: a aba estourou o limite e o navegador a recarregou; reduza .data e texturas, calibre a memória inicial (cap. 8). (c) Estágio 5: compilação de shaders ou cena inicial pesada; pré-aqueça shaders e carregue a fase de forma assíncrona.

Capítulo 04 Intermediário · Interativo

Orçamento de tamanho e como medir

Otimizar sem meta vira coleção de truques. Defina quantos megabytes o seu aluno pode esperar e meça cada mudança contra esse número.

4.1 Da rede do aluno ao orçamento

A conta do estágio de download é simples: segundos ≈ megabytes × 8 ÷ megabits por segundo. Ela ignora descompressão e compilação, então é um piso, não uma previsão. Use a calculadora para testar o tamanho da sua build contra conexões típicas.

Conexão de referênciaVelocidadeEspera só de download
Wi-Fi escolar dividido5 Mbps
4G razoável12 Mbps
Banda larga doméstica50 Mbps

Se você informar vários alunos simultâneos, a velocidade de cada linha é dividida entre eles: é o cenário de uma turma inteira abrindo o jogo ao mesmo tempo, que costuma ser o pior caso real de uma escola.

4.2 Faixas de orçamento (uma sugestão de partida)

Não existe um limite oficial. As faixas abaixo são um ponto de partida para a primeira jogada, e você deve ajustá-las com a ficha do exercício 1.2:

Tamanho transferidoEspera a 5 MbpsPostura recomendada
até ~10 MB~16 sAceitável para a maior parte dos públicos, inclusive escolas
~10 a 30 MB~16 a 48 sExige tela de loading cuidadosa e um bom motivo (assets únicos)
acima de ~30 MB> 48 sDivida: build inicial pequena e o resto sob demanda (Addressables, cap. 8)

4.3 O peso do "vazio"

Uma medição pública de Aras Pranckevičius (engenheiro da Unity) num projeto vazio do Unity 6 mostra a ordem de grandeza do que o motor sozinho custa: o template 3D com URP passou de 10 MB comprimidos (cerca de 3,7 MB de dados e 6,9 MB de código), enquanto o mesmo tipo de projeto, com Built-in, código otimizado, exceções desligadas, sem Input System nem Unity UI e com a compressão Brotli, ficou em torno de 2 MB. O que mais pesou na redução foi remover pacotes que puxam código de motor mesmo sem uso. Os números mudam a cada versão; o aprendizado é que o "vazio" já pode consumir a maior parte do seu orçamento se você aceitar os padrões.

4.4 Como ver o que pesa

# tamanhos dos arquivos comprimidos que realmente trafegam
cd Builds/Web/Build
ls -lh
du -sh .

4.5 O hábito: diário de builds

Mude uma coisa por vez e registre o resultado. Uma planilha de cinco colunas basta:

BuildMudança.wasm.dataTotal transferido
#01Baseline (padrões da Unity)
#02Managed Stripping Level: High

Sem o diário, você não sabe qual mudança valeu a pena e nem qual quebrou o jogo.

Na prática · mercado de trabalho

Um orçamento de tamanho por escrito ("primeira jogada ≤ 12 MB") é um requisito não funcional que times profissionais tratam como qualquer outro: entra na definição de pronto e é verificado a cada build (cap. 12). É o que evita descobrir o problema na véspera da entrega ao cliente.

Exercício 4.1 — Defina o seu orçamento

Use a calculadora com a rede e o número de alunos da ficha do exercício 1.2. Defina a espera máxima tolerável (ex.: 20 s) e derive o tamanho máximo em MB. Esse número é o seu orçamento.

Ver exemplo resolvido

Turma de 30 alunos num link de 60 Mbps compartilhado: cada aluno tem ~2 Mbps. Espera máxima aceitável: 30 s. MB = 30 × 2 ÷ 8 = 7,5 MB. Se o jogo tem 25 MB, o projeto precisa de Addressables (cap. 8) ou de uma estratégia de cache/pré-carregamento antes da aula.

Exercício 4.2 — Leia o Build Report

Faça uma build do seu projeto, abra o Editor.log, ache o Build Report e liste os três assets que mais pesam. Para cada um, escreva se ele é necessário na primeira jogada.

Capítulo 05 Aplicado

Reduzir o código: stripping, IL2CPP e exceções

O .wasm é o arquivo que mais pesa em projetos pequenos, e é o que mais atrapalha em celulares. Cortar código de motor que você não usa rende em dobro.

5.1 De onde vem o peso do código

5.2 A ordem certa de ataque

Comece pelos ganhos baratos e seguros, e deixe para o fim os que trazem risco de quebrar o jogo:

#AçãoOnde (varia por versão)Risco
1Desligar Development Build em builds de entrega (não comprimido e sem minificação)Build Profiles / Build SettingsBaixo
2Remover pacotes e módulos não usados (ex.: Input System, Unity UI, vídeo, física 3D em jogo 2D, terreno, tecido)Package Manager (inclusive a aba de módulos nativos)Baixo a médio: confira se algo do jogo dependia deles
3Strip Engine Code ligadoPlayer Settings → Other SettingsBaixo
4Managed Stripping Level em Medium ou HighPlayer Settings → Other Settings → OptimizationMédio: pode remover código usado só por reflexão
5Code Optimization orientado a tamanho (por exemplo, "Disk Size", ou "Disk Size with LTO" para a versão final)Build Profiles / Build Settings da plataforma WebBaixo, mas o build demora mais
6IL2CPP Code Generation em "Faster (smaller) builds"Player Settings → Other SettingsBaixo: a execução pode ficar um pouco mais lenta
7Enable Exceptions em "None" na versão finalPlayer Settings → Publishing SettingsAlto: veja o alerta abaixo
Armadilha: exceções desligadas

Com exceções em None, o jogo não consegue capturar exceções: um erro que antes era registrado e ignorado pode encerrar a execução. Só desligue depois de testar todas as fases. Durante o desenvolvimento, use uma opção com stack trace para conseguir diagnosticar.

5.3 Stripping sem quebrar: link.xml e [Preserve]

O linker da Unity remove o que parece não usado. Quem carrega tipos por reflexão, por serialização (JSON, por exemplo) ou por AssetBundles/Addressables pode ter uma classe removida e receber um erro só na build. Duas defesas:

<!-- Assets/link.xml -->
<linker>
  <assembly fullname="Assembly-CSharp">
    <type fullname="MeuJogo.Perguntas.PerguntaData" preserve="all"/>
  </assembly>
</linker>
using UnityEngine.Scripting;

[Preserve]   // impede o stripping desta classe
public class PerguntaData { public string enunciado; public string[] opcoes; public int correta; }

Regra prática: suba o nível de stripping e teste o jogo inteiro, com foco em tudo o que é lido de arquivo, rede ou dado dinâmico. Sempre que aparecer um erro de tipo ou método ausente só na build, preserve o tipo e registre no diário de builds.

5.4 Ferramentas para enxergar o motor

A Unity oferece o pacote Web Stripping Tool, que analisa o código do motor incluído no WebAssembly e permite remover submódulos que o seu projeto não usa (por exemplo, partes de gráficos 3D num jogo 2D). O fluxo é instalar o pacote, perfilar a build para ver o que é usado e configurar o stripping por submódulo. É uma otimização de segunda etapa, para quando os passos 1 a 7 já foram feitos.

Na prática · mercado de trabalho

Em portfólios, mostrar uma tabela "antes e depois" do wasm (com o que você mudou em cada linha) prova capacidade de investigar e de trabalhar com restrições. É o tipo de conteúdo que serve tanto para vagas de Unity quanto para as de engenharia de performance web.

Exercício 5.1 — Escada de otimização

Partindo do baseline do exercício 2.1, aplique as ações 1 a 6 da tabela, uma por vez, e registre o tamanho do .wasm após cada uma. Qual ação rendeu mais? Alguma não mudou nada?

Ver o que costuma acontecer

O maior ganho costuma vir de remover pacotes e módulos que puxavam código de motor (ação 2); em seguida vêm Strip Engine Code e o nível de stripping. O Code Optimization por tamanho ajuda, mas o efeito varia por projeto. Se uma ação "não mudou nada", é um sinal de que o código que ela cortaria já não estava lá: registre mesmo assim.

Exercício 5.2 — Quebre de propósito

Crie uma classe lida apenas por JSON e sem nenhuma referência direta no código. Suba o Managed Stripping Level para High, faça a build e veja o que acontece. Depois conserte com [Preserve] e com link.xml, e compare.

Capítulo 06 Aplicado

Reduzir os assets: texturas, áudio, malhas, fontes e shaders

O .data é onde o conteúdo do jogo vive, e também o que vira memória da aba. Em serious games, a maior parte do peso costuma ser textura e áudio.

6.1 Texturas: o maior alvo

6.2 Áudio

6.3 Malhas e animações

6.4 Shaders

6.5 Fontes, vídeo e a pasta Resources

Na prática · mercado de trabalho

Times de produção separam orçamento por categoria (ex.: texturas até 40%, áudio 25%, malhas 15%...) e o artista de arte técnica é quem faz cumprir. Se você trabalha sozinho, escreva os limites: "nenhuma textura acima de 1024 px sem justificativa; nenhuma música acima de 1 MB".

Exercício 6.1 — Auditoria de texturas

No Build Report do exercício 4.2, liste as 5 texturas mais pesadas. Para cada uma: em que tamanho aparece na tela, qual o Max Size atual e qual deveria ser, e se precisa de mipmaps. Aplique as mudanças e refaça a build.

Ver exemplo resolvido

Uma imagem de fundo de 4096×4096 mostrada em tela cheia (1920×1080) pode ir para 2048 sem perda visível: de 16 MP para 4 MP, cerca de 4 vezes menos memória e peso. Um ícone de 512 px exibido a 64 px pode ir para 128 px. Fundos 2D e interface normalmente dispensam mipmaps.

Exercício 6.2 — Orçamento por categoria

Com o orçamento do exercício 4.1, escreva quantos MB cabem em cada categoria (texturas, áudio, malhas, código, outros) e verifique quais estão estourando.

Capítulo 07 Aplicado

Compressão, servidor e cache

Metade dos jogos que "não abrem depois de publicar" tem a mesma causa: o servidor não diz ao navegador que os arquivos estão comprimidos.

7.1 As opções de compressão da Unity

OpçãoO que fazQuando escolher
GzipÉ o padrão. Suportado por todos os navegadores em HTTP e HTTPS. Tamanho médio; build rápido.Uso geral e desenvolvimento
BrotliArquivos bem menores que o Gzip, mas o build de release demora muito mais. Chrome e Firefox só o usam em HTTPS.Versão final, em HTTPS, quando você controla o servidor
DisabledSem compressão dos arquivos. O servidor ou a CDN pode comprimir na hora.Hospedagens que comprimem sozinhas; LMS que não deixam definir cabeçalhos (cap. 9)
Decompression Fallback (opção à parte, desligada por padrão)Embute um decompressor em JavaScript na build, que assume quando o navegador não descomprime. Funciona sem configurar o servidor, mas deixa o carregador maior e a descompressão mais lenta.Quando você não consegue configurar os cabeçalhos
Name Files As HashesNomeia os arquivos pelo hash do conteúdo, o que invalida o cache automaticamente a cada nova versão.Sempre que for usar cache longo (7.4)

7.2 O que o servidor precisa dizer

Para cada arquivo comprimido, o servidor deve responder com dois cabeçalhos: Content-Encoding (como está comprimido) e Content-Type do conteúdo original, não do arquivo comprimido:

ArquivoContent-EncodingContent-Type
*.wasm.br / *.wasm.gzbr / gzipapplication/wasm
*.js.br / *.js.gzbr / gzipapplication/javascript
*.data.br / *.data.gzbr / gzipapplication/octet-stream

O tipo application/wasm importa por dois motivos: é ele que permite ao navegador compilar o WebAssembly enquanto baixa (estágio 3 do cap. 3), e a falta dele gera avisos ou perda dessa otimização. Se algo estiver errado, o loader da Unity costuma exibir uma mensagem como "não foi possível ler o arquivo… a compressão da build foi ativada, mas o servidor não entregou o cabeçalho Content-Encoding".

7.3 Exemplos de servidor

Nginx, servindo arquivos Brotli pré-comprimidos:

location ~ \.data\.br$ {
    types {} default_type application/octet-stream;
    add_header Content-Encoding br;
}
location ~ \.wasm\.br$ {
    types {} default_type application/wasm;
    add_header Content-Encoding br;
}
location ~ \.js\.br$ {
    types {} default_type application/javascript;
    add_header Content-Encoding br;
}

Apache (o servidor trata cada extensão separadamente: .br é a codificação, .wasm é o tipo):

<IfModule mod_mime.c>
  AddEncoding br .br
  AddEncoding gzip .gz
  AddType application/wasm .wasm
  AddType application/javascript .js
  AddType application/octet-stream .data
</IfModule>

Para Vercel e Netlify, defina os cabeçalhos por padrão de caminho no arquivo de configuração da plataforma (headers no vercel.json; arquivo _headers no Netlify), seguindo a tabela 7.2. Em S3 + CloudFront, defina Content-Encoding e Content-Type como metadados de cada objeto ao enviar. Sempre teste:

curl -sI -H "Accept-Encoding: br, gzip" https://seu-site/Build/jogo.wasm.br | grep -i -E "content-(type|encoding)"
# esperado:  content-encoding: br   e   content-type: application/wasm

7.4 Cache

7.5 Onde hospedar: o que muda

DestinoControle de cabeçalhosEstratégia
itch.ioTratado pela plataforma para builds Unity comprimidas (teste a sua)Envie o zip da build; ótimo para portfólio
GitHub PagesNão dá para definir cabeçalhos por arquivoCompressão Disabled ou Gzip com Decompression Fallback
Vercel / NetlifySim, por arquivo de configuraçãoBrotli ou Gzip pré-comprimidos + cabeçalhos da 7.2
S3 + CloudFrontSim, por metadadosPré-comprimido com metadados, ou Disabled com compressão da CDN
Dentro do LMS (SCORM)Normalmente nãoDisabled ou Decompression Fallback (cap. 9)
Na prática · mercado de trabalho

Saber diagnosticar "a build abre no itch.io mas não no LMS do cliente" pela aba Network (é quase sempre Content-Encoding ou Content-Type) é a habilidade de suporte que mais economiza horas em projetos de e-learning.

Exercício 7.1 — Bateria de testes

Faça a mesma build em Gzip, Brotli e Disabled, publique nas hospedagens que você tem e preencha: tamanho transferido, tempo até jogar, se abre. Qual combinação vence no seu público?

Exercício 7.2 — Erro proposital

Remova o cabeçalho Content-Encoding do servidor de teste e recarregue. Anote a mensagem de erro do loader. Ligue o Decompression Fallback e recarregue: o que mudou no tamanho do .loader.js e no tempo de carga?

Ver o que esperar

Sem o cabeçalho, o jogo falha ao ler os arquivos comprimidos e mostra uma mensagem sobre a configuração do servidor. Com o fallback ligado, o jogo abre, o carregador fica maior (porque inclui o decompressor) e a carga demora mais que com o cabeçalho correto. É a prova de que o fallback é uma rede de segurança, e não uma solução.

Capítulo 08 Avançado

Carregar menos: Addressables, memória e tela de loading

Quando reduzir não basta, o jogo passa a carregar só o que a primeira jogada precisa e busca o resto enquanto o aluno joga.

8.1 Addressables: build pequena + conteúdo remoto

A ideia: a build Web inicial contém apenas um "bootstrap" (menu, tela de loading, sistema de dados) e o conteúdo de cada fase ou módulo vive em grupos Addressables hospedados numa CDN. O jogador baixa cada fase quando precisa dela, e o navegador guarda os pacotes em cache.

using System.Collections;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class CarregadorDeFase : MonoBehaviour
{
    [SerializeField] Slider barra;

    public IEnumerator Carregar(string chaveDaFase)
    {
        // 1) Quanto falta baixar? (0 se já está em cache)
        var tamanho = Addressables.GetDownloadSizeAsync(chaveDaFase);
        yield return tamanho;

        if (tamanho.Result > 0)
        {
            var baixar = Addressables.DownloadDependenciesAsync(chaveDaFase);
            while (!baixar.IsDone) { barra.value = baixar.PercentComplete; yield return null; }
            Addressables.Release(baixar);
        }
        Addressables.Release(tamanho);

        // 2) Entrar na fase (a cena é um endereço)
        yield return Addressables.LoadSceneAsync(chaveDaFase, LoadSceneMode.Single);
    }
}

Pontos de atenção específicos da web:

Para outras cenas grandes que ficam na própria build, use SceneManager.LoadSceneAsync: o progresso reportado para em 0,9 até você liberar allowSceneActivation, o que permite terminar a tela de loading antes de trocar.

8.2 Tela de loading no template

O carregador da Unity só cria o jogo depois que createUnityInstance termina, e aceita um callback de progresso de 0 a 1. Personalize um WebGL Template (pasta Assets/WebGLTemplates/SuaMarca, escolhida em Player Settings → Resolution and Presentation) para mostrar marca, barra e mensagem em HTML:

<canvas id="unity-canvas" tabindex="-1"></canvas>
<div id="loading">
  <div id="bar"><div id="fill"></div></div>
  <p id="msg">Carregando o jogo…</p>
</div>
<script>
  var s = document.createElement("script");
  s.src = "Build/{{{ LOADER_FILENAME }}}";
  s.onload = function () {
    createUnityInstance(document.querySelector("#unity-canvas"), {
      dataUrl: "Build/{{{ DATA_FILENAME }}}",
      frameworkUrl: "Build/{{{ FRAMEWORK_FILENAME }}}",
      codeUrl: "Build/{{{ CODE_FILENAME }}}",
      streamingAssetsUrl: "StreamingAssets",
      companyName: "{{{ COMPANY_NAME }}}",
      productName: "{{{ PRODUCT_NAME }}}",
      productVersion: "{{{ PRODUCT_VERSION }}}",
      devicePixelRatio: 1            // menos pixels em telas de alta densidade
    }, function (p) {
      document.getElementById("fill").style.width = (p * 100) + "%";
    }).then(function (u) {
      window.unityInstance = u;
      document.getElementById("loading").hidden = true;
    }).catch(function (e) {
      document.getElementById("msg").textContent =
        "Não foi possível carregar o jogo. Recarregue a página.";
      console.error(e);
    });
  };
  document.body.appendChild(s);
</script>

As variáveis {{{ … }}} são preenchidas pela Unity na hora do build, com os nomes reais dos arquivos. Repare no .catch: uma mensagem humana vale mais que uma tela em branco. E o devicePixelRatio: 1 reduz a resolução interna em telas de alta densidade, o que ajuda bastante em celulares.

8.3 Percepção de espera

8.4 Data caching

Em Player Settings → Publishing Settings, a opção Data Caching guarda o .data no IndexedDB do navegador, evitando baixá-lo de novo em visitas seguintes (útil em turmas que jogam toda semana). Três ressalvas: ele não evita o desempacotar (estágio 4); a cada versão nova, o cache precisa ser reconstruído; e o navegador pode limitar ou negar armazenamento (janela privada, cotas, iframes de terceiros em alguns navegadores). Sempre teste em janela anônima e não dependa dele para a jogabilidade.

8.5 Memória: o limite silencioso

A memória da aba tem três áreas: o heap da Unity (objetos, cenas, shaders), os dados (o .data desempacotado) e o comportamento do coletor de lixo. Nas configurações da build Web, o Initial Memory Size (tamanho inicial do heap) e o Memory Growth Mode controlam como a memória cresce. Recomendações:

8.6 Celulares e tablets

As versões recentes da Unity suportam navegadores móveis (Chrome no Android, Safari no iOS), mas com limites de memória menores e comportamentos próprios: teclado virtual em campos de texto do jogo historicamente exige soluções em HTML/.jslib; a tela cheia no iPhone tem restrições; e a orientação da tela precisa ser tratada. Se o seu público usa celular, inclua-o na matriz de testes desde o começo (cap. 12).

Na prática · mercado de trabalho

"Carregamento em duas fases com Addressables e conteúdo remoto" é um requisito recorrente em jogos de treinamento com muitos módulos. Saber montar isso e hospedar numa CDN com CORS correto é o que permite entregar um curso de 20 fases sem que a primeira abertura leve minutos.

Exercício 8.1 — Plano de carregamento

Para um jogo de 6 fases de ~5 MB cada e um menu de ~4 MB, desenhe: o que vai na build inicial, o que vai em Addressables, quando cada grupo é baixado e o que o aluno vê em cada momento. Compare a espera da primeira jogada com a de uma build única.

Ver exemplo resolvido

Build única: ~34 MB antes de qualquer jogada (~54 s a 5 Mbps). Com Addressables: build inicial ~4 MB + fase 1 (~5 MB) = ~9 MB antes da primeira jogada (~14 s a 5 Mbps); as fases 2 a 6 são baixadas durante a fase anterior ou na tela de seleção. O custo: mais complexidade e o tratamento de falhas de rede.

Exercício 8.2 — Template próprio

Crie um WebGL Template com a sua marca, barra de progresso real e mensagem de erro amigável. Teste-o com a rede lenta do DevTools e com o servidor "quebrado" do exercício 7.2.

Capítulo 09 Avançado

SCORM na prática: empacotar e reportar ao LMS

SCORM é antigo, limitado e onipresente. Se o seu cliente tem um LMS, é provável que a pergunta dele seja "vocês exportam SCORM?".

9.1 O que é o SCORM

SCORM (Sharable Content Object Reference Model) é um conjunto de especificações da ADL que faz duas coisas: define como empacotar um conteúdo (um zip com um arquivo imsmanifest.xml) e como esse conteúdo conversa com o LMS (uma API em JavaScript que o LMS expõe). As versões que você encontra são a 1.2 (a mais compatível) e a 2004 (várias edições; com sequenciamento mais rico e mais complexidade). O que o SCORM registra é pouco: status de conclusão, nota, tempo e um pouco de estado salvo. Para dados mais ricos, veja o cap. 10.

9.2 Como a conversa acontece

O LMS abre o seu conteúdo (o index.html da build) dentro de uma janela ou iframe e coloca nessa hierarquia um objeto: window.API (SCORM 1.2) ou window.API_1484_11 (SCORM 2004). O seu código procura esse objeto subindo pela cadeia de window.parent e depois chama métodos:

IdeiaSCORM 1.2SCORM 2004
Objeto da APIAPIAPI_1484_11
Iniciar / encerrarLMSInitialize("") / LMSFinish("")Initialize("") / Terminate("")
Gravar / lerLMSSetValue(k, v) / LMSGetValue(k)SetValue(k, v) / GetValue(k)
SalvarLMSCommit("")Commit("")
Conclusão / aprovaçãocmi.core.lesson_status (passed, failed, completed, incomplete, browsed, not attempted)cmi.completion_status + cmi.success_status (separados)
Notacmi.core.score.raw (0 a 100)cmi.score.raw e cmi.score.scaled (−1 a 1)
Tempo da sessãocmi.core.session_time (formato HH:MM:SS)cmi.session_time (duração ISO 8601, ex. PT4M12S)
Estado salvocmi.suspend_data (até 4096 caracteres)cmi.suspend_data (até 64000 caracteres)

9.3 A ponte JavaScript ↔ C#

O jogo em C# não enxerga window.API. A ponte tem três peças: um bridge em JavaScript no template (acha a API e a envolve), um plugin .jslib (a ponte entre o wasm e o JavaScript) e uma classe C# que o resto do jogo chama. Este exemplo é SCORM 1.2.

1) No index.html do template:

<script>
window.scormBridge = (function () {
  function achar(win) {
    for (var n = 0; n < 10; n++) {
      try { if (win.API) return win.API; } catch (e) { return null; } // outro domínio: bloqueado
      if (!win.parent || win.parent === win) return null;
      win = win.parent;
    }
    return null;
  }
  var api = achar(window) || (window.opener ? achar(window.opener) : null);
  var ok = !!api && String(api.LMSInitialize("")) === "true";
  return {
    ativo:    function ()    { return ok; },
    setValue: function (k, v){ return ok && String(api.LMSSetValue(k, v)) === "true"; },
    getValue: function (k)   { return ok ? String(api.LMSGetValue(k)) : ""; },
    commit:   function ()    { return ok && String(api.LMSCommit("")) === "true"; },
    finish:   function ()    { if (ok) { api.LMSCommit(""); api.LMSFinish(""); ok = false; } }
  };
})();
window.addEventListener("beforeunload", function () { window.scormBridge.finish(); });
</script>

2) Assets/Plugins/WebGL/ScormBridge.jslib:

mergeInto(LibraryManager.library, {
  ScormSetValue: function (keyPtr, valuePtr) {
    var k = UTF8ToString(keyPtr), v = UTF8ToString(valuePtr);
    return (window.scormBridge && window.scormBridge.setValue(k, v)) ? 1 : 0;
  },
  ScormCommit: function () {
    if (window.scormBridge) window.scormBridge.commit();
  },
  ScormAtivo: function () {
    return (window.scormBridge && window.scormBridge.ativo()) ? 1 : 0;
  }
});

3) Scorm12.cs:

using System.Runtime.InteropServices;
using UnityEngine;

public static class Scorm12
{
#if UNITY_WEBGL && !UNITY_EDITOR
    [DllImport("__Internal")] static extern int  ScormSetValue(string key, string value);
    [DllImport("__Internal")] static extern void ScormCommit();
    [DllImport("__Internal")] static extern int  ScormAtivo();
#else
    // No Editor e fora do LMS, apenas registra no Console.
    static int  ScormSetValue(string k, string v) { Debug.Log($"[SCORM] {k} = {v}"); return 1; }
    static void ScormCommit() { }
    static int  ScormAtivo() { return 0; }
#endif

    public static bool Ativo => ScormAtivo() == 1;

    public static void ReportarResultado(int notaPercentual, bool aprovado, int segundos)
    {
        ScormSetValue("cmi.core.score.raw", Mathf.Clamp(notaPercentual, 0, 100).ToString());
        ScormSetValue("cmi.core.lesson_status", aprovado ? "passed" : "failed");
        ScormSetValue("cmi.core.session_time", $"{segundos / 3600:0000}:{segundos % 3600 / 60:00}:{segundos % 60:00}");
        ScormCommit();
    }
}

Três cuidados: chame LMSCommit em marcos (fim de fase), não a cada frame; guarde suspend_data num formato compacto (o limite do 1.2 é de 4096 caracteres); e, se o manifesto define uma nota mínima (adlcp:masteryscore), muitos LMS decidem aprovado ou reprovado a partir da nota, ignorando o status que você enviou.

9.4 O manifesto e o pacote

O pacote é um zip com imsmanifest.xml na raiz, ao lado de index.html, Build/, TemplateData/ etc. Um manifesto mínimo do SCORM 1.2:

<?xml version="1.0" encoding="UTF-8"?>
<manifest identifier="br.com.exemplo.jogo-fracoes" version="1"
  xmlns="http://www.imsproject.org/xsd/imscp_rootv1p1p2"
  xmlns:adlcp="http://www.adlnet.org/xsd/adlcp_rootv1p2"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <metadata>
    <schema>ADL SCORM</schema>
    <schemaversion>1.2</schemaversion>
  </metadata>
  <organizations default="org1">
    <organization identifier="org1">
      <title>Jogo das Frações</title>
      <item identifier="item1" identifierref="res1">
        <title>Jogo das Frações</title>
      </item>
    </organization>
  </organizations>
  <resources>
    <resource identifier="res1" type="webcontent" adlcp:scormtype="sco" href="index.html">
      <file href="index.html"/>
      <!-- liste todos os arquivos do pacote (ou gere a lista com uma ferramenta) -->
    </resource>
  </resources>
</manifest>

Repare no adlcp:scormtype em minúsculas: é o nome no SCORM 1.2; no 2004 é adlcp:scormType. Erros de manifesto são a segunda causa de "o LMS não aceita o pacote". Teste sempre no SCORM Cloud (serviço da Rustici para validar pacotes) e em um Moodle local antes de entregar.

9.5 Os quatro problemas que só aparecem no LMS

Problema 1

Compressão sem cabeçalho

O LMS serve os arquivos do pacote com os seus próprios cabeçalhos, sem Content-Encoding para .br ou .gz. Solução: gere a build com compressão Disabled (ou use Decompression Fallback) para o pacote SCORM.

Problema 2

Limite de upload

Muitos LMS limitam o tamanho do pacote (é configurável, mas o administrador precisa alterar). Uma build de 100 MB descomprimida bate nesse teto. Solução: reduzir a build (caps. 5 e 6) ou usar um pacote-casca (9.6).

Problema 3

Outro domínio, sem API

Se o jogo está hospedado fora do LMS (em outra origem), o navegador impede o acesso a window.parent.API. O bridge não acha a API e nada é registrado. Solução: 9.6 ou cmi5 (cap. 10).

Problema 4

Armazenamento em iframe

Dentro de um iframe de outro domínio, alguns navegadores particionam ou bloqueiam o IndexedDB, onde PlayerPrefs e o data caching guardam dados. Não use PlayerPrefs como única cópia do progresso: use suspend_data ou um servidor.

9.6 O pacote-casca (hospedar fora, reportar dentro)

Quando a build é grande demais para o LMS ou precisa de cabeçalhos que ele não permite, o pacote SCORM contém só uma página-casca pequena. Ela encontra a API do SCORM (está na mesma origem do LMS) e embute o jogo hospedado na sua CDN num iframe, trocando mensagens com ele via postMessage: o jogo envia "fim da fase, nota 80" e a casca traduz para LMSSetValue. É o mesmo desenho do bridge do 9.3, com uma camada de mensagens no meio. Validar a origem da mensagem (event.origin) é obrigatório, senão qualquer página poderia registrar notas.

// Na página-casca (dentro do pacote SCORM), escutando o jogo hospedado fora:
var ORIGEM_DO_JOGO = "https://jogos.exemplo.com.br";
window.addEventListener("message", function (ev) {
  if (ev.origin !== ORIGEM_DO_JOGO) return;          // ignora qualquer outra origem
  var m = ev.data || {};
  if (m.tipo === "resultado") {
    scormBridge.setValue("cmi.core.score.raw", String(m.nota));
    scormBridge.setValue("cmi.core.lesson_status", m.aprovado ? "passed" : "failed");
    scormBridge.commit();
  }
});

9.7 Limites do SCORM (e por que o cap. 10 existe)

Na prática · mercado de trabalho

Editais e contratos corporativos costumam listar "exportação SCORM 1.2 ou 2004" como requisito. Ter um pacote-modelo já testado no SCORM Cloud e em Moodle (com o bridge, o manifesto e o processo de build) vira ativo reutilizável de portfólio e de proposta comercial. Para o lado pedagógico e de plataformas, veja Técnicas Modernas de Ensino e Aprendizagem.

Exercício 9.1 — Primeiro pacote

Pegue a build do exercício 2.1 (compressão Disabled), acrescente o bridge, a classe Scorm12 e um botão "Concluir" que envia nota 100 e passed. Escreva o manifesto, gere o zip e suba no SCORM Cloud. Confira se a nota e o status chegaram.

Ver checklist de erros comuns

1) imsmanifest.xml dentro de uma subpasta em vez de na raiz do zip. 2) O href do recurso não bate com o nome real do index.html. 3) Compressão da build ligada e sem cabeçalhos. 4) Esqueceu de chamar LMSCommit. 5) Testou só no Editor, onde o bridge não existe.

Exercício 9.2 — Migrar para SCORM 2004

Usando a tabela 9.2, escreva o que muda no bridge, no .jslib e na classe C# para suportar SCORM 2004 (nome da API, métodos, chaves, formato de tempo e de nota).

Capítulo 10 Avançado · Interativo

xAPI, LRS e cmi5: dados além do LMS

Se o SCORM diz "passou ou não passou", o xAPI conta a história: quem fez o quê, em qual contexto, com qual resultado.

10.1 O modelo em uma frase

O xAPI (Experience API, também chamado Tin Can) registra experiências de aprendizagem como statements, frases estruturadas em JSON no formato ator + verbo + objeto: "Ana respondeu à pergunta 4 do nível 2". Os statements vão por HTTP para um LRS (Learning Record Store), que os guarda e permite consultar. Ao contrário do SCORM, o jogo não precisa estar dentro do LMS: pode estar em qualquer lugar da web. A versão 1.0.3 é a mais implantada; o xAPI 2.0 foi padronizado pelo IEEE em 2023.

10.2 Anatomia de um statement

{
  "actor": {
    "objectType": "Agent",
    "account": { "homePage": "https://jogos.exemplo.com.br", "name": "aluno-4f2a91" }
  },
  "verb": {
    "id": "http://adlnet.gov/expapi/verbs/answered",
    "display": { "pt-BR": "respondeu" }
  },
  "object": {
    "objectType": "Activity",
    "id": "https://jogos.exemplo.com.br/fracoes/nivel-2/pergunta-4",
    "definition": { "name": { "pt-BR": "Fração equivalente a 3/4" }, "type": "http://adlnet.gov/expapi/activities/cmi.interaction" }
  },
  "result": {
    "success": false,
    "response": "6/9",
    "duration": "PT23S"
  },
  "context": {
    "registration": "0b3f5ec3-2d09-4b9e-9f4a-6a1a8f5e7a10",
    "contextActivities": { "parent": [ { "id": "https://jogos.exemplo.com.br/fracoes/nivel-2" } ] }
  },
  "timestamp": "2026-09-19T14:03:22.000Z"
}
CampoPara que serveCuidado
actorQuem fez. Identificado por conta (account) ou e-mail (mbox).Prefira um identificador pseudônimo (cap. 11), não o e-mail.
verbO que fez. Tem um IRI (um identificador em formato de URL) e um texto para exibição.Reaproveite verbos do vocabulário ADL (completed, passed, failed, attempted, answered, experienced, progressed...) antes de inventar.
objectSobre o que. Uma atividade com IRI estável e nome.O IRI deve ser estável: se mudar a cada versão, você não consegue comparar resultados.
resultNota (score.scaled de 0 a 1, raw, min, max), success, completion, response, duration.A duração é ISO 8601 (PT23S, PT4M12S).
contextEm que contexto: sessão (registration), atividade-pai, extensões próprias.É aqui que você liga a pergunta à fase e ao curso.

10.3 Demo: monte um statement

Altere os campos e veja o JSON mudar. Repare que o ator usa uma conta pseudônima e que a duração é convertida para o formato ISO 8601.


  

10.4 O LRS

Um LRS expõe um endpoint /statements: você faz POST com o JSON, envia o cabeçalho X-Experience-API-Version (por exemplo, 1.0.3) e credenciais. Há LRS de código aberto que você hospeda (como o Learning Locker, o Yet Analytics SQL LRS e o Trax LRS), LRS comerciais com plano gratuito para testes e o LRS embutido do SCORM Cloud. Confirme os planos e a manutenção atuais antes de escolher.

10.5 Enviando statements do Unity

Na build Web, use UnityWebRequest (nada de HttpClient ou sockets). Bibliotecas de xAPI para .NET existem, mas confirme a manutenção e a compatibilidade com IL2CPP e WebGL antes de adotar; para um envio simples, cerca de trinta linhas resolvem e você controla tudo:

using System.Collections;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;

public class XapiSender : MonoBehaviour
{
    [SerializeField] string endpoint = "https://lrs.exemplo.com.br/xapi/statements";
    string authorization;  // recebido em tempo de execução; nunca escrito no projeto

    public void Configurar(string valorAuthorization) => authorization = valorAuthorization;

    public IEnumerator Enviar(string statementJson)
    {
        using var req = new UnityWebRequest(endpoint, UnityWebRequest.kHttpVerbPOST);
        req.uploadHandler   = new UploadHandlerRaw(Encoding.UTF8.GetBytes(statementJson));
        req.downloadHandler = new DownloadHandlerBuffer();
        req.SetRequestHeader("Content-Type", "application/json");
        req.SetRequestHeader("X-Experience-API-Version", "1.0.3");
        req.SetRequestHeader("Authorization", authorization);

        yield return req.SendWebRequest();

        if (req.result != UnityWebRequest.Result.Success)
            Debug.LogWarning($"xAPI falhou ({req.responseCode}): {req.error}");  // enfileirar para tentar de novo
    }
}
Segurança: nunca embuta credenciais do LRS na build

Tudo o que vai no .data ou no .wasm pode ser extraído por qualquer pessoa com o DevTools. Se a chave do LRS está no projeto, qualquer aluno consegue gravar (ou apagar) statements de qualquer outro. A saída correta é uma credencial de curta duração entregue em tempo de execução: pelo cmi5 (10.7) ou por um pequeno servidor seu que valida a sessão e repassa ao LRS.

10.6 Robustez: rede, CORS e fim de sessão

10.7 cmi5: o xAPI lançado por um LMS

O cmi5 é um perfil do xAPI para conteúdo lançado por um LMS: ele resolve o que o SCORM não resolve, porque não depende da janela pai, então o jogo pode estar hospedado em qualquer domínio. O LMS abre a URL do jogo com parâmetros de lançamento:

O perfil também define um ciclo de vida: initialized deve ser o primeiro statement da sessão e terminated o último; no meio ficam completed, passed, failed, abandoned, waived e quantos statements xAPI próprios você quiser. O suporte a cmi5 varia entre LMS: confira o do seu cliente antes de prometê-lo.

10.8 Comparando os três caminhos

SCORMxAPI "solto"cmi5
Quem lançaLMSQualquer um (link, app, LMS)LMS
Onde o jogo pode estarNo LMS (ou pacote-casca)Qualquer domínioQualquer domínio
DadosNota, status, tempo, estadoQualquer eventoQualquer evento + ciclo de vida definido
AutorizaçãoSessão do LMSVocê resolve (risco de chave exposta)Token de curta duração vindo do LMS
Compatibilidade de LMSQuase universalDepende de LRSVaria (cresceu, mas não é universal)
Quando escolherO cliente exige "SCORM" e pontoAnálise rica, fora de LMS (portfólio, pesquisa)LMS moderno + jogo hospedado fora
Na prática · mercado de trabalho

"Trabalho com SCORM e xAPI" é diferencial em vagas de EdTech, treinamento corporativo e learning analytics. Um portfólio com um jogo publicado, um dashboard simples lendo o LRS e um texto explicando o que se decidiu com os dados mostra o ciclo completo, e não só a parte de programação.

Exercício 10.1 — Vocabulário de eventos

Para o jogo da ficha do exercício 1.2, escreva a lista de statements que ele enviará: para cada um, o verbo (do vocabulário ADL, quando possível), o IRI da atividade e o que vai em result e context. Limite-se a 8 tipos de evento.

Ver exemplo resolvido

(1) initialized no início da sessão; (2) attempted ao abrir cada fase; (3) answered a cada pergunta, com response, success e duration; (4) progressed ao concluir 50% de uma fase (com progress em extensão); (5) completed ao fim de cada fase, com score.scaled; (6) passed ou failed no fim do jogo; (7) terminated ao sair; (8) um evento próprio (extensão) para "pediu dica". Todos com registration e a fase como atividade-pai.

Exercício 10.2 — Ponta a ponta

Suba um LRS de teste (o do SCORM Cloud ou um de código aberto local), envie três statements a partir da sua build Web usando a classe XapiSender e confirme-os na interface do LRS. Depois, remova o CORS do LRS e observe o erro no Console.

Capítulo 11 Muito avançado

Desenho da evidência, privacidade e acessibilidade

Enviar dados é fácil; enviar dados que respondam a uma pergunta pedagógica é o trabalho de verdade. E nada disso vale se o dado for ilegal ou se o aluno não consegue jogar.

11.1 Comece pela decisão, não pelos eventos

O erro típico é registrar tudo "porque pode ser útil". Você acaba com milhões de linhas e nenhuma resposta. Faça o caminho inverso, inspirado no Evidence-Centered Design (Mislevy, Almond e Lukas, 2003):

1

Modelo do aluno

O que você quer saber? Ex.: "domina frações equivalentes?", "sabe seguir o protocolo de evacuação?".

2

Modelo de tarefa

Que situações do jogo obrigam o aluno a mostrar esse conhecimento? Ex.: um puzzle em que só a fração equivalente abre a porta.

3

Modelo de evidência

Que comportamentos observáveis contam como sinal, e como interpretá-los? Ex.: acertos, tipo de erro, tentativas, uso de dica.

4

Instrumentação

Só agora: quais statements o jogo envia para registrar essas evidências.

Essa cadeia liga cada evento a uma pergunta. É também a base da avaliação embutida (stealth assessment), defendida por Valerie Shute: a avaliação acontece dentro do jogo, sem interromper a experiência com um teste.

11.2 Taxonomia de eventos de um serious game

CategoriaExemplosPergunta que responde
Ciclo de vidainiciou, terminou, abandonou, retomouQuantos chegam ao fim? Onde desistem?
Progressoabriu fase, concluiu fase, tempo por faseOnde o jogo trava a turma?
Desempenhorespondeu, acertou/errou, tentativa nºQuais conceitos ele domina?
Erro qualificadoescolheu o distrator B, erro de sinalQual é a concepção equivocada por trás do erro?
Apoiopediu dica, abriu glossárioDe que ajuda precisa?
Decisãoescolheu a opção X num dilemaComo pensa em situações abertas? (simulações, soft skills)

11.3 Perfis e padrões

Para não reinventar a semântica, procure perfis xAPI existentes. O xAPI Serious Games Profile (xAPI-SG), produzido por pesquisadores da Universidad Complutense de Madrid, define verbos, atividades e extensões para eventos de jogos educativos (iniciar e completar níveis, escolhas, uso de itens). Vale conhecê-lo como referência de vocabulário; confirme a versão e a manutenção antes de basear o projeto nele. Um vocabulário compartilhado é o que permite comparar resultados entre jogos e turmas.

11.4 Privacidade: LGPD e minimização

Statements descrevem pessoas, muitas vezes crianças. Trate-os como dados pessoais:

11.5 Acessibilidade em jogos web

Um jogo em canvas é opaco para leitores de tela: o navegador enxerga um retângulo de pixels. Se o serious game é para um público amplo (e em contexto educacional ou corporativo, frequentemente há obrigação de acessibilidade), planeje desde o início:

Na prática · mercado de trabalho

Learning engineers e designers instrucionais valorizam quem consegue traduzir "queremos saber se aprenderam" em um plano de evidência antes de escrever uma linha de código. É a diferença entre entregar um jogo e entregar uma solução de aprendizagem auditável. Para o lado de ciência de dados desse trabalho, veja também Datavis com Python e Webdesign.

Exercício 11.1 — Cadeia de evidência

Para um objetivo de aprendizagem do seu jogo, preencha as quatro caixas de 11.1 e derive dois statements que respondam à pergunta. Descarte um evento que você ia registrar e que não responde a nenhuma pergunta.

Ver exemplo resolvido

Modelo do aluno: reconhece frações equivalentes. Tarefa: escolher, entre quatro portas, a que mostra fração equivalente a 3/4. Evidência: resposta correta na 1ª tentativa; tipo de erro (compara só numeradores? só denominadores?). Statements: answered com response, success e uma extensão tipo-de-erro; progressed ao fechar o conjunto de portas. Descartado: "moveu o mouse" (não responde a nada).

Exercício 11.2 — Auditoria de privacidade

Liste todo dado pessoal que o seu jogo coleta, ou coletaria, e classifique cada item como necessário ou dispensável. Para os necessários, defina o pseudônimo, o prazo de retenção e quem pode ver.

Exercício 11.3 — Teste de acessibilidade

Jogue a primeira fase só com o teclado, depois em 200% de zoom e depois com o som desligado. Anote onde o jogo deixa de ser jogável ou compreensível.

Capítulo 12 Prática · Plano

Pipeline, cenários de decisão e plano de 30 dias

Tudo o que vimos vira rotina quando a build, o teste e a verificação de tamanho deixam de ser manuais.

12.1 Automatizando o build

Faça a build Web em integração contínua para que qualquer commit gere um pacote testável. O projeto de código aberto GameCI oferece ações do GitHub para compilar projetos Unity (é preciso configurar a licença); o resultado é uma pasta Build pronta para publicar. Depois da build, verifique o orçamento do cap. 4 automaticamente:

#!/usr/bin/env bash
# Falha o pipeline se o total transferido passar do orçamento (em MB)
ORCAMENTO_MB=12
TOTAL_KB=$(du -sk Builds/Web/Build | cut -f1)
LIMITE_KB=$((ORCAMENTO_MB * 1024))
echo "Build: $((TOTAL_KB / 1024)) MB (limite: ${ORCAMENTO_MB} MB)"
if [ "$TOTAL_KB" -gt "$LIMITE_KB" ]; then
  echo "ERRO: a build estourou o orçamento de tamanho"; exit 1
fi

Faça o mesmo com o servidor: um passo de curl -I que confere Content-Encoding e Content-Type depois de cada deploy (7.3). Para o lado de infraestrutura, veja DevOps, Docker e Kubernetes.

12.2 Matriz de testes

EixoItens mínimos
NavegadoresChrome, Firefox, Safari (macOS e iOS), Edge; Chrome no Android
RedePerfil lento do DevTools; cache limpo e cache preenchido; janela anônima
DispositivoUm PC fraco, um Chromebook, um celular mediano
ContextoDireto no link; dentro do iframe do LMS; SCORM Cloud e Moodle
ServidorContent-Encoding, Content-Type, cache, CORS do LRS e da CDN
DadosStatements chegam ao LRS; nota e status chegam ao LMS; fim de sessão registra
AcessibilidadeTeclado, zoom, sem som

12.3 Cenários de decisão (ilustrativos)

Os cenários abaixo são hipotéticos, construídos para treinar a decisão; não descrevem projetos reais.

Cenário A — A escola com Wi-Fi compartilhado

Educação básica

Uma rede municipal quer um jogo de matemática em Chromebooks, entregue pelo Moodle. Cada turma tem 30 alunos e um link compartilhado de 60 Mbps. A build atual tem 28 MB e o Moodle limita uploads a 50 MB.

Decisão: orçamento de ~8 MB (cap. 4); build inicial mínima + Addressables (cap. 8); pacote-casca SCORM com o jogo na CDN (9.6); compressão Brotli na CDN, em HTTPS, ou Disabled dentro do pacote.

Lição — O contexto de rede e o LMS definem a arquitetura antes de qualquer otimização de asset.

Cenário B — Treinamento de segurança corporativo

Corporativo

Uma empresa quer um simulador de identificação de riscos numa fábrica virtual. O LMS é moderno e o RH quer saber quais riscos os funcionários mais deixam passar, além de "concluiu ou não".

Decisão: cmi5 + xAPI (cap. 10), com evento por risco identificado ou ignorado, incluindo o tipo de risco; nota e conclusão também reportadas. Jogo hospedado fora do LMS, com token de curta duração.

Lição — Quando a pergunta é "o quê, especificamente", só o modelo de dados do xAPI responde.

Cenário C — Portfólio de desenvolvedor

Carreira

Uma pessoa quer um serious game jogável no portfólio, sem LMS. O recrutador deve conseguir jogar em segundos pelo celular.

Decisão: build enxuta (≤ 10 MB), hospedagem em itch.io ou Vercel, compressão testada, uma tela "Toque para começar", e um LRS de teste com dashboard simples mostrando os dados de uso.

Lição — Para demonstrar competência, o tempo até jogar importa tanto quanto a jogabilidade.

Cenário D — O cliente exige SCORM e o jogo tem 90 MB

Conflito de requisitos

O LMS aceita só pacotes SCORM de até 100 MB e a pessoa administradora não pode alterar o limite. Os 90 MB descomprimidos estouram o teto depois de adicionar os arquivos auxiliares, e o LMS não serve arquivos comprimidos.

Decisão: pacote-casca (9.6) apontando para a CDN, com CORS e validação de origem; ou reduzir a build e mover o conteúdo pesado para Addressables. Documentar a decisão e o risco da dependência externa.

Lição — Restrições contratuais entram no pipeline como requisitos técnicos. Registre a decisão por escrito.

12.4 Checklist de entrega

Passe os 12 pontos antes de entregar
  1. Build de release (Development Build desligado)?
  2. Tamanho transferido dentro do orçamento escrito?
  3. Managed Stripping, Strip Engine Code e módulos não usados revisados, e o jogo testado inteiro depois disso?
  4. Compressão compatível com o destino (servidor, CDN ou LMS)?
  5. Content-Encoding e Content-Type conferidos com curl -I?
  6. Nomes com hash e cache configurados; index.html sem cache longo?
  7. Tela de loading com progresso real, mensagem de erro e "Toque para começar"?
  8. Testado em celular, em rede lenta e em janela anônima?
  9. Pacote SCORM validado no SCORM Cloud e no LMS do cliente?
  10. Nenhuma credencial embutida na build?
  11. Eventos que respondem a perguntas, dados pseudonimizados e política de privacidade explicada?
  12. Acessibilidade mínima (teclado, contraste, legendas) verificada?

12.5 Plano de 30 dias (~45 min/dia)

SemanaFocoEntregável
1 — Base e mediçãoCaps. 1 a 4: build vazia, medição, orçamento, diário de builds.Diário de builds com o baseline e o orçamento escrito
2 — EnxugarCaps. 5 a 7: código, assets, compressão e servidor; teste com curl -I.Build do seu jogo dentro do orçamento, publicada e verificada
3 — Carregar e empacotarCap. 8 e 9: template com loading, Addressables (se precisar), pacote SCORM validado.Pacote SCORM funcionando no SCORM Cloud
4 — Dados e entregaCaps. 10 e 11: plano de evidência, statements para um LRS, privacidade, acessibilidade.Jogo publicado + LRS com dados reais + checklist 12.4 preenchido

12.6 Bibliografia e fontes para seguir

Verifique na sua versão

Nomes de menus, valores padrão e suporte a recursos (memória, mobile, WebAssembly, perfis de compressão) mudam entre versões da Unity, e também os planos e a manutenção de LRS e bibliotecas. Este guia dá o mapa e as razões; confirme cada detalhe na documentação da versão que você usa e meça na sua build.

Exercício final — O relatório de release

Escreva uma página para o cliente (real ou fictício) com: o orçamento de tamanho e o resultado medido, a estratégia de compressão e hospedagem, o método de empacotamento escolhido e por quê, os eventos registrados e a decisão pedagógica que eles apoiam, e os riscos conhecidos. Esse texto é o seu melhor item de portfólio.