-- Aseprite Lua: player + NPCs + portraits + extras -- Run: aseprite -b -script scripts/generate_sprites.lua local function newSprite(w, h) local s = Sprite(w, h, ColorMode.RGB) app.activeSprite = s return s end local function clear(img, w, h) for y = 0, h - 1 do for x = 0, w - 1 do img:drawPixel(x, y, app.pixelColor.rgba(0, 0, 0, 0)) end end end local function P(img, x, y, r, g, b, a) a = a or 255 if x < 0 or y < 0 then return end img:drawPixel(x, y, app.pixelColor.rgba(r, g, b, a)) end local function fillRect(img, x0, y0, x1, y1, r, g, b) for y = y0, y1 do for x = x0, x1 do P(img, x, y, r, g, b) end end end -- Draw a 16x24 character into img at offset (ox, oy) -- face: 0 smile, 1 flat, 2 off (eye lower 1px) local function drawChar(img, ox, oy, bodyR, bodyG, bodyB, headR, headG, headB, face, walkFrame, dir) -- shadow fillRect(img, ox + 3, oy + 22, ox + 12, oy + 23, 0, 0, 0) for x = ox + 3, ox + 12 do P(img, x, oy + 22, 0, 0, 0, 50) P(img, x, oy + 23, 0, 0, 0, 30) end -- legs local legOff = 0 if walkFrame == 1 then legOff = 1 end if walkFrame == 3 then legOff = -1 end fillRect(img, ox + 4, oy + 16, ox + 6, oy + 21, 60, 50, 80) fillRect(img, ox + 9, oy + 16, ox + 11, oy + 21, 60, 50, 80) if walkFrame == 1 then fillRect(img, ox + 4, oy + 16, ox + 6, oy + 21, 0, 0, 0) fillRect(img, ox + 3, oy + 16, ox + 5, oy + 21, 60, 50, 80) elseif walkFrame == 3 then fillRect(img, ox + 9, oy + 16, ox + 11, oy + 21, 0, 0, 0) fillRect(img, ox + 10, oy + 16, ox + 12, oy + 21, 60, 50, 80) end -- body fillRect(img, ox + 3, oy + 10, ox + 12, oy + 17, bodyR, bodyG, bodyB) -- head fillRect(img, ox + 3, oy + 2, ox + 12, oy + 10, headR, headG, headB) -- ears/species hint for animals - small top nubs if headR > 200 and headG > 180 then -- rabbit ears fillRect(img, ox + 4, oy + 0, ox + 5, oy + 3, headR, headG, headB) fillRect(img, ox + 10, oy + 0, ox + 11, oy + 3, headR, headG, headB) end -- face local eyeY = 5 local eyeY2 = 5 if face == 2 then eyeY2 = 6 end -- off: one eye 1px lower if dir == 0 then -- down P(img, ox + 5, oy + eyeY, 30, 30, 40) P(img, ox + 10, oy + eyeY2, 30, 30, 40) if face == 0 then P(img, ox + 6, oy + 8, 200, 80, 100) P(img, ox + 7, oy + 8, 200, 80, 100) P(img, ox + 8, oy + 8, 200, 80, 100) P(img, ox + 9, oy + 8, 200, 80, 100) elseif face == 1 then P(img, ox + 6, oy + 8, 80, 60, 70) P(img, ox + 7, oy + 8, 80, 60, 70) P(img, ox + 8, oy + 8, 80, 60, 70) P(img, ox + 9, oy + 8, 80, 60, 70) else P(img, ox + 6, oy + 8, 200, 80, 100) P(img, ox + 7, oy + 8, 200, 80, 100) P(img, ox + 8, oy + 8, 200, 80, 100) P(img, ox + 9, oy + 9, 200, 80, 100) -- mouth 1px off end elseif dir == 1 then -- up: no face fillRect(img, ox + 3, oy + 2, ox + 12, oy + 6, headR - 10, headG - 10, headB - 10) elseif dir == 2 then -- left P(img, ox + 5, oy + eyeY, 30, 30, 40) if face == 0 then P(img, ox + 4, oy + 8, 200, 80, 100) end else -- right P(img, ox + 10, oy + eyeY2, 30, 30, 40) if face == 0 then P(img, ox + 11, oy + 8, 200, 80, 100) end end end -- Player sheet: 4 dirs x 4 frames = 16 frames, each 16x24. Layout 4 cols x 4 rows local pw, ph = 16 * 4, 24 * 4 local ps = newSprite(pw, ph) local pimg = ps.cels[1].image clear(pimg, pw, ph) for dir = 0, 3 do for frame = 0, 3 do drawChar(pimg, frame * 16, dir * 24, 100, 170, 220, 250, 220, 190, 0, frame, dir) end end ps:saveAs("public/assets/sprites/player.aseprite") ps:saveCopyAs("public/assets/sprites/player.png") -- Player portraits: smile + real face, 32x32 each side by side local port = newSprite(64, 32) local po = port.cels[1].image clear(po, 64, 32) -- smile portrait fillRect(po, 4, 4, 27, 28, 250, 220, 190) P(po, 11, 14, 30, 30, 40) P(po, 20, 14, 30, 30, 40) fillRect(po, 12, 20, 19, 21, 200, 80, 100) -- real tired face fillRect(po, 36, 4, 59, 28, 240, 210, 185) P(po, 43, 14, 30, 30, 40) P(po, 52, 15, 30, 30, 40) fillRect(po, 44, 21, 51, 21, 100, 80, 90) P(po, 42, 18, 200, 160, 160) P(po, 53, 18, 200, 160, 160) port:saveAs("public/assets/sprites/portraits.aseprite") port:saveCopyAs("public/assets/sprites/portraits.png") -- Named NPCs: 7 npcs x 3 faces, each 16x24. Grid: 3 faces per row, 7 rows local npcs = { { name = "afu", br = 200, bg = 160, bb = 100, hr = 180, hg = 130, hb = 70 }, -- dog { name = "yuanyuan", br = 255, bg = 200, bb = 210, hr = 255, hg = 230, hb = 230 }, -- rabbit { name = "dabao", br = 160, bg = 110, bb = 70, hr = 180, hg = 130, hb = 80 }, -- bear { name = "mimi", br = 255, bg = 180, bb = 100, hr = 255, hg = 160, hb = 80 }, -- cat { name = "fu", br = 200, bg = 220, bb = 160, hr = 230, hg = 210, hb = 140 }, -- deer { name = "twinbirds", br = 100, bg = 180, bb = 220, hr = 255, hg = 220, hb = 80 }, { name = "gui", br = 100, bg = 150, bb = 90, hr = 120, hg = 160, hb = 100 }, -- turtle } local ns = newSprite(16 * 3, 24 * 7) local ni = ns.cels[1].image clear(ni, 16 * 3, 24 * 7) for i, n in ipairs(npcs) do for face = 0, 2 do drawChar(ni, face * 16, (i - 1) * 24, n.br, n.bg, n.bb, n.hr, n.hg, n.hb, face, 0, 0) end end ns:saveAs("public/assets/sprites/npcs.aseprite") ns:saveCopyAs("public/assets/sprites/npcs.png") -- Twin birds beak anim 2 frames 16x16 local tb = newSprite(32, 16) local ti = tb.cels[1].image clear(ti, 32, 16) -- frame 0 closed fillRect(ti, 2, 4, 13, 12, 100, 180, 220) P(ti, 5, 7, 30, 30, 40) fillRect(ti, 10, 8, 12, 9, 255, 100, 80) -- frame 1 open fillRect(ti, 18, 4, 29, 12, 100, 180, 220) P(ti, 21, 7, 30, 30, 40) fillRect(ti, 26, 7, 29, 10, 255, 100, 80) tb:saveAs("public/assets/sprites/birds.aseprite") tb:saveCopyAs("public/assets/sprites/birds.png") -- Extras x5, 2 frames each, 16x24 local ex = newSprite(16 * 2, 24 * 5) local ei = ex.cels[1].image clear(ei, 32, 120) local colors = { { 200, 140, 80 }, { 160, 100, 60 }, { 220, 160, 100 }, { 180, 120, 70 }, { 140, 100, 50 }, } for i = 1, 5 do local c = colors[i] for f = 0, 1 do drawChar(ei, f * 16, (i - 1) * 24, c[1], c[2], c[3], c[1] + 20, c[2] + 20, c[3] + 10, 0, f * 2, 0) end end ex:saveAs("public/assets/sprites/extras.aseprite") ex:saveCopyAs("public/assets/sprites/extras.png") -- Sunflower HUD petals sprite sheet 16x16 x 14 (12 petals + center + seeds) local sf = newSprite(16 * 14, 16) local si = sf.cels[1].image clear(si, 16 * 14, 16) for i = 0, 11 do local ox = i * 16 fillRect(si, ox + 4, 2, ox + 11, 13, 255, 210, 50) fillRect(si, ox + 6, 0, ox + 9, 15, 255, 200, 40) end -- center fillRect(si, 12 * 16 + 4, 4, 12 * 16 + 11, 11, 180, 120, 40) -- black seeds center fillRect(si, 13 * 16 + 4, 4, 13 * 16 + 11, 11, 40, 30, 20) for k = 0, 5 do P(si, 13 * 16 + 5 + (k % 3) * 2, 5 + math.floor(k / 3) * 3, 20, 15, 10) end sf:saveAs("public/assets/sprites/sunflower.aseprite") sf:saveCopyAs("public/assets/sprites/sunflower.png") -- UI dialog panel 320x48 local ui = newSprite(320, 48) local uiImg = ui.cels[1].image clear(uiImg, 320, 48) fillRect(uiImg, 0, 0, 319, 47, 40, 30, 25) fillRect(uiImg, 2, 2, 317, 45, 250, 240, 220) fillRect(uiImg, 4, 4, 315, 43, 255, 250, 235) ui:saveAs("public/assets/sprites/dialog_panel.aseprite") ui:saveCopyAs("public/assets/sprites/dialog_panel.png") print("sprites generated")