Como criar um jogo para o Catálogo de Games
Guia técnico completo para quem (ou qual IA) for gerar um jogo HTML5/JS para a plataforma. Os limites e opções aqui embaixo são lidos direto da configuração do servidor — se mudarem, esta página muda junto.
1. O ambiente onde o jogo roda
O jogo é servido dentro de um <iframe sandbox="allow-scripts">
sem allow-same-origin e sem
allow-forms/allow-popups/allow-top-navigation.
Na prática:
- Origem opaca (
null): sem acesso alocalStorage,sessionStorage,indexedDBnem cookies. connect-src: 'none':fetch/XMLHttpRequest/WebSocketnão funcionam, nem pro próprio domínio da plataforma. Toda comunicação passa peloGameSDK.- Sem popup, sem navegar a página pai, sem submeter formulário.
- Sem recursos externos — nada de CDN, fontes do Google, analytics. Só o que estiver dentro do próprio zip.
- O jogo ocupa 100% de largura/altura do contêiner que a plataforma desenha ao redor — layout deve se adaptar (
width:100%; height:100%, listener deresizese usar canvas), não assumir tamanho fixo.
Resumindo: o jogo tem que ser 100% autocontido e falar com o mundo só pelo GameSDK.
2. Estrutura do arquivo .zip
index.htmlobrigatoriamente na raiz do zip (não numa subpasta).- Extensões permitidas:
.html .htm .css .js .json .png .jpg .jpeg .gif .webp .svg .mp3 .ogg .wav .woff .woff2— qualquer outra é rejeitada. - Sem symlink, sem
../(zip-slip), sem pasta aninhada além de 6 níveis. - Zip comprimido: até 50MB.
- Conteúdo descomprimido: até 200MB.
- Cota por conta de dev: 200MB somando todos os jogos publicados.
- Imagens passam por reencode automático no servidor (decodifica e regrava) — camada extra contra arquivo-polígloto.
3. GameSDK — a única forma de falar com a plataforma
Inclua antes do seu script principal:
<script src="/sdk/game-sdk.js?v=09c5ad64d2"></script>O ?v=... é opcional mas recomendado — sem ele, o navegador de quem joga ainda funciona (o servidor nunca deixa isso ficar em cache velho de qualquer forma), mas com ele o arquivo pode ser cacheado com segurança em qualquer CDN na frente do site.
Isso expõe o objeto global GameSDK. Não precisa esperar nenhum evento de "pronto" — o SDK enfileira as chamadas até a página pai confirmar o handshake. Referência completa, método a método:
GameSDK.startSession()
Chame no início de cada partida — a plataforma passa a rastrear essa sessão internamente, não existe token pra você gerenciar. Retorna Promise<void>.
await GameSDK.startSession();GameSDK.addScore(valor)
valor é um incremento (ex: pegou moeda = +10) — nunca o placar total. Sujeito a teto por evento e rate limit (ver seção 4). Devolve o total acumulado pelo servidor até agora.
var res = await GameSDK.addScore(10);
console.log(res.accumulatedScore); // total real, some com o que você já mandou antesGameSDK.finishSession(stats)
Encerra a partida atual. O placar final devolvido é sempre o que o servidor já acumulou — não dá pra passar um valor aqui. stats é opcional: um array de até 8 { label, value } só de exibição (nunca persistido, nunca usado pra calcular nada) — a plataforma monta uma tabelinha "Resultado final" com eles, ANTES do placar, na tela de fim de jogo.
var res = await GameSDK.finishSession([
{ label: 'Ouro ganho', value: totalGold },
{ label: 'Inimigos derrotados', value: kills },
{ label: 'Ondas resistidas', value: wave },
]);
alert('Placar final: ' + res.score);GameSDK.getLeaderboard()
Top 50 scores visíveis do jogo — o mesmo dado que aparece no ranking público da página do jogo.
var scores = await GameSDK.getLeaderboard(); // [{ player_name, score, created_at }, ...]GameSDK.save(dados) / GameSDK.load()
Um slot de JSON livre por (jogo, usuário), até 256KB — organize múltiplos "slots" dentro do próprio objeto se precisar. load() devolve undefined se nunca salvou nada.
await GameSDK.save({ level: 3, coins: 120 });
var data = await GameSDK.load();GameSDK.onPause(fn) / GameSDK.onResume(fn)
A plataforma chama isso quando o jogador sai da tela cheia sem querer (gesto do sistema, botão físico) — pause seu loop/áudio ali; onResume dispara quando ele volta. Registre uma vez, no início; não precisa (nem deve) ser chamado manualmente pelo seu código.
GameSDK.onPause(function () { engine.pause(); });
GameSDK.onResume(function () { engine.resume(); });GameSDK.requireOrientation(orientation)
Declare 'landscape' ou 'portrait' se seu jogo só faz sentido numa orientação. No celular, a plataforma tenta travar de verdade (quando o navegador suporta) e, quando não dá, bloqueia a visão do jogo com um aviso pra girar o aparelho — sem que você precise tratar isso na mão. Chame uma vez, no início.
GameSDK.requireOrientation('landscape');GameSDK.onMuteChange(fn)
O jogador pode ligar/desligar o som geral pela barra lateral da sandbox — a plataforma não tem áudio próprio pra mutar (quem toca som é o seu jogo), então só avisa: fn(true) = mutar, fn(false) = religar. Opcional — sem handler registrado, o toggle simplesmente não tem efeito no seu jogo.
GameSDK.onMuteChange(function (muted) {
bgMusic.muted = muted;
sfx.muted = muted;
});4. Regra de ouro do anti-cheat — nunca envie um placar pronto
Não existe GameSDK.setScore(numero) de propósito. O placar final salvo é sempre a
soma que o próprio servidor acumulou, evento a evento, via GameSDK.addScore(valor).
Não dá pra forjar o placar no DevTools chamando uma função com o número que quiser — essa função não existe.
- Chame
addScore(valor)a cada ganho de pontos real do jogo — nunca acumule localmente pra mandar tudo no final. - Cada evento tem teto de 100 pontos por padrão (o dono do jogo pode pedir outro valor ao admin).
- Rate limit: no máximo 15 chamadas de
addScorea cada 5 segundos por partida. - Sessão precisa durar pelo menos 3 segundos entre
startSessionefinishSession. - Trate rejeição com
.catch()— não deixe a Promise sem tratamento (ver exemplo abaixo).
5. Boas práticas de UI dentro do jogo
- Layout responsivo a 100% do iframe, não pixels fixos pensando em desktop.
- Use
pointerdown/pointerup(funciona pra mouse e toque), não sóclick. touch-action: noneno CSS evita que o navegador tente rolar/dar zoom durante o toque.- Não dependa só de teclado — boa parte de quem joga está no celular.
- Fontes: use fontes do sistema ou arquivos
.woff/.woff2embutidos no zip — nunca CDN externo.
6. Metadados do cadastro (fora do zip)
- Título (2–100 caracteres)
- Descrição (até 500 caracteres, opcional)
- Gênero — texto livre; sugestões: Arcade, Tower Defense, RPG, Puzzle, Corrida, Simulação, Terror, Esporte, Estratégia, Plataforma, Outro
- Ícone (obrigatório) — imagem quadrada, recomendado 512×512. Usada em qualquer lugar pequeno que precise representar o jogo (ex: fileira de ícones no Top 10 jogadores). Dá pra trocar depois no painel de gerenciar.
- 1 a 3 screenshots (obrigatório) — recomendado 1280×800, formato paisagem (16:10); é como aparecem na página do jogo.
- Mini banner / capa (opcional, configurável depois no painel de gerenciar) — usado no destaque da home e nos cards do catálogo, nunca na própria página do jogo. Duas variantes, porque o mesmo recorte que fica bom numa faixa larga de desktop corta o assunto principal numa tela de celular (a altura é sempre 350px, só a largura muda demais entre os dois):
- Desktop — recomendado 1600×400 (bem largo).
- Mobile (opcional) — recomendado 700×600 (mais próximo de quadrado). Sem uma definida, o celular usa a imagem Desktop mesmo — só vale a pena enviar se o recorte largo estiver cortando algo importante.
Ícone, capa (nas duas variantes) e screenshots passam pelo mesmo reencode automático (PNG/JPG → WebP) citado na seção 2.
7. O que acontece depois do envio
- Validação automática (fila): zip-slip, extensões, antivírus, reencode de imagem — qualquer falha é logada com o motivo específico, visível pro dev.
- Se passar, o jogo fica
pendingaguardando aprovação manual de um admin. - Só depois de aprovado ele aparece no catálogo público.
A conta que envia precisa ter e-mail confirmado e papel de dev (ou admin) — conta comum não publica jogo.
8. Exemplo mínimo completo
Zipe esse index.html sozinho (na raiz do zip) e já está pronto pra enviar pelo formulário. Cobre só o essencial (sessão + placar); os recursos opcionais (save/load, pause/resume, orientação, som) têm exemplo próprio na seção 3, junto da regra de cada um.
<!doctype html>
<html lang="pt-br">
<head>
<meta charset="utf-8">
<title>Meu Jogo</title>
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<style>
html, body { margin:0; height:100%; overflow:hidden; background:#111; touch-action:none; }
#score { position:fixed; top:14px; left:14px; color:#fff; font:700 18px system-ui; }
button { font:600 15px system-ui; padding:12px 24px; border-radius:8px; border:0; cursor:pointer; }
</style>
</head>
<body>
<div id="score">0 pontos</div>
<button id="tap" style="position:fixed;bottom:24px;left:50%;transform:translateX(-50%)">Toque aqui</button>
<script src="/sdk/game-sdk.js?v=09c5ad64d2"></script>
<script>
var scoreEl = document.getElementById('score');
var score = 0;
var playing = false;
async function start() {
try {
await GameSDK.startSession();
playing = true;
} catch (err) {
console.error('Erro ao iniciar sessão:', err.message);
}
}
document.getElementById('tap').addEventListener('pointerdown', async function () {
if (!playing) return;
score += 1;
scoreEl.textContent = score + ' pontos';
try {
var res = await GameSDK.addScore(1);
score = res.accumulatedScore; // servidor manda — sincroniza com o total real
scoreEl.textContent = score + ' pontos';
} catch (err) {
score -= 1; // evento rejeitado (ex: rate limit) — desfaz o incremento otimista
scoreEl.textContent = score + ' pontos';
}
});
start().then(function () {
setTimeout(async function () {
playing = false;
var res = await GameSDK.finishSession();
alert('Fim de jogo! Placar final: ' + res.score);
}, 30000);
});
</script>
</body>
</html>9. Erros comuns que rejeitam o jogo ou quebram em produção
index.htmldentro de uma subpasta em vez de na raiz do zip.- Usar
fetch/XMLHttpRequestesperando falar com sua própria API — sempre falha dentro do sandbox. Use oGameSDK. - Tentar
localStorage.setItem(...)pra salvar progresso — não persiste (origem opaca). UseGameSDK.save()/GameSDK.load(). - Carregar lib de CDN — bloqueado pelo CSP. Baixe e inclua o arquivo dentro do zip.
- Mandar o placar final pronto — não existe essa função de propósito (releia a seção 4).
- Zip com extensão fora da lista permitida (ex:
.ttfem vez de.woff, ou.mp4) — converta antes de enviar.