pxl:像素艺术即代码
你编写一个小型的 .px.js 文件;pxl 会渲染 PNG 和放大预览图供你查看。DSL
的设计旨在避免重复绘制像素:镜像、自动轮廓、调色板交换变体、可复用部件、
图元、笔刷和瓦片地图。需要 Node ≥ 22.18。无需安装步骤。
下面的 PXL 表示 node <此技能的目录>/bin/pxl.js。
工作流程
- 编写
art.px.js(从下面的模板开始;示例在examples/目录中)。 - 仅构建你正在处理的部分:
PXL build art.px.js --only slime,heart - 查看它:读取
out/_preview/<name>.png(放大图,棋盘格 = 透明,每个像素有网格, 每 8 个像素网格加深)。如需精确像素修正,使用PXL ascii art.px.js <name>(显示键 + x/y 标尺); 这比图像处理更高效。 - 通过小幅编辑(一个
grid(x, y, ...)补丁,一次操作)进行修复并重新构建。重复此步骤。 - 如果用户想实时观看:在后台启动
PXL serve art.px.js并给用户 URL。 每次保存时它会重新构建并回放已编辑资源的绘画步骤;同时会写入相同的out/文件,因此可以照常读取预览。
错误信息会指出文件名:行:列、资源/图层/操作路径,并建议修复方法(例如“你是不是想用 'r'?”)。 一个资源出错不会阻止其他资源构建。
模板
pal({ k: "#1b1630", g: "#8ccf63", G: "#4c8a44", t: "#2a5560", w: "#fff6e0", r: "#e2585e", R: "#a63a4c" })
ramps("ktGg", "kRr") // 从暗到亮,驱动 shade()/light()
// 对称:只写左半部分,自动镜像并添加轮廓。
sprite("slime", { mx: true, outline: "k" }, `
....ggg
..ggGGg
.ggGwGg
gggkwgg
gggkkgg
tgggggg
.tttttt`)
sprite("slime_red", { base: "slime", swap: { g: "r", G: "R" } }) // 变体:一行代码
sprite("_eye", "wk\nkk") // "_" 表示部件,不会被导出
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"))) // 投射阴影
sheet("chars", ["slime*", "chest"], { pad: 1 })
核心要点
- 颜色:使用来自
pal的单字符键、"#hex"、"."跳过、"_"擦除。默认调色板是 pico8 (0-f);使用use("sweetie16" | "db16" | "endesga32" | "gray8")使用其他调色板。 - 项目按顺序执行:网格字符串(在 0,0 处绘制)、操作、
(c) => { c.rect(...) }函数、layer(...)。然后进行镜像 → 交换 → 翻转 → 旋转 → 轮廓(每边增长 1 像素)→ 裁剪。 - 形状:
px rect box line(…, width) hline vline circ ring ellipse poly fill wire text。 - 色调:
dither grad noise、shade/light(region, steps, density)、recolor,以及shade:/light:图层用于投射阴影和发光效果。区域:[cx,cy,r]、[x,y,w,h]、 多边形或函数。clip(region, ...items)限制任何项目(半形状、斜面)。 - 复用:
stamp tile pattern tilemap scatter。名称可以是组或通配符 (group("rocks", ["rock_a", "rock_a", "rock_b"])、"tree_*")。每次放置随机选择但 可重现。使用rflip、rswap、seed和reseed: true实现每个副本的多样性。 - 笔刷:
scatter("_leaf*", x, y, w, h, 30, { on: "g", clip: true, rflip: "xy" })使用 小部件精灵绘制纹理,仅覆盖在给定颜色上。 - 动画:
anim("cat", { outline: "k", fps: 10 }, grid, frame(), frame(grid(23, 0, "_K"))): 帧是共享项目或base的补丁;或frames: 6+(c) => ... c.phase ...用于 程序化循环。在场景中 stamp 一个动画会使其动起来(rdelay: true使副本不同步,frame: n冻结某一帧)。查看_preview/<name>.png(所有帧并排显示)或每帧使用pxl ascii。 - 发光:
glow("zgQ", { r: 6, strength: 0.5 })直接在精灵/场景主体中使用,会给这些键的 每个像素添加光晕(火焰、药水、月亮、霓虹灯);ramp: 2则保持在调色板内。 让发光物体使用独立的键,以免其他元素发光,或者使用where限制范围。
完整 API 及所有选项:reference.md。 动态光照(灯具、被实心图层阻挡的光、天空光、类似 Terraria 风格):仅在场景需要光照时 阅读 lighting.md。
节省令牌并创作更好艺术的习惯
- 任何对称的东西都只写一半(
mx),让outline绘制轮廓。永远不要手动绘制其中任何一种。 - 重新着色使用
base+swap;重复的细节使用_parts+stamp;系列使用group或 循环:for (let i = 1; i <= 3; i++) sprite("tree_" + i, ...)。 - 大面积区域:图元、
grad、noise、pattern、tilemap。永远不要用手写的大网格。 - 有机纹理(叶子、草地、石头、锈迹):2–3 个微小的笔刷部件 + 使用
on/clip的scatter。 - 不要无意义的噪点。 不要在墙壁、地板或灰泥上撒上
noise斑点让它们“不那么平坦”: 这看起来像随机点,而不是材质。表面上的每个标记都需要有一个你能命名的原因, 并且它应该位于该原因会放置它的位置:- 建造结构:壁板/木板线、带有错缝的砖或石材层、瓷砖接缝;
- 风化:屋檐和窗台下的雨渍条纹、从地面向上渐淡的溅回污垢、金属支架下的锈迹、 木板上脱落的油漆(沿着木板线);
- 讲述故事的杂物:海报、管道、通风口、从角落开始的裂缝。
纯色是好的;干净的墙壁胜过有斑点的墙壁。
noise仅当材质本身是颗粒状时才可以(砾石、沙子、 沥青、焦油屋顶、粗糙的石头),并且保持稀疏。在以一定角度(3/4 视图)绘制的脸上, 让标记沿着脸的倾斜方向。
- 光照:定义
ramps,然后light/shade区域;叠加一个density: 0.5的遍用于柔和边缘。 使用带有shade: true的图层和poly来投射跨越多物体的阴影。 - 使用助记键(
g/G表示浅/深绿色)。每种材质保持 2–4 个色阶,以获得清晰的像素艺术外观。 - 迭代时使用
--only构建;完整构建也会写入所有预览图。