Spaces:
Running
Running
Download tools/preview-frames.js from kingabzpro/neon-rooftops: direct link, hf CLI and curl.
- Browser
- Download file 3.99 kB
-
https://huggingface.co/spaces/kingabzpro/neon-rooftops/resolve/main/tools/preview-frames.js
- Command line
-
hf download hf://spaces/kingabzpro/neon-rooftops/tools/preview-frames.js
-
curl -L -o preview-frames.js https://huggingface.co/spaces/kingabzpro/neon-rooftops/resolve/main/tools/preview-frames.js
3.99 kB
| // Render each fox frame magnified, with the anchor line and hit box overlaid, so | |
| // we can see whether they actually sit on the fox's body. | |
| // | |
| // node tools/preview-frames.js (uses the boxes in this file) | |
| ; | |
| const fs = require('fs'), path = require('path'), zlib = require('zlib'); | |
| const { decodePNG } = require('./pngdec.js'); | |
| function crc32(buf) { | |
| let c, t = []; | |
| for (let n = 0; n < 256; n++) { c = n; for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1; t[n] = c >>> 0; } | |
| let crc = 0xffffffff; | |
| for (let i = 0; i < buf.length; i++) crc = t[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8); | |
| return (crc ^ 0xffffffff) >>> 0; | |
| } | |
| const chunk = (type, data) => { | |
| const len = Buffer.alloc(4); len.writeUInt32BE(data.length); | |
| const td = Buffer.concat([Buffer.from(type, 'ascii'), data]); | |
| const crc = Buffer.alloc(4); crc.writeUInt32BE(crc32(td)); | |
| return Buffer.concat([len, td, crc]); | |
| }; | |
| function encodePNG(w, h, rgba) { | |
| const stride = w * 4, raw = Buffer.alloc((stride + 1) * h); | |
| for (let y = 0; y < h; y++) { raw[y * (stride + 1)] = 0; rgba.copy(raw, y * (stride + 1) + 1, y * stride, y * stride + stride); } | |
| const ihdr = Buffer.alloc(13); | |
| ihdr.writeUInt32BE(w, 0); ihdr.writeUInt32BE(h, 4); ihdr[8] = 8; ihdr[9] = 6; | |
| return Buffer.concat([Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]), | |
| chunk('IHDR', ihdr), chunk('IDAT', zlib.deflateSync(raw, { level: 9 })), chunk('IEND', Buffer.alloc(0))]); | |
| } | |
| const { SHEET, FOX_BOX } = require('./game-constants.js'); | |
| const img = decodePNG(fs.readFileSync(path.join(__dirname, '..', 'assets', 'sprites.png'))); | |
| const { bpp, px } = img; | |
| const at = (x, y) => { const i = (y * img.w + x) * bpp; return [px[i], px[i + 1], px[i + 2], px[i + 3]]; }; | |
| const S = 8, PAD = 14, COLS = 3; | |
| // only the fox frames carry hit boxes; SHEET also holds the obstacles and tiles | |
| const names = Object.keys(FOX_BOX); | |
| const cellW = Math.max(...names.map(n => SHEET[n].w)) * S + PAD * 2; | |
| const cellH = Math.max(...names.map(n => SHEET[n].h)) * S + PAD * 2; | |
| const rows = Math.ceil(names.length / COLS); | |
| const W = COLS * cellW, H = rows * cellH; | |
| const out = Buffer.alloc(W * H * 4); | |
| for (let i = 0; i < W * H; i++) { out[i * 4] = 0x08; out[i * 4 + 1] = 0x10; out[i * 4 + 2] = 0x20; out[i * 4 + 3] = 255; } | |
| const set = (x, y, r, g, b) => { | |
| if (x < 0 || y < 0 || x >= W || y >= H) return; | |
| const o = (y * W + x) * 4; out[o] = r; out[o + 1] = g; out[o + 2] = b; out[o + 3] = 255; | |
| }; | |
| names.forEach((name, idx) => { | |
| const f = SHEET[name]; | |
| const ox = (idx % COLS) * cellW + PAD; | |
| const oy = Math.floor(idx / COLS) * cellH + PAD; | |
| // sprite | |
| for (let y = 0; y < f.h; y++) for (let x = 0; x < f.w; x++) { | |
| const c = at(f.x + x, f.y + y); | |
| if (c[3] < 128) continue; | |
| for (let sy = 0; sy < S; sy++) for (let sx = 0; sx < S; sx++) set(ox + x * S + sx, oy + y * S + sy, c[0], c[1], c[2]); | |
| } | |
| // anchor line (ax) in cyan, and the ground row (ay) in pink | |
| for (let y = 0; y < f.h * S; y++) for (let t = 0; t < 2; t++) set(ox + f.ax * S + t, oy + y, 0x00, 0xf0, 0xff); | |
| for (let x = -PAD; x < f.w * S + PAD; x++) for (let t = 0; t < 2; t++) { | |
| if (oy + f.ay * S + t < H) set(ox + x, oy + f.ay * S + t, 0xff, 0x2e, 0xc7); | |
| } | |
| // hit box in yellow | |
| for (const b of FOX_BOX[name]) { | |
| for (let x = b.x * S; x < (b.x + b.w) * S; x++) { | |
| set(ox + x, oy + b.y * S, 0xff, 0xd1, 0x66); | |
| set(ox + x, oy + (b.y + b.h) * S - 1, 0xff, 0xd1, 0x66); | |
| } | |
| for (let y = b.y * S; y < (b.y + b.h) * S; y++) { | |
| set(ox + b.x * S, oy + y, 0xff, 0xd1, 0x66); | |
| set(ox + (b.x + b.w) * S - 1, oy + y, 0xff, 0xd1, 0x66); | |
| } | |
| } | |
| console.log(`${name.padEnd(5)} frame ${f.w}x${f.h} anchor ax=${f.ax} ground ay=${f.ay} box=${JSON.stringify(FOX_BOX[name][0])}`); | |
| }); | |
| fs.writeFileSync(path.join(__dirname, 'frame-check.png'), encodePNG(W, H, out)); | |
| console.log(`\nwrote tools/frame-check.png ${W}x${H}`); | |
| console.log('cyan = anchor x pink = ground row yellow = hit box'); | |