Abra o Laboratório da Água-viva e você encontrará nosso mascote nadando pelo escritório, com tentáculos de contas atrás dele e uma mancha de luz colorida no chão. Parece um sprite, mas é desenhado do zero enquanto a página está rodando.
A forma do sino, o rosto, os tentáculos, o grão de pixel e o brilho vêm todos de um pequeno conjunto de parâmetros. Cada um é exposto como um controle deslizante, então o artigo acompanha controles que você pode experimentar no laboratório.
- O sino é calculado linha por linha. Cinco formas de sino usam cinco curvas de largura diferentes; um rosto precisa de apenas um a três pixels por olho.
- O mundo é renderizado em um canvas com um quarto do tamanho da viewport, e depois esticado de volta sem suavização.
- O brilho vem de um mapa de luz em escala 1/16 com queda quadrática, níveis de luz escalonados e uma matriz de dithering 4×4.
Todas as imagens aqui são capturas de tela do laboratório. Usamos a mesma água-viva pêssego do início ao fim, com a predefinição de rosto desenhado à mão do autor. Nada foi montado para o artigo.
1. O personagem é uma função
Um personagem em pixel art geralmente começa em um editor de imagens. O nosso começou com uma pergunta: qual é a menor descrição de “uma água-viva” que ainda permite que alguém faça a sua parecer diferente da minha?
A resposta acabou sendo uma curva de largura. O sino é desenhado uma linha de cada vez, de cima para baixo, e a única coisa que muda entre um sino, uma cúpula, uma bolha, uma lanterna e uma gota é a função que diz qual é a largura de cada linha. Uma cúpula se abre rápido com um ombro suave; uma bolha é mais larga no meio e estreita nas duas pontas; uma lanterna se abre no primeiro sexto e se recolhe no último quinto; uma gota atinge a largura total cerca de três quartos do caminho para baixo e então estreita; um sino simples é um alargamento reto.
Duas pequenas regras ficam por baixo da curva. O controle deslizante de “arredondamento” (1–4) decide quantas linhas a coroa leva para se abrir. Cada linha também tem uma largura ímpar. Essa coluna central importa mais do que parece: os olhos e a boca podem ficar exatamente no centro em vez de meio pixel fora.
A partir daqui o resto do corpo segue. Largura, largura do topo, altura, arredondamento e achatamento são cinco controles deslizantes. Como a forma é calculada em vez de armazenada, o sino também pode se deformar: quando você o arrasta, a linha que você agarrou se inclina mais do que as linhas distantes do cursor. Essa única regra é o que faz ele parecer borracha macia em vez de um cartão rígido.
A rotação é tratada da mesma forma, mas ao contrário: em vez de rotacionar pixels para frente (o que deixaria buracos), o renderizador percorre a caixa delimitadora rotacionada, mapeia cada célula da tela de volta para o espaço do sino e pergunta “isto está dentro da linha r?”. Vizinho mais próximo, sem anti-aliasing, então uma água-viva inclinada permanece tão nítida quanto uma em pé.
O rosto
Os olhos ficam a 54% da altura do sino, espaçados a 18% da largura do sino. Essas duas proporções são todo o sistema de layout. A expressão é então apenas quais um a três pixels se acendem: um ponto, um traço, um ^, ou um ponto deslocado um pixel para cima ou para dentro. Curioso é um olho ligeiramente mais alto que o outro. Travado é ambos os olhos empurrados em direção ao nariz. Sonolento é dois traços. Tonto — acionado por três curvas bruscas em 850 ms — é dois traços largos e um balanço.
O que vende o rosto não são os pixels, mas para onde eles olham. Os olhos são a única parte do corpo que tem suas próprias regras de movimento: eles piscam um pixel contra a direção em que você puxa o sino, e enquanto você nada com o teclado eles lideram a curva antes do corpo. A boca só aparece quando a água-viva está falando ou emotando; enquanto fala, ela abre e fecha em uma senoide rápida. Nada disso precisou de um quadro de animação para ser desenhado.
Tentáculos
Não há braços orais nem sprites de tentáculos. Cada tentáculo é uma corrente de contas quadradas sob integração de Verlet, ancorada a 28% abaixo do sino, com três passagens de restrição de distância por quadro para manter as contas na distância de seu “espaço”. O que os faz parecer submersos são três ondas senoidais em três frequências diferentes — uma deriva lenta, um balanço mais rápido e um enrolamento ponderado em direção à cauda — para que as raízes fiquem calmas e as pontas ondulem. Mova o cursor através deles e eles se abrem e rodopiam; pare e eles se acomodam no próprio ritmo.
Os controles deslizantes dos tentáculos (quantidade 3–12, segmentos 4–18, gravidade, atrito, tensão, corrente, enrolamento, esquiva) são expostos diretamente, e quatro “predefinições de movimento” de um clique — Parado, Vivo, Selvagem, Deriva — não são nada mais que quatro combinações salvas desses seis números.
Cor
Sete paletas de três cores vêm por padrão — sino, tentáculos, olhos — mais seletores de cor livres para cada um:
| Paleta | Sino | Tentáculos | Olhos |
|---|---|---|---|
| oceano | #8ae2ff | #8ae2ff | #303030 |
| pôr do sol | #ffb38a | #ff8fc7 | #3a2a2a |
| lima | #b6f5a0 | #8ce6c0 | #26331f |
| uva | #c7a2ff | #a98cff | #2c2640 |
| rosa | #ff9ec4 | #ffc0dd | #3a2630 |
| dourado | #ffe08a | #ffd06a | #3a3320 |
| mono | #cfd8e3 | #aab4c2 | #20242b |
A cor da campânula faz mais do que colorir a campânula. O motor a escreve na variável de destaque da página (e a cor dos tentáculos no destaque secundário), então toda a interface do laboratório se re-tematiza para qualquer água-viva que você esteja criando. Preto, branco, cinzas e o vermelho semântico nunca mudam; todo o resto segue você. E, como veremos na parte 3, ela também se torna a cor da luz que você carrega.
Se a campânula procedural não for suficiente, você pode desativá-la e pintar a sua própria em uma grade de 6×6 a 28×28, e pintar até oito rostos personalizados por cima. Treze dos nossos colegas de equipe fizeram exatamente isso; suas águas-vivas agora vivem na cena do escritório como NPCs, cada uma com seu próprio corpo, número de tentáculos, expressão e falas de diálogo.
2. O visual pixelado é uma resolução
A forma mais comum de obter um “visual pixelado” na web é renderizar em resolução total e pós-processar — ajustar cores, adicionar uma sobreposição de grade, talvez desfocar e requantizar. Fizemos o oposto: o mundo é de fato minúsculo. Ele é desenhado em um canvas com um quarto do tamanho da janela — uma célula do mundo equivale a quatro pixels de tela na escala padrão — e o navegador é instruído a esticá-lo sem suavização.
Cada chamada de desenho no motor escreve valores de cor empacotados de 32 bits diretamente nesse pequeno buffer — a campânula, as contas dos tentáculos, os ladrilhos do terreno, até o texto. Não há nenhum caminho vetorial em lugar algum na camada do mundo, então nada pode ser suavizado por acidente. As posições são arredondadas para a grade de células em cada âncora: o topo da campânula, a linha dos olhos, cada ladrilho do terreno, o deslocamento da câmera usado pela grade de iluminação.
Isso tem uma consequência agradável para o controle deslizante de “tamanho do pixel”. Como ele altera quantos pixels de tela uma célula do mundo cobre (4 × 0,7 a 4 × 1,6), ele muda tanto o grão da imagem quanto o tamanho aparente da água-viva, em um único movimento. Não há zoom separado.
Dois canvases e uma camada de texto real
Uma decisão estrutural da qual gostamos especialmente: o mundo são dois canvases com uma camada HTML entre eles. Os pisos das salas e os títulos são pintados no canvas inferior. O texto no estilo de blog que vive dentro do escritório é HTML comum, selecionável e acessível, por cima disso. O canvas superior — ondulações da água, itens, portais, a própria água-viva — deixa cada célula intocada transparente, então o texto é lido através da água vazia, mas a água-viva nada por cima dele.
O resultado é um texto que parece afundado na cena em vez de colado sobre ela. O canvas de fundo também é armazenado em cache: ele só se repinta quando a câmera se move por uma célula inteira ou quando a cena muda.
Dither, não gradientes
Onde a imagem precisa de um gradiente — principalmente na iluminação — usamos dithering ordenado em vez de interpolação suave. Uma matriz de Bayer 4×4, indexada por coordenadas do mundo para que o padrão permaneça fixo enquanto você rola, quebra a luz em faixas planas e então dispersa as emendas. “Passos de luz” é exposto como um controle deslizante (2–8, padrão 3). Em 2, o piso parece uma xilogravura de dois tons; em 8, fica próximo do suave. Três é onde ainda parece feito à mão.
A correção de cor — um matiz global e um ajuste de saturação — passa por uma tabela de consulta 32×32×32, que discretamente quantiza a paleta para cinco bits por canal como efeito colateral. Todo canvas de pré-visualização na interface tem a suavização desativada; o único lugar em que o motor usa um desfoque CSS é nas transições da interface, nunca no mundo.
3. O brilho é um campo de luz
Uma água-viva que não brilha não é uma água-viva. A implementação óbvia — um halo radial suave misturado sobre a cena, ou uma passada de desfoque — foi descartada no primeiro dia porque produz gradientes suaves de subpixel que parecem errados em uma grade de pixels rígida. Um halo suave flutua sobre o mundo; ele não pertence a ele.
Então, em vez de desenhar um brilho, nós iluminamos a sala. Cada luz — a água-viva, lâmpadas, itens brilhantes — é estampada em um pequeno mapa de luz com um quarto da resolução do canvas (um dezesseis avos da tela), com uma queda (1 − d/r)²: não fisicamente correta, mas suave, barata e fácil de ajustar. O piso e os móveis são então clareados pela quantidade de luz que os alcança, nos mesmos três passos planos com o mesmo dither. Dois mapas são mantidos — um para o piso e outro para objetos com um raio 1,6× mais amplo, para que os adereços fiquem dentro de um halo ligeiramente maior do que o chão sob eles.
A passada de sombreamento multiplica cada pixel pela luz ambiente mais o campo de luz, limitado a um teto. Esse teto — 1,25, significando que a luz pode empurrar uma cor até 125% de si mesma e não mais — é todo o orçamento de “bloom”. É daí que vem o núcleo ligeiramente superexposto do brilho, e é a diferença entre uma lanterna e um reflexo de lente.
Três detalhes de implementação são fáceis de perder:
- A cor da campânula também é a cor da luz. Escolha uma água-viva rosa e o piso próximo fica rosa; o mesmo valor hexadecimal atualiza a cor de destaque do laboratório.
- A cor é normalizada pelo seu canal de pico, então uma campânula azul-marinho escura ainda projeta um tom azul-marinho em força total em vez de mal iluminar a sala.
- O tremeluzir atualiza a cada 8 quadros. Uma leve instabilidade combina mais com este mundo de pixels do que um cintilar perfeitamente suave.
Mais uma regra impede que as lâmpadas pareçam erradas: qualquer coisa que emita luz é excluída de ser iluminada. Itens com um componente de luz são desenhados com uma máscara emissiva, e a passada de sombreamento copia esses pixels sem alteração — então uma lâmpada nunca é escurecida pela sua própria luz ambiente.
Os controles
Tudo isso é exposto no painel Cena → Luz. Estes são os números com que acabamos lançando:
| Controle | Intervalo | Padrão |
|---|---|---|
| Brilho ambiente | 0.2 – 1 | 0.55 |
| Cor ambiente | — | #6d8fae |
| Passos de luz | 2 – 8 | 3 |
| Teto de destaque | 1 – 1.5 | 1.25 |
| Brilho da água-viva · raio | 8 – 80 | 32 |
| Brilho da água-viva · intensidade | 0 – 1.5 | 0.6 |
| Brilho da água-viva · tremeluzir | 0 – 0.6 | 0.05 |
O piso só é re-sombreado quando o nível, os parâmetros do shader ou as posições das luzes mudam; luzes fora da viewport são descartadas antes de serem estampadas; a tabela de correção de cor é armazenada em cache. Na prática, o mundo inteiro — fundo, simulação de água, itens, tentáculos, iluminação, água-viva — cabe confortavelmente em um único quadro de animação em um laptop, em uma resolução em que uma janela de 1440px de largura tem apenas 360 células de largura.
4. Algumas coisas sobre como ele se move
Não estritamente sobre aparência, mas elas moldam como o personagem é percebido, então brevemente:
- O impulso do teclado se acumula e se amortece, limitado a pouco menos de uma célula por quadro. A velocidade também alimenta a deformação da campânula, então um nado rápido visivelmente a comprime contra a “corrente”.
- Um entalhe da roda alimenta um impulso na mesma velocidade que o teclado e equivale a uma célula de deslocamento em regime permanente.
- A câmera segue lentamente enquanto você nada, mais rápido enquanto se acomoda, e muito lentamente por 1,6 segundos após um arrasto para não voltar de repente.
- A velocidade horizontal define uma inclinação alvo. Uma mola rígida e amortecida a segue e deixa a campânula ultrapassar ligeiramente quando para.
- O cursor empurra as contas dos tentáculos para longe dentro de 40 px. Dentro de 58 px, ele também adiciona um redemoinho e um pulso radial, com peso maior na cauda.
- A cada duas células de deslocamento, um pulso suave é adicionado à simulação de onda 2D. Essas ondas podem acionar itens, que podem propagar o pulso.
5. Por que criar o personagem a partir de dados
Não começamos com uma teoria sobre personagens de IA. Começamos com um editor de água-viva e então percebemos uma vantagem prática: os mesmos parâmetros que tornam o personagem personalizável também o tornam legível para um agente.
Parâmetros são mais fáceis para um agente usar
Uma sprite sheet é opaca para um modelo. Nossa água-viva tem cerca de trinta números e alguns enums, pequeno o suficiente para um prompt, um blob JSON ou uma chamada de ferramenta.
- Um agente pode ler os parâmetros e saber como é a água-viva do seu usuário.
- Ele pode transformar “faça uma roxa sonolenta com tentáculos longos” em um preset sem gerar uma nova imagem. O NPC de cada colega de equipe é uma linha de dados em vez de um recurso de arte.
- Expressão, brilho e movimento são estado de execução, então o mascote pode parecer concentrado enquanto o agente trabalha ou tonto quando fica confuso.
- Os parâmetros podem variar amplamente enquanto o resultado ainda parece o mesmo tipo de água-viva.
Essa é a parte útil para o AirJelly. O mascote pode atuar como o corpo do agente porque sua aparência e comportamento são dados que o agente pode ler e alterar. Nunca precisamos pedir a um modelo para editar uma imagem opaca.
Por que ainda gostamos de pixels (e ASCII)
Há uma razão prática. Um sprite em pixel é uma pequena grade de índices de paleta; arte ASCII já é texto. Ambos podem ser descritos de forma compacta e editados célula por célula. Para as mudanças precisas de que precisamos, um sprite 16×16 simplesmente deixa menos espaço para um modelo improvisar do que uma pintura.
Há também uma razão mais subjetiva: gostamos de grades rígidas. Elas são honestas sobre serem feitas por uma máquina, e um erro de um pixel muitas vezes parece charmoso em vez de estranho. Esse tom combina com um produto sobre uma IA vivendo no seu computador. Pode ser por isso que os terminais parecem confortáveis em ferramentas de IA também.
Então foi aqui que chegamos: um personagem descrito por números e desenhado em uma grade. Por acaso parece retrô, mas a parte útil é como é fácil tanto para uma pessoa quanto para um agente alterar com precisão.
Uma última coisa. Se você ajustar uma água-viva de que gosta no laboratório, exporte seus parâmetros e envie para xiaohua@airjelly.ai — vamos colocá-la no site. Treze já vivem no escritório; sempre há espaço para mais uma.


























