pxl: arte em pixel como código
Você escreve um pequeno arquivo .px.js; pxl renderiza PNGs e prévias ampliadas que você pode visualizar. O DSL
é construído para evitar escrever pixels duas vezes: espelhamento, contorno automático, variantes de troca de paleta, partes
reutilizáveis, primitivas, pincéis e tilemaps. Precisa de Node ≥ 22.18. Sem etapa de instalação.
PXL abaixo significa node <diretório desta skill>/bin/pxl.js.
Fluxo de trabalho
- Escreva
art.px.js(comece pelo modelo abaixo; exemplos emexamples/). - Construa apenas o que você está trabalhando:
PXL build art.px.js --only slime,heart - Veja o resultado: leia
out/_preview/<nome>.png(ampliado, xadrez = transparente, grade a cada pixel, mais escura a cada 8). Para correções exatas de pixel usePXL ascii art.px.js <nome>(teclas + réguas x/y); é mais barato que uma imagem. - Corrija com pequenas edições (um patch
grid(x, y, ...), uma operação) e reconstrua. Repita. - Se o usuário quiser assistir: inicie
PXL serve art.px.jsem segundo plano e forneça a URL. Ele reconstrói a cada salvamento e reproduz as etapas de pintura do ativo editado; também escreve os mesmos arquivosout/, então continue lendo as prévias como de costume.
Erros informam o arquivo:linha:col, o caminho do ativo/camada/operação e sugerem correções ("você quis dizer 'r'?"). Um ativo quebrado não impede a construção dos outros.
Modelo
pal({ k: "#1b1630", g: "#8ccf63", G: "#4c8a44", t: "#2a5560", w: "#fff6e0", r: "#e2585e", R: "#a63a4c" })
ramps("ktGg", "kRr") // escuro -> claro, orienta shade()/light()
// Simétrico: escreva a metade esquerda, espelhe, contorne.
sprite("slime", { mx: true, outline: "k" }, `
....ggg
..ggGGg
.ggGwGg
gggkwgg
gggkkgg
tgggggg
.tttttt`)
sprite("slime_red", { base: "slime", swap: { g: "r", G: "R" } }) // variante: 1 linha
sprite("_eye", "wk\nkk") // "_" = parte, não exportada
sprite("chest", { w: 16, h: 12, outline: "k" },
rect(0, 3, 16, 9, "R"), rect(0, 0, 16, 4, "r"), stamp("_eye", 7, 4), shade([0, 9, 16, 3]))
scene("meadow", { w: 160, h: 96, bg: "w" },
layer("ground", { y: 64 }, rect(0, 0, 160, 32, "G"), noise(0, 0, 160, 32, "g", 0.1, 3)),
layer("props", stamp("slime", 40, 54), stamp("slime_red", 70, 54, { flip: "x" })),
layer("shadow", { shade: true }, poly([38, 64, 88, 64, 92, 67, 34, 67], "k"))) // sombra projetada
sheet("chars", ["slime*", "chest"], { pad: 1 })
Essenciais
- Cores: chaves de um caractere de
pal,"#hex","."pular,"_"apagar. A paleta padrão é pico8 (0-f); useuse("sweetie16" | "db16" | "endesga32" | "gray8")para outras. - Itens são executados em ordem: strings de grade (desenhadas em 0,0), ops, funções
(c) => { c.rect(...) },layer(...). Depois: espelhamento → troca → virar → girar → contorno (cresce 1px de cada lado) → corte. - Formas:
px rect box line(…, width) hline vline circ ring ellipse poly fill wire text. - Tom:
dither grad noise,shade/light(região, passos, densidade),recolor, e camadasshade:/light:para sombras projetadas e brilhos. Regiões:[cx,cy,r],[x,y,w,h], polígono ou uma função.clip(region, ...items)restringe qualquer item (meias formas, inclinações). - Reutilização:
stamp tile pattern tilemap scatter. Nomes podem ser grupos ou globos (group("rocks", ["rock_a", "rock_a", "rock_b"]),"tree_*"). Cada colocação escolhe aleatoriamente, mas de forma reproduzível. Userflip,rswap,seedereseed: truepara variedade por cópia. - Pincéis:
scatter("_leaf*", x, y, w, h, 30, { on: "g", clip: true, rflip: "xy" })pinta textura com pequenas partes de sprite, apenas nas cores fornecidas. - Animação:
anim("cat", { outline: "k", fps: 10 }, grid, frame(), frame(grid(23, 0, "_K"))): quadros são patches sobre itens compartilhados ou umabase; ouframes: 6+(c) => ... c.phase ...para loops processuais. Selar uma animação anima a cena (rdelay: truedessincroniza cópias,frame: ncongela uma). Verifique_preview/<nome>.png(todos os quadros lado a lado) oupxl asciipor quadro. - Brilho:
glow("zgQ", { r: 6, strength: 0.5 })diretamente em um corpo de sprite/cena dá a cada pixel dessas chaves um halo (chamas, poções, luas, neon);ramp: 2em vez disso permanece na paleta. Dê às coisas brilhantes suas próprias chaves para que nada mais acenda, ou limite comwhere.
API completa com todas as opções: reference.md. Iluminação dinâmica (lâmpadas, luz bloqueada por camadas sólidas, luz do céu, estilo Terraria): leia lighting.md apenas quando uma cena precisar de luzes.
Hábitos que economizam tokens e produzem arte melhor
- Escreva a metade de qualquer coisa simétrica (
mx) e deixeoutlinedesenhar contornos. Nunca desenhe à mão nenhum dos dois. - Recolorir é
base+swap; detalhes repetidos são_parts+stamp; famílias são umgroupou um loop:for (let i = 1; i <= 3; i++) sprite("tree_" + i, ...). - Grandes áreas: primitivas,
grad,noise,pattern,tilemap. Nunca grandes grades manuais. - Textura orgânica (folhas, grama, pedras, ferrugem): 2–3 pequenas partes de pincel +
scattercomon/clip. - Sem ruído de preenchimento. Não espalhe pontilhado
noisesobre paredes, pisos ou gesso para torná-los "menos planos": isso é lido como pontos aleatórios, não como material. Cada marca em uma superfície precisa de uma causa que você pudesse nomear, e deve estar onde essa causa a colocaria:- estrutura construída: linhas de revestimento/tábua, cursos de tijolo ou pedra com juntas deslocadas, costuras de azulejo;
- intempéries: estrias de mancha de chuva sob beirais e peitoris, sujeira de respingo subindo do chão, ferrugem descendo sob suportes de metal, tinta descascando de tábuas (seguindo as linhas das tábuas);
- desordem que conta a história: cartazes, canos, respiros, rachaduras saindo de um canto.
Cor plana é aceitável; uma parede limpa é melhor que uma pontilhada.
noiseé aceitável apenas onde o material em si é granular (cascalho, areia, asfalto, telhados de alcatrão, pedra áspera), e mantenha-o esparso. Em rostos desenhados em ângulo (vistas 3/4), faça as marcas seguirem a inclinação do rosto.
- Iluminação: defina
ramps, depois regiõeslight/shade; empilhe uma passagemdensity: 0.5para bordas suaves. Use camadasshade: truecom umpolypara sombras projetadas sobre muitos objetos. - Use chaves mnemônicas (
g/Gverde claro/escuro). Mantenha 2–4 tons por material para uma aparência limpa de pixel art. - Construa com
--onlydurante iteração; uma construção completa também escreve cada prévia.