code-kumpel/scripts/export-sprites.js
2026-10-24 04:51:23 +02:00

105 lines
3.8 KiB
JavaScript
Executable file

#!/usr/bin/env node
/**
* Exportiert Pixel-Sprites pro Stufe + Mood als PNG und Sheets.
* Nutzung: node scripts/export-sprites.js
* Braucht: google-chrome + python3/pillow
*/
const fs = require('fs');
const path = require('path');
const { execSync, spawnSync } = require('child_process');
const ROOT = path.resolve(__dirname, '..');
const OUT = path.join(ROOT, 'assets', 'sprites');
const TMP = '/tmp/ck-sprites';
const STAGES = ['funke','kleiner-kumpel','junior-coder','senior-kumpel','code-meister'];
const MOODS = ['neugierig','denkend','stolz','verwirrt','müde'];
const MOOD_FILE = { neugierig:'neugierig', denkend:'denkend', stolz:'stolz', verwirrt:'verwirrt', 'müde':'muede' };
const SIZE = 128;
fs.mkdirSync(TMP, { recursive: true });
fs.mkdirSync(path.join(OUT, 'sheets'), { recursive: true });
const avatarSrc = fs.readFileSync(path.join(ROOT, 'avatar-pixel.js'), 'utf8');
const html = `<!doctype html><html><head><meta charset=utf-8>
<style>
html,body{margin:0;background:transparent;overflow:hidden}
#grid{display:flex;flex-wrap:wrap;width:${5*SIZE}px}
.cell{width:${SIZE}px;height:${SIZE}px;display:flex;align-items:center;justify-content:center}
svg{width:${SIZE}px;height:${SIZE}px;image-rendering:pixelated}
</style></head><body>
<script>${avatarSrc}</script>
<script>
const moods=${JSON.stringify(MOODS)};
const grid=document.createElement('div'); grid.id='grid'; document.body.appendChild(grid);
for(let st=0;st<5;st++){
for(const m of moods){
const cell=document.createElement('div'); cell.className='cell';
const svg=document.createElementNS('http://www.w3.org/2000/svg','svg');
svg.setAttribute('viewBox','0 0 200 200');
svg.innerHTML = buddyPixelSVG(st, m);
cell.appendChild(svg); grid.appendChild(cell);
}
}
</script></body></html>`;
const htmlPath = path.join(TMP, 'sprites.html');
fs.writeFileSync(htmlPath, html);
const shotPath = path.join(TMP, 'all.png');
console.log('Chrome screenshot…');
const r = spawnSync('google-chrome', [
'--headless=new','--disable-gpu','--hide-scrollbars','--no-sandbox',
'--default-background-color=00000000',
`--window-size=${5*SIZE},${5*SIZE}`,
`--screenshot=${shotPath}`,
'file://'+htmlPath
], { encoding:'utf8' });
if (r.status !== 0){ console.error(r.stderr||r.stdout); process.exit(1); }
if (!fs.existsSync(shotPath)){ console.error('Screenshot missing'); process.exit(1); }
const py = `
from PIL import Image
import os
img = Image.open(${JSON.stringify(shotPath)}).convert('RGBA')
W,H = img.size
cell = ${SIZE}
cols, rows = 5, 5
ow, oh = cols*cell, rows*cell
if W >= ow and H >= oh:
sheet = img.crop((0,0,ow,oh))
else:
sheet = img.resize((ow,oh), Image.NEAREST)
stages = ${JSON.stringify(STAGES)}
moods = ${JSON.stringify(MOODS)}
mood_file = ${JSON.stringify(MOOD_FILE)}
out = ${JSON.stringify(OUT)}
os.makedirs(os.path.join(out,'sheets'), exist_ok=True)
for st in range(5):
folder = os.path.join(out, f'stage-{st}-{stages[st]}')
os.makedirs(folder, exist_ok=True)
row_imgs = []
for mi,m in enumerate(moods):
x,y = mi*cell, st*cell
tile = sheet.crop((x,y,x+cell,y+cell))
fn = os.path.join(folder, mood_file[m] + '.png')
tile.save(fn)
row_imgs.append(tile)
print('wrote', fn)
row = Image.new('RGBA', (cols*cell, cell), (0,0,0,0))
for i,t in enumerate(row_imgs):
row.paste(t, (i*cell, 0))
sp = os.path.join(out,'sheets', f'stage-{st}-sheet.png')
row.save(sp)
print('sheet', sp)
full = Image.new('RGBA', (cols*cell, rows*cell), (0,0,0,0))
full.paste(sheet, (0,0))
fp = os.path.join(out,'sheets','all-stages-sheet.png')
full.save(fp)
print('full', fp)
`;
const pyPath = path.join(TMP, 'slice.py');
fs.writeFileSync(pyPath, py);
console.log('Slicing…');
execSync('python3 '+pyPath, { stdio:'inherit' });
console.log('OK →', OUT);