From 9e88804695ddf1b6e6f3a2aba12f5099b66e2769 Mon Sep 17 00:00:00 2001 From: michael <1+michael@noreply.git.casaderoll.de> Date: Thu, 24 Sep 2026 10:08:00 +0200 Subject: [PATCH] Add procedural pixel-art sprite engine --- public/js/core/sprites.js | 101 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 public/js/core/sprites.js diff --git a/public/js/core/sprites.js b/public/js/core/sprites.js new file mode 100644 index 0000000..daa9c6d --- /dev/null +++ b/public/js/core/sprites.js @@ -0,0 +1,101 @@ +/* ============================================================ + DSP ARCADE — procedural pixel-art sprites + ============================================================ */ +(function () { + 'use strict'; + + const cache = {}; + + function makeSprite(map, palette, scale = 1) { + const h = map.length; + const w = Math.max(...map.map(r => r.length)); + const cv = document.createElement('canvas'); + cv.width = w; cv.height = h; + const ctx = cv.getContext('2d'); + for (let y = 0; y < h; y++) { + for (let x = 0; x < map[y].length; x++) { + const c = map[y][x]; + if (c === '.' || c === ' ') continue; + ctx.fillStyle = palette[c] || '#fff'; + ctx.fillRect(x, y, 1, 1); + } + } + if (scale > 1) { + const big = document.createElement('canvas'); + big.width = w * scale; big.height = h * scale; + const bctx = big.getContext('2d'); + bctx.imageSmoothingEnabled = false; + bctx.drawImage(cv, 0, 0, w * scale, h * scale); + return big; + } + return cv; + } + + function sprite(map, palette, scale = 1, key) { + const k = key || JSON.stringify([map, palette, scale]); + if (!cache[k]) cache[k] = makeSprite(map, palette, scale); + return cache[k]; + } + + function draw(ctx, img, x, y) { + ctx.drawImage(img, Math.round(x - img.width / 2), Math.round(y - img.height / 2)); + } + + const SPRITES = { + ship: { + map: ['...W...','..WWW..','..WWW..','.WWWWWW','WWWWWWWW','W.W..W.W'], + palette: { W: '#e8f4ff' }, + }, + shipAlt: { + map: ['...C...','..CCC..','..CCC..','.CCCCCC','CCCCCCCC','C.C..C.C'], + palette: { C: '#7df9ff' }, + }, + invader1: { + map: ['..X...X..','...X.X...','..XXXXX..','.XX.X.XX.','XXXXXXXXX','X.XXXXX.X','X.X...X.X','...X.X...'], + palette: { X: '#7dff5e' }, + }, + invader2: { + map: ['...X.X...','..XXXXX..','.XXXXXXX.','XX.XXX.XX','XXXXXXXXX','..X...X..','.X.X.X.X.','X..X.X..X'], + palette: { X: '#ff7de9' }, + }, + invader3: { + map: ['..X...X..','.XXXXXXX.','XXXXXXXXX','XXX.X.XXX','XXXXXXXXX','.X.....X.','X..X.X..X','.X.....X.'], + palette: { X: '#ffe14d' }, + }, + ufo: { + map: ['...GGGG...','..GGGGGG..','.GGGGGGGG.','GGGRRRRGGG','GGGGGGGGGG','.G.G..G.G.'], + palette: { G: '#9d7dff', R: '#ff4d6d' }, + }, + heart: { + map: ['.XX.XX.','XXXXXXX','XXXXXXX','.XXXXX.','..XXX..','...X...'], + palette: { X: '#ff2d95' }, + }, + heartEmpty: { + map: ['.XX.XX.','X.....X','X.....X','.X...X.','..X.X..','...X...'], + palette: { X: '#5a2a5a' }, + }, + star: { + map: ['...X...','...X...','XXXXXXX','...X...','...X...'], + palette: { X: '#ffffff' }, + }, + pellet: { + map: ['XX','XX'], + palette: { X: '#ffe14d' }, + }, + bonusCell: { + map: ['.XXX.','X...X','X.X.X','X...X','.XXX.'], + palette: { X: '#ff9d2d' }, + }, + ghostCell: { + map: ['.XXX.','X...X','X.X.X','X...X','.XXX.'], + palette: { X: '#7df9ff' }, + }, + }; + + function get(name, scale = 1) { + const s = SPRITES[name]; + return sprite(s.map, s.palette, scale, name + '_' + scale); + } + + window.DSP_SPRITES = { get, sprite, draw, SPRITES }; +})(); \ No newline at end of file