pxl: Pixel-Art als Code
Du schreibst eine kleine .px.js-Datei; pxl rendert PNGs und hochskalierte Vorschauen, die du ansehen kannst. Die DSL ist so gebaut, dass Pixel nicht doppelt geschrieben werden müssen: Spiegelung, automatische Kontur, Palettenaustausch-Varianten, wiederverwendbare Teile, Primitive, Pinsel und Kachelmaps. Benötigt Node ≥ 22.18. Kein Installationsschritt.
PXL bedeutet node <Verzeichnis dieses Skills>/bin/pxl.js.
Arbeitsablauf
- Schreibe
art.px.js(beginne mit der Vorlage unten; Beispiele inexamples/). - Baue nur das, woran du arbeitest:
PXL build art.px.js --only slime,heart - Sieh es dir an: lies
out/_preview/<name>.png(hochskaliert, Schachbrett = transparent, Raster alle Pixel, alle 8 Pixel dunkler). Für exakte Pixel-Fixes verwendePXL ascii art.px.js <name>(Tasten + x/y-Lineale); das ist günstiger als ein Bild. - Korrigiere mit kleinen Änderungen (ein
grid(x, y, ...)-Patch, eine Operation) und baue neu. Wiederhole. - Wenn der Benutzer zusehen möchte: starte
PXL serve art.px.jsim Hintergrund und gib ihm die URL. Es baut bei jedem Speichern neu und spielt die Malschritte des bearbeiteten Assets ab; es schreibt auch dieselbenout/-Dateien, also lies die Vorschauen wie gewohnt.
Fehler nennen die Datei:Zeile:Spalte, den Asset/Layer/Op-Pfad und schlagen Korrekturen vor ("meintest du 'r'?"). Ein kaputtes Asset stoppt nicht die Erstellung der anderen.
Vorlage
pal({ k: "#1b1630", g: "#8ccf63", G: "#4c8a44", t: "#2a5560", w: "#fff6e0", r: "#e2585e", R: "#a63a4c" })
ramps("ktGg", "kRr") // dunkel -> hell, treibt shade()/light()
// Symmetrisch: schreibe die linke Hälfte, spiegele sie, umrande sie.
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 Zeile
sprite("_eye", "wk\nkk") // "_" = Teil, nicht exportiert
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"))) // Schattenwurf
sheet("chars", ["slime*", "chest"], { pad: 1 })
Wesentliches
- Farben: Ein-Zeichen-Schlüssel aus
pal,"#hex","."überspringen,"_"löschen. Standard-Palette ist pico8 (0-f);use("sweetie16" | "db16" | "endesga32" | "gray8")für andere. - Elemente werden in Reihenfolge ausgeführt: Raster-Strings (gezeichnet bei 0,0), Operationen,
(c) => { c.rect(...) }-Funktionen,layer(...)s. Dann Spiegelung → Tausch → Flip → Rotation → Kontur (wächst 1px pro Seite) → Zuschneiden. - Formen:
px rect box line(…, width) hline vline circ ring ellipse poly fill wire text. - Ton:
dither grad noise,shade/light(bereich, schritte, dichte),recolorundshade:/light:-Layer für Schlagschatten und Glühen. Bereiche:[cx,cy,r],[x,y,w,h], Polygon oder eine Funktion.clip(bereich, ...items)schränkt beliebige Elemente ein (halbe Formen, Schrägen). - Wiederverwendung:
stamp tile pattern tilemap scatter. Namen können Gruppen oder Globs sein (group("rocks", ["rock_a", "rock_a", "rock_b"]),"tree_*"). Jede Platzierung wählt zufällig, aber reproduzierbar. Verwenderflip,rswap,seedundreseed: truefür Abwechslung pro Kopie. - Pinsel:
scatter("_leaf*", x, y, w, h, 30, { on: "g", clip: true, rflip: "xy" })malt Textur mit winzigen Teil-Sprites, nur auf den angegebenen Farben. - Animation:
anim("cat", { outline: "k", fps: 10 }, grid, frame(), frame(grid(23, 0, "_K"))): Frames sind Patches über gemeinsamen Elementen oder einembase; oderframes: 6+(c) => ... c.phase ...für prozedurale Schleifen. Das Stempeln eines Animationsobjekts animiert die Szene (rdelay: trueentsynchronisiert Kopien,frame: nfriert eine ein). Prüfe_preview/<name>.png(alle Frames nebeneinander) oderpxl asciipro Frame. - Glühen:
glow("zgQ", { r: 6, strength: 0.5 })direkt im Sprite/Szenen-Körper gibt jedem Pixel dieser Schlüssel einen Halo (Flammen, Tränke, Monde, Neon);ramp: 2bleibt stattdessen in der Palette. Gib glühenden Dingen eigene Schlüssel, damit nichts anderes aufleuchtet, oder begrenze es mitwhere.
Vollständige API mit allen Optionen: reference.md. Dynamische Beleuchtung (Lampen, von soliden Layern blockiertes Licht, Himmelslicht, Terraria-Stil): lies lighting.md nur, wenn eine Szene Licht benötigt.
Gewohnheiten, die Tokens sparen und bessere Kunst liefern
- Schreibe die Hälfte von symmetrischen Dingen (
mx) und lasseoutlineKonturen zeichnen. Zeichne niemals von Hand beides. - Neu einfärben sind
base+swap; wiederholte Details sind_parts+stamp; Familien sind einegroupoder eine Schleife:for (let i = 1; i <= 3; i++) sprite("tree_" + i, ...). - Große Flächen: Primitive,
grad,noise,pattern,tilemap. Niemals große Handraster. - Organische Textur (Blätter, Gras, Steine, Rost): 2–3 winzige Pinselteile +
scattermiton/clip. - Kein Füllrauschen. Streue kein
noise-Gesprenkel über Wände, Böden oder Putz, um sie "weniger flach" zu machen: es wirkt wie zufällige Punkte, nicht wie Material. Jede Markierung auf einer Oberfläche braucht eine Ursache, die du benennen könntest, und sie sollte dort sitzen, wo diese Ursache sie hinsetzen würde:- gebaute Struktur: Verkleidungs-/Brettlinien, Ziegel- oder Steinreihen mit versetzten Fugen, Fliesennähte;
- Verwitterung: Regenflecken-Streifen unter Überhängen und Fensterbänken, aufspritzende Verschmutzung, die vom Boden her verblasst, Rostläufe unter Metallhalterungen, abblätternde Farbe auf Brettern (den Brettlinien folgend);
- Unordnung, die die Geschichte erzählt: Poster, Rohre, Lüftungen, Risse von einer Ecke.
Flächige Farbe ist in Ordnung; eine saubere Wand ist besser als eine gepunktete.
noiseist nur dort OK, wo das Material selbst körnig ist (Kies, Sand, Asphalt, Teerdächer, rauer Stein), und dann sparsam. Bei schräg gezeichneten Gesichtern (3/4-Ansichten) sollten die Markierungen der Neigung des Gesichts folgen.
- Beleuchtung: definiere
ramps, dannlight/shade-Bereiche; staple einendensity: 0.5-Durchgang für weiche Kanten. Verwendeshade: true-Layer mit einempolyfür Schlagschatten über viele Objekte hinweg. - Verwende mnemonische Schlüssel (
g/Ghell-/dunkelgrün). Halte 2–4 Schattierungen pro Material für einen sauberen Pixel-Art-Look. - Baue mit
--onlywährend der Iteration; ein vollständiger Build schreibt auch jede Vorschau.