Experimente ao vivo Sobre este projeto
Playroom é um arcade de sala de estar que roda no navegador. A página abre em uma mesa de jogos no estilo de 1989 — um monitor CRT, um console, um controle e um cartucho de Snake já encaixado — que dá para girar e examinar; clique no console ou em POWER ON e a câmera avança para a tela, atravessa o tubo e chega a uma biblioteca de três cartuchos.
Os três jogos são originais do Playroom: Snake é sobre encadear mordidas no ritmo, Breakout traz três fases de tijolos e power-ups, e Blocks torna o empilhamento previsível com um fantasma de pouso e sacos de sete peças. Sem conta, sem atualizações, sem saves — os recordes ficam no seu navegador, e a música e os efeitos se desligam com um único botão.
O projeto inteiro depende apenas de Three.js e Vite, sem backend e sem variáveis de ambiente; seis modelos de hardware GLB e um único decal com a marca facial da Tripo são toda a lista de ativos. Quando o WebGL não está disponível, um menu de texto assume e os jogos continuam totalmente jogáveis.
Destaques
- Arraste para orbitar toda a sala (monitor CRT, console, controle e cartucho) e depois clique no console ou em POWER ON: a câmera avança em direção à tela e atravessa o CRT até a biblioteca de cartuchos.
- Três cartuchos, três jogos originais: Snake, com corrente 5× em cinco segundos e fruta dourada de 50 pontos; Breakout, com três fases de tijolos e os power-ups W (raquete mais larga) e 3 (bola tripla); Blocks, com sacos de sete peças, fantasma de pouso e bônus de combo.
- Setas / WASD e Espaço no desktop, P para pausar e ESC para sair, controles por toque no celular; os recordes ficam apenas no seu próprio navegador.
- Cada jogo tem seu próprio loop chiptune e efeitos sonoros, e nos menus toca a faixa “Trip Of Your Smile” (CC BY 4.0); se o WebGL não estiver disponível, o arcade recorre a botões de texto e continua jogável.
Análise técnica
Ativos 3D · Seis peças de hardware GLB revinculadas em tempo de execução
O monitor CRT, o console, o controle e os três cartuchos são entregues como seis arquivos GLB (exportados com o GLTFExporter do Three.js, em low-poly com 128–384 triângulos cada). O GLTFLoader os carrega em paralelo e cada instância é um clone profundo: os deslocamentos de centralização da exportação são zerados, os materiais são clonados por instância e todas as malhas projetam e recebem sombras. A placa de etiqueta do cartucho é localizada pelo seu deslocamento local exportado, nomeada cartridge-label e remapeada para uma textura de capa pintada em Canvas; o mascote em pixels é rigado a partir de nós nomeados — mascot-body, os dois braços, a antena e um decal de logo com a marca facial da Tripo, com tone mapping e escrita de profundidade desativados para que o decal plano permaneça nítido.
A camada de ativos também mantém um slot de troca: aponte o campo controller de public/models/manifest.json para qualquer GLB e o fitProp o escala pela caixa delimitadora para 2,54 unidades de largura, apoia no chão, centraliza e assume a posição do controle original — um controle personalizado (gerado com a Tripo, por exemplo) entra sem mexer no código.
Tela e etiquetas · Texturas Canvas como interface
A cena não carrega nenhum mapa de textura externo. A face do CRT é uma CanvasTexture de 1024×700: a tela de espera (PLAYROOM SYSTEM / PLAYER ONE / PRESS START) é desenhada quadro a quadro em um contexto 2D e usada ao mesmo tempo como map e emissiveMap para que o tubo brilhe. Cada capa de cartucho é um canvas próprio de 640×610, pintado na hora a partir da cor, da tinta, do número e do slogan daquele jogo no catálogo — pixel art e letreiro do título incluídos.
Ao ligar, um controlador do ciclo de vida da tela troca o material por uma textura de fluxo de dados enquanto a câmera volta para a frente do tubo por uma trajetória esférica ao redor do centro da tela, avança e corta para a biblioteca ao atravessar o vidro; com prefers-reduced-motion, todo o movimento é pulado em favor de uma troca direta.
Interação · Gestos de ponteiro, seleção por raycast e órbita cinematográfica
Na vista da mesa, uma mesma sequência do ponteiro é ao mesmo tempo arrasto de órbita e clique: o controlador de gestos registra theta / phi ao pressionar, trata o movimento acima de um limiar como arrasto e qualquer outra coisa como clique. Os acertos são resolvidos com um Raycaster — na mesa, todo o conjunto funciona como botão de ligar; na biblioteca, o acerto sobe pela cadeia de pais até o nó que carrega um gameId. Ao soltar, a órbita nunca congela; um controlador cinematográfico funde o deslocamento manual de volta a um balanço automático lento.
Passar o cursor ou focar um cartucho o eleva em 0,24, inclina-o para a frente com um leve rolamento e troca o mascote em pixels para a pose daquele jogo, enquanto cada cartucho oscila na sua própria fase senoidal. Toda interpolação no loop de renderização usa o easing independente da taxa de quadros 1 − e^(−5·dt), então quadros lentos nunca dão saltos.
Jogos e áudio · Sessões 2D independentes e som sintetizado
Os três jogos são módulos de lógica pura (create / step / input); uma camada de sessão os desenha em um canvas 2D independente — 480×480 para Snake e Breakout, 300×540 para Blocks. O requestAnimationFrame só é agendado enquanto o jogo roda, então a pausa literalmente para os quadros, e um teto de 250 ms por passo evita o tunneling depois de trocar de aba. Os eventos do jogo (comer, tijolo, linha, power-up, fase) alimentam uma camada de efeitos de partículas e se condensam por prioridade em um único sinal sonoro.
Todo o áudio é sintetizado ao vivo com osciladores do Web Audio: uma melodia chiptune de 16 passos por jogo (82–120 BPM) e cerca de vinte sinais construídos com deslizamentos de frequência e envelopes, limitados a 64 vozes. A faixa do menu é uma gravação CC BY 4.0 que recorre ao loop sintetizado se o download falhar; o áudio é silenciado quando a aba está oculta e o contexto só é desbloqueado após o primeiro clique. Os recordes vão para o localStorage e caem para a memória quando o armazenamento é negado — uma gravação que falha nunca interrompe uma partida.


