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を読みます(拡大表示、チェッカーボード=透明、1ピクセルごとにグリッド、8ピクセルごとに濃いグリッド)。ピクセル単位の正確な修正が必要な場合はPXL ascii art.px.js <name>を使います(キー+x/y ルーラー表示)。画像を確認するよりも軽量です。 - 小さな編集(
grid(x, y, ...)パッチ、1回の操作)で修正し、再ビルド。繰り返します。 - ユーザーがライブで見たい場合:バックグラウンドで
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" } }) // バリエーション:1行
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からの1文字キー、"#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 ...でプロシージャルループ。アニメをスタンプするとシーンがアニメーションします(rdelay: trueでコピーの同期をずらし、frame: nで1フレームに固定)。_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個の小さなブラシ部品 +
scatterにon/clipを指定。 - ノイズの詰め込みはしない。 壁や床、漆喰を「平坦でなくする」ために
noiseの斑点をまぶさないでください。それはランダムなドットに見え、素材感は出ません。表面のすべてのマークには名前を付けられる原因が必要であり、その原因が置かれるべき場所に配置されるべきです:- 建築構造:外壁/板のライン、目地をずらしたレンガや石積み、タイルの継ぎ目。
- 風化:ひさしや窓台の下の雨染みの筋、地面から上に薄れていく跳ね返りの汚れ、金属ブラケットの下の錆の流れ、板に沿った塗装の剥がれ。
- 物語を語る散らかり:ポスター、パイプ、通気口、隅からのひび割れ。
平坦な色で構いません。きれいな壁は斑点だらけの壁より優れています。
noiseは素材自体が粒状(砂利、砂、アスファルト、タール屋根、粗い石)の場合にのみ適しており、その場合も控えめにしてください。斜めから描かれた面(3/4ビュー)では、マークを面の傾きに沿わせてください。
- ライティング:
rampsを定義し、light/shade領域を設定。density: 0.5のパスを重ねてソフトなエッジに。多くのオブジェクトにまたがる投げ影にはshade: trueレイヤーとpolyを使用。 - ニーモニックキーを使う(
g/Gは明るい緑/暗い緑など)。素材あたり2~4階調に抑え、きれいなピクセルアートの見た目に。 - 反復中は
--onlyでビルド。フルビルドではすべてのプレビューも出力されます。