Honey Village: six model builds + hub frontend, Dockerized for Dokploy

This commit is contained in:
2026-07-15 10:50:12 -04:00
commit 67253bded5
523 changed files with 58879 additions and 0 deletions
+757
View File
@@ -0,0 +1,757 @@
-- 《崩坏童话:蜜糖村》全部像素素材生成脚本(Aseprite Lua
-- 运行:aseprite -b --script tools/gen_art.lua
-- 输出:public/assets/*.png + art/*.aseprite(源文件)
-- 全部素材为本脚本逐像素绘制的原创内容,无任何外部素材。
local pc = app.pixelColor
local function C(hex, a)
a = a or 255
local r = tonumber(hex:sub(1, 2), 16)
local g = tonumber(hex:sub(3, 4), 16)
local b = tonumber(hex:sub(5, 6), 16)
return pc.rgba(r, g, b, a)
end
-- ============ 糖果色板(村庄)+ 灰白色板(白色层) ============
local PAL = {
outline = C("5a4232"),
black = C("2a2420"),
white = C("ffffff"),
-- 草地/植物
grass = C("8fd05a"), grassD = C("79b94a"), grassL = C("a5e070"),
leaf = C("5da53f"), leafD = C("4a8a32"), trunk = C("8a5a34"), trunkD = C("6e4526"),
-- 路面/石
path = C("e8d5a8"), pathD = C("cbb488"), stone = C("c8c0a8"), stoneD = C("a8a088"),
-- 水
water = C("7ec8e8"), waterD = C("5fb0d8"), waterL = C("b8e6f8"),
-- 花
pink = C("ff9ecb"), red = C("e05858"), yellow = C("ffd94a"), berry = C("e8497a"),
-- 建筑
wall = C("fff1d0"), wallD = C("e8d8b0"), roof = C("f4924a"), roofD = C("d97733"),
wood = C("c98a4b"), woodD = C("a86a34"), woodL = C("e0aa6a"),
-- 室内
floor = C("e6c896"), floorD = C("d4b47e"), clinic = C("eaf2ec"), clinicD = C("d0e0d6"),
inwall = C("f6e8c8"), inwallD = C("e0cca0"),
-- 灰白层
wgray = C("e8e8ea"), wgrayD = C("cfcfd4"), wgrayDD = C("aeaeb6"), wdark = C("8a8a92"),
-- 其他
fog = C("e8ecf0"), fogD = C("cdd6de"),
metal = C("9aa4ac"), blue = C("5a88c8"), sky = C("8ecae6"),
}
-- ============ 绘制助手 ============
local function makeCtx(img, ox, oy)
local ctx = {}
function ctx.px(x, y, c) img:putPixel(ox + x, oy + y, c) end
function ctx.rect(x, y, w, h, c)
for j = y, y + h - 1 do for i = x, x + w - 1 do img:putPixel(ox + i, oy + j, c) end end
end
function ctx.hline(x, y, w, c) ctx.rect(x, y, w, 1, c) end
function ctx.vline(x, y, h, c) ctx.rect(x, y, 1, h, c) end
function ctx.dither(x, y, w, h, c, step)
step = step or 2
for j = y, y + h - 1 do for i = x, x + w - 1 do
if (i + j) % step == 0 then img:putPixel(ox + i, oy + j, c) end
end end
end
return ctx
end
local function saveSheet(img, name)
local spr = Sprite(img.width, img.height, ColorMode.RGB)
spr.cels[1].image = img
spr:saveAs("art/" .. name .. ".aseprite")
spr:saveCopyAs("public/assets/" .. name .. ".png")
spr:close()
end
-- ============ 瓦片集:10 列 × 8 行 = 80 瓦(16×16 ============
local T = {}
T[0] = function(c) -- 草地1
c.rect(0, 0, 16, 16, PAL.grass)
c.px(3, 4, PAL.grassL); c.px(11, 9, PAL.grassL); c.px(6, 13, PAL.grassD); c.px(13, 2, PAL.grassD)
end
T[1] = function(c) -- 草地2(草茎)
c.rect(0, 0, 16, 16, PAL.grass)
c.px(4, 5, PAL.grassD); c.px(4, 4, PAL.grassD); c.px(10, 10, PAL.grassD); c.px(10, 9, PAL.grassD)
c.px(13, 5, PAL.grassL); c.px(2, 12, PAL.grassL)
end
T[2] = function(c) -- 草地+白花
T[0](c); c.px(5, 6, PAL.white); c.px(4, 5, PAL.white); c.px(6, 5, PAL.white); c.px(5, 4, PAL.white); c.px(5, 5, PAL.yellow)
c.px(12, 12, PAL.white)
end
T[3] = function(c) -- 草地+粉花
T[1](c); c.px(11, 4, PAL.pink); c.px(10, 3, PAL.pink); c.px(12, 3, PAL.pink); c.px(11, 2, PAL.pink); c.px(11, 3, PAL.yellow)
c.px(3, 10, PAL.pink)
end
T[4] = function(c) -- 石板路
c.rect(0, 0, 16, 16, PAL.path)
c.hline(0, 7, 16, PAL.pathD); c.vline(7, 0, 8, PAL.pathD); c.vline(3, 8, 8, PAL.pathD); c.vline(12, 8, 8, PAL.pathD)
c.px(2, 3, PAL.pathD); c.px(10, 12, PAL.pathD)
end
T[5] = function(c) -- 石板路2
c.rect(0, 0, 16, 16, PAL.path)
c.hline(0, 3, 16, PAL.pathD); c.hline(0, 11, 16, PAL.pathD); c.vline(5, 4, 7, PAL.pathD); c.vline(11, 12, 4, PAL.pathD); c.vline(11, 0, 3, PAL.pathD)
end
T[6] = function(c) -- 路缘(草→路)
c.rect(0, 0, 16, 8, PAL.grass); c.rect(0, 8, 16, 8, PAL.path)
c.hline(0, 7, 16, PAL.grassD); c.hline(0, 8, 16, PAL.pathD)
c.px(3, 6, PAL.grassL); c.px(11, 12, PAL.pathD)
end
T[7] = function(c) -- 湖水1
c.rect(0, 0, 16, 16, PAL.water)
c.hline(2, 4, 4, PAL.waterL); c.hline(9, 10, 4, PAL.waterL); c.hline(5, 13, 3, PAL.waterD)
end
T[8] = function(c) -- 湖水2
c.rect(0, 0, 16, 16, PAL.water)
c.hline(8, 3, 4, PAL.waterD); c.hline(1, 9, 4, PAL.waterL); c.hline(11, 14, 3, PAL.waterL)
end
T[9] = function(c) -- 岸边(上草下水)
c.rect(0, 0, 16, 6, PAL.grass); c.rect(0, 6, 16, 10, PAL.water)
c.hline(0, 5, 16, PAL.grassD); c.hline(0, 6, 16, PAL.waterL)
end
T[10] = function(c) -- 花丛
c.rect(0, 0, 16, 16, PAL.grass)
c.rect(1, 2, 14, 12, PAL.leaf)
c.px(1, 2, PAL.grass); c.px(14, 2, PAL.grass); c.px(1, 13, PAL.grass); c.px(14, 13, PAL.grass)
c.px(4, 5, PAL.pink); c.px(8, 4, PAL.yellow); c.px(12, 6, PAL.white); c.px(3, 9, PAL.yellow)
c.px(7, 8, PAL.pink); c.px(11, 10, PAL.pink); c.px(5, 12, PAL.white); c.px(10, 12, PAL.yellow)
end
T[11] = function(c) -- 莓果丛(有果)
c.rect(0, 0, 16, 16, PAL.grass)
c.rect(2, 3, 12, 11, PAL.leaf); c.rect(3, 2, 10, 13, PAL.leaf)
c.dither(3, 3, 10, 10, PAL.leafD, 3)
c.px(5, 5, PAL.berry); c.px(10, 4, PAL.berry); c.px(7, 8, PAL.berry); c.px(11, 9, PAL.berry); c.px(4, 10, PAL.berry)
end
T[12] = function(c) -- 莓果丛(已采)
c.rect(0, 0, 16, 16, PAL.grass)
c.rect(2, 3, 12, 11, PAL.leaf); c.rect(3, 2, 10, 13, PAL.leaf)
c.dither(3, 3, 10, 10, PAL.leafD, 3)
end
T[13] = function(c) -- 树·左上
c.rect(0, 0, 16, 16, PAL.grass)
c.rect(4, 2, 12, 14, PAL.leaf)
c.px(4, 2, PAL.grass); c.px(5, 2, PAL.grass); c.px(4, 3, PAL.grass)
c.dither(5, 4, 11, 12, PAL.leafD, 4); c.px(8, 5, PAL.grassL); c.px(12, 8, PAL.grassL)
end
T[14] = function(c) -- 树·右上
c.rect(0, 0, 16, 16, PAL.grass)
c.rect(0, 2, 12, 14, PAL.leaf)
c.px(11, 2, PAL.grass); c.px(10, 2, PAL.grass); c.px(11, 3, PAL.grass)
c.dither(0, 4, 11, 12, PAL.leafD, 4); c.px(4, 6, PAL.grassL)
end
T[15] = function(c) -- 树·左下
c.rect(0, 0, 16, 16, PAL.grass)
c.rect(4, 0, 12, 6, PAL.leaf); c.dither(4, 0, 12, 6, PAL.leafD, 4)
c.px(4, 5, PAL.grass)
c.rect(10, 6, 6, 10, PAL.trunk); c.vline(10, 6, 10, PAL.trunkD)
end
T[16] = function(c) -- 树·右下
c.rect(0, 0, 16, 16, PAL.grass)
c.rect(0, 0, 12, 6, PAL.leaf); c.dither(0, 0, 12, 6, PAL.leafD, 4)
c.px(11, 5, PAL.grass)
c.rect(0, 6, 6, 10, PAL.trunk); c.vline(5, 6, 10, PAL.trunkD)
end
T[17] = function(c) -- 小灌木
c.rect(0, 0, 16, 16, PAL.grass)
c.rect(3, 5, 10, 9, PAL.leaf); c.rect(4, 4, 8, 11, PAL.leaf)
c.dither(4, 5, 8, 8, PAL.leafD, 3)
end
T[18] = function(c) -- 房屋外墙
c.rect(0, 0, 16, 16, PAL.wall)
c.hline(0, 15, 16, PAL.wallD); c.vline(0, 0, 16, PAL.wallD)
c.px(4, 4, PAL.wallD); c.px(11, 9, PAL.wallD)
end
T[19] = function(c) -- 外墙+窗
T[18](c)
c.rect(4, 4, 8, 8, PAL.woodD); c.rect(5, 5, 6, 6, PAL.sky)
c.vline(7, 5, 6, PAL.woodD); c.hline(5, 8, 6, PAL.woodD)
c.px(6, 6, PAL.waterL)
end
T[20] = function(c) -- 门
c.rect(0, 0, 16, 16, PAL.wall)
c.rect(3, 1, 10, 15, PAL.wood); c.rect(4, 2, 8, 14, PAL.woodL)
c.vline(4, 2, 14, PAL.wood); c.px(10, 9, PAL.woodD); c.hline(4, 2, 8, PAL.wood)
end
T[21] = function(c) -- 屋顶·左
c.rect(0, 0, 16, 16, PAL.roof)
c.hline(0, 0, 16, PAL.roofD); c.hline(0, 5, 16, PAL.roofD); c.hline(0, 10, 16, PAL.roofD); c.hline(0, 15, 16, PAL.roofD)
c.vline(0, 0, 16, PAL.roofD)
end
T[22] = function(c) -- 屋顶·右
c.rect(0, 0, 16, 16, PAL.roof)
c.hline(0, 0, 16, PAL.roofD); c.hline(0, 5, 16, PAL.roofD); c.hline(0, 10, 16, PAL.roofD); c.hline(0, 15, 16, PAL.roofD)
c.vline(15, 0, 16, PAL.roofD)
end
T[23] = function(c) -- 屋檐
c.rect(0, 0, 16, 8, PAL.roof); c.hline(0, 7, 16, PAL.roofD); c.hline(0, 6, 16, PAL.roofD)
c.rect(0, 8, 16, 8, PAL.wall); c.hline(0, 8, 16, PAL.wallD)
end
T[24] = function(c) -- 木地板
c.rect(0, 0, 16, 16, PAL.floor)
c.hline(0, 4, 16, PAL.floorD); c.hline(0, 9, 16, PAL.floorD); c.hline(0, 14, 16, PAL.floorD)
c.px(5, 2, PAL.floorD); c.px(12, 7, PAL.floorD); c.px(3, 12, PAL.floorD)
end
T[25] = function(c) -- 木地板2
c.rect(0, 0, 16, 16, PAL.floor)
c.hline(0, 2, 16, PAL.floorD); c.hline(0, 7, 16, PAL.floorD); c.hline(0, 12, 16, PAL.floorD)
c.px(9, 4, PAL.floorD); c.px(2, 9, PAL.floorD)
end
T[26] = function(c) -- 诊所地砖
c.rect(0, 0, 16, 16, PAL.clinic)
c.hline(0, 7, 16, PAL.clinicD); c.vline(7, 0, 16, PAL.clinicD); c.hline(0, 15, 16, PAL.clinicD); c.vline(15, 0, 16, PAL.clinicD)
end
T[27] = function(c) -- 室内墙
c.rect(0, 0, 16, 16, PAL.inwall)
c.dither(0, 0, 16, 16, PAL.inwallD, 8)
end
T[28] = function(c) -- 室内墙裙
c.rect(0, 0, 16, 10, PAL.inwall)
c.rect(0, 10, 16, 6, PAL.wood); c.hline(0, 10, 16, PAL.woodD)
end
T[29] = function(c) -- 地毯
c.rect(0, 0, 16, 16, PAL.pink)
c.rect(1, 1, 14, 14, C("ffc4de")); c.rect(3, 3, 10, 10, PAL.pink)
c.px(7, 7, PAL.white); c.px(8, 8, PAL.white)
end
T[30] = function(c) -- 床头(枕头)
c.rect(0, 0, 16, 16, PAL.wood)
c.rect(1, 1, 14, 15, C("f8f4e8"))
c.rect(3, 3, 10, 7, PAL.white); c.hline(3, 9, 10, C("d8d4c8"))
c.hline(1, 0, 14, PAL.woodD)
end
T[31] = function(c) -- 床尾(被子)
c.rect(0, 0, 16, 16, PAL.wood)
c.rect(1, 0, 14, 15, C("8ec8e0"))
c.hline(1, 3, 14, C("6fb0d0")); c.hline(1, 8, 14, C("6fb0d0")); c.hline(1, 13, 14, C("6fb0d0"))
c.hline(1, 15, 14, PAL.woodD)
end
T[32] = function(c) -- 柜台
c.rect(0, 0, 16, 16, PAL.wood)
c.rect(0, 0, 16, 6, PAL.woodL); c.hline(0, 6, 16, PAL.woodD)
c.vline(0, 0, 16, PAL.woodD); c.vline(15, 0, 16, PAL.woodD); c.hline(0, 15, 16, PAL.woodD)
end
T[33] = function(c) -- 烤炉
c.rect(0, 0, 16, 16, PAL.stoneD)
c.rect(1, 1, 14, 14, PAL.stone)
c.rect(3, 6, 10, 7, PAL.black)
c.rect(4, 8, 8, 4, C("f4924a")); c.px(6, 9, PAL.yellow); c.px(9, 10, PAL.yellow)
c.hline(3, 3, 10, PAL.stoneD)
end
T[34] = function(c) -- 货架(空)
c.rect(0, 0, 16, 16, PAL.wood)
c.rect(1, 1, 14, 14, PAL.woodL)
c.hline(1, 5, 14, PAL.woodD); c.hline(1, 10, 14, PAL.woodD); c.hline(1, 14, 14, PAL.woodD)
c.vline(0, 0, 16, PAL.woodD); c.vline(15, 0, 16, PAL.woodD)
end
T[35] = function(c) -- 货架(同一商品重复排列,标签空白)
T[34](c)
for _, y in ipairs({ 2, 7, 11 }) do
c.rect(2, y, 3, 3, PAL.berry); c.rect(6, y, 3, 3, PAL.berry); c.rect(10, y, 3, 3, PAL.berry)
c.px(3, y, PAL.white); c.px(7, y, PAL.white); c.px(11, y, PAL.white)
end
end
T[36] = function(c) -- 桌子
c.rect(0, 0, 16, 16, PAL.floor)
c.rect(2, 3, 12, 9, PAL.woodL); c.hline(2, 11, 12, PAL.woodD)
c.vline(3, 12, 3, PAL.woodD); c.vline(12, 12, 3, PAL.woodD)
c.hline(2, 3, 12, PAL.wood)
end
T[37] = function(c) -- 椅子
c.rect(0, 0, 16, 16, PAL.floor)
c.rect(4, 3, 8, 3, PAL.wood)
c.rect(4, 6, 8, 6, PAL.woodL)
c.vline(4, 12, 3, PAL.woodD); c.vline(11, 12, 3, PAL.woodD)
end
T[38] = function(c) -- 井·顶(小屋顶)
c.rect(0, 0, 16, 16, PAL.grass)
c.rect(2, 2, 12, 6, PAL.roofD); c.rect(3, 1, 10, 2, PAL.roof)
c.vline(3, 8, 8, PAL.trunkD); c.vline(12, 8, 8, PAL.trunkD)
end
T[39] = function(c) -- 井·底(石圈)
c.rect(0, 0, 16, 16, PAL.grass)
c.rect(2, 2, 12, 12, PAL.stone)
c.rect(4, 4, 8, 8, PAL.black)
c.px(3, 3, PAL.stoneD); c.px(12, 3, PAL.stoneD); c.px(3, 12, PAL.stoneD); c.px(12, 12, PAL.stoneD)
c.hline(2, 2, 12, PAL.stoneD)
end
T[40] = function(c) -- 雾(轻)
c.rect(0, 0, 16, 16, PAL.fog)
c.dither(0, 0, 16, 16, PAL.fogD, 3)
end
T[41] = function(c) -- 雾(浓)
c.rect(0, 0, 16, 16, PAL.fogD)
c.dither(0, 0, 16, 16, PAL.fog, 2); c.dither(1, 0, 16, 16, C("bcc8d2"), 5)
end
T[42] = function(c) -- 白色层·墙
c.rect(0, 0, 16, 16, PAL.wgray)
c.hline(0, 12, 16, PAL.wgrayD); c.rect(0, 13, 16, 3, PAL.wgrayDD)
end
T[43] = function(c) -- 白色层·地
c.rect(0, 0, 16, 16, PAL.wgrayD)
c.hline(0, 7, 16, PAL.wgrayDD); c.vline(7, 0, 16, PAL.wgrayDD)
end
T[44] = function(c) -- 白色层·门
c.rect(0, 0, 16, 16, PAL.wgray)
c.rect(3, 1, 10, 15, PAL.wgrayDD); c.rect(4, 2, 8, 14, PAL.wgrayD)
c.px(10, 9, PAL.wdark)
end
T[45] = function(c) -- 长椅·左
c.rect(0, 0, 16, 16, PAL.grass)
c.rect(2, 4, 14, 3, PAL.woodL); c.rect(2, 7, 14, 4, PAL.wood)
c.vline(3, 11, 3, PAL.woodD); c.hline(2, 7, 14, PAL.woodD)
end
T[46] = function(c) -- 长椅·右
c.rect(0, 0, 16, 16, PAL.grass)
c.rect(0, 4, 14, 3, PAL.woodL); c.rect(0, 7, 14, 4, PAL.wood)
c.vline(12, 11, 3, PAL.woodD); c.hline(0, 7, 14, PAL.woodD)
end
T[47] = function(c) -- 公告板·左
c.rect(0, 0, 16, 16, PAL.grass)
c.rect(2, 1, 14, 12, PAL.wood); c.rect(4, 3, 12, 8, C("f8f0d8"))
c.hline(5, 5, 8, PAL.stoneD); c.hline(5, 7, 6, PAL.stoneD); c.hline(5, 9, 9, PAL.stoneD)
c.vline(4, 13, 3, PAL.trunkD)
end
T[48] = function(c) -- 公告板·右
c.rect(0, 0, 16, 16, PAL.grass)
c.rect(0, 1, 14, 12, PAL.wood); c.rect(0, 3, 12, 8, C("f8f0d8"))
c.hline(1, 5, 8, PAL.stoneD); c.hline(1, 7, 9, PAL.stoneD); c.hline(1, 9, 5, PAL.stoneD)
c.vline(11, 13, 3, PAL.trunkD)
end
T[49] = function(c) -- 邮筒
c.rect(0, 0, 16, 16, PAL.grass)
c.rect(4, 2, 8, 10, PAL.red); c.rect(5, 1, 6, 2, C("c04040"))
c.rect(6, 5, 4, 2, PAL.black)
c.vline(6, 12, 3, C("c04040")); c.vline(9, 12, 3, C("c04040"))
end
T[50] = function(c) -- 栅栏·横
c.rect(0, 0, 16, 16, PAL.grass)
c.hline(0, 6, 16, PAL.woodL); c.hline(0, 7, 16, PAL.wood); c.hline(0, 10, 16, PAL.woodL); c.hline(0, 11, 16, PAL.wood)
c.rect(2, 4, 2, 10, PAL.wood); c.rect(12, 4, 2, 10, PAL.wood)
c.px(2, 4, PAL.woodL); c.px(12, 4, PAL.woodL)
end
T[51] = function(c) -- 栅栏·柱
c.rect(0, 0, 16, 16, PAL.grass)
c.rect(6, 2, 3, 12, PAL.wood); c.px(6, 2, PAL.woodL); c.px(7, 2, PAL.woodL)
end
T[52] = function(c) -- 路牌
c.rect(0, 0, 16, 16, PAL.grass)
c.rect(2, 3, 12, 6, PAL.woodL); c.hline(3, 5, 10, PAL.woodD); c.hline(3, 7, 7, PAL.woodD)
c.rect(7, 9, 2, 6, PAL.wood)
end
T[53] = function(c) -- 野餐布1
c.rect(0, 0, 16, 16, C("f8f0e0"))
for j = 0, 15 do for i = 0, 15 do
if (math.floor(i / 4) + math.floor(j / 4)) % 2 == 0 then c.px(i, j, C("f0a0a8")) end
end end
end
T[54] = function(c) -- 野餐布2(有食物)
T[53](c)
c.rect(5, 5, 6, 6, C("fff8e8")); c.px(7, 7, PAL.berry); c.px(9, 8, PAL.berry); c.px(6, 9, PAL.yellow)
end
T[55] = function(c) -- 花坛(盛开)
c.rect(0, 0, 16, 16, PAL.grass)
c.rect(1, 1, 14, 14, PAL.stoneD); c.rect(2, 2, 12, 12, C("8a6a48"))
c.px(4, 4, PAL.pink); c.px(8, 5, PAL.yellow); c.px(11, 4, PAL.red); c.px(5, 8, PAL.yellow)
c.px(9, 9, PAL.pink); c.px(12, 8, PAL.white); c.px(4, 11, PAL.red); c.px(8, 12, PAL.pink); c.px(11, 11, PAL.yellow)
end
T[56] = function(c) -- 花田·土
c.rect(0, 0, 16, 16, C("a8825a"))
c.hline(0, 3, 16, C("94714c")); c.hline(0, 8, 16, C("94714c")); c.hline(0, 13, 16, C("94714c"))
end
T[57] = function(c) -- 花田·苗
T[56](c)
c.px(3, 2, PAL.leaf); c.px(3, 1, PAL.leaf); c.px(9, 7, PAL.leaf); c.px(9, 6, PAL.leaf); c.px(13, 12, PAL.leaf); c.px(13, 11, PAL.leaf)
c.px(4, 1, PAL.pink); c.px(10, 6, PAL.yellow)
end
T[58] = function(c) -- 彩带(挂起)
c.rect(0, 0, 16, 16, pc.rgba(0, 0, 0, 0))
for i = 0, 15 do
local y = math.floor(2 + 2 * math.sin(i / 4))
c.px(i, y + 1, PAL.woodD)
end
c.px(2, 4, PAL.red); c.px(2, 5, PAL.red); c.px(6, 5, PAL.yellow); c.px(6, 6, PAL.yellow)
c.px(10, 4, PAL.pink); c.px(10, 5, PAL.pink); c.px(14, 5, PAL.sky); c.px(14, 6, PAL.sky)
end
T[59] = function(c) -- 灯笼杆
c.rect(0, 0, 16, 16, PAL.grass)
c.rect(7, 2, 2, 12, PAL.trunk)
c.rect(5, 2, 6, 5, PAL.yellow); c.rect(6, 1, 4, 1, PAL.roofD); c.px(7, 4, C("fff0a0")); c.px(8, 4, C("fff0a0"))
end
T[60] = function(c) -- 湖水·波光
T[7](c)
c.px(4, 6, PAL.white); c.px(11, 8, PAL.white); c.hline(7, 2, 3, PAL.waterL)
end
T[61] = function(c) -- 室内窗
T[27](c)
c.rect(3, 3, 10, 8, PAL.woodD); c.rect(4, 4, 8, 6, PAL.sky)
c.vline(7, 4, 6, PAL.woodD); c.px(5, 5, PAL.waterL)
end
T[62] = function(c) -- 白色层·病床头
c.rect(0, 0, 16, 16, PAL.wgrayD)
c.rect(1, 1, 14, 15, PAL.wgray)
c.rect(3, 3, 10, 6, PAL.white); c.hline(3, 8, 10, PAL.wgrayD)
c.hline(1, 0, 14, PAL.wgrayDD)
end
T[63] = function(c) -- 白色层·病床尾
c.rect(0, 0, 16, 16, PAL.wgrayD)
c.rect(1, 0, 14, 15, C("dfe2e6"))
c.hline(1, 4, 14, PAL.wgrayD); c.hline(1, 9, 14, PAL.wgrayD)
c.hline(1, 15, 14, PAL.wgrayDD)
end
T[64] = function(c) -- 桌(诊所/办公)
c.rect(0, 0, 16, 16, PAL.clinic)
c.rect(1, 3, 14, 9, PAL.woodL); c.hline(1, 11, 14, PAL.woodD); c.hline(1, 3, 14, PAL.wood)
c.rect(3, 5, 6, 4, PAL.white); c.hline(4, 6, 4, PAL.stoneD); c.hline(4, 7, 3, PAL.stoneD)
c.vline(2, 12, 3, PAL.woodD); c.vline(13, 12, 3, PAL.woodD)
end
T[65] = function(c) -- 日历(墙面)
T[27](c)
c.rect(4, 2, 8, 11, PAL.white); c.rect(4, 2, 8, 3, PAL.red)
c.hline(5, 7, 6, PAL.stoneD); c.hline(5, 9, 6, PAL.stoneD); c.rect(7, 10, 3, 2, PAL.black)
end
T[66] = function(c) -- 柜台+配方纸
T[32](c)
c.rect(4, 1, 7, 5, C("f8f0d8")); c.hline(5, 2, 5, PAL.stoneD); c.hline(5, 4, 4, PAL.stoneD)
end
T[67] = function(c) -- 木箱
c.rect(0, 0, 16, 16, PAL.grass)
c.rect(2, 3, 12, 12, PAL.wood); c.rect(3, 4, 10, 10, PAL.woodL)
c.hline(3, 8, 10, PAL.wood); c.vline(8, 4, 10, PAL.wood)
end
T[68] = function(c) -- 篮子
c.rect(0, 0, 16, 16, PAL.floor)
c.rect(3, 6, 10, 8, PAL.woodL); c.hline(3, 6, 10, PAL.woodD); c.hline(3, 13, 10, PAL.woodD)
c.dither(4, 8, 8, 5, PAL.wood, 2)
c.px(6, 5, PAL.berry); c.px(9, 5, PAL.berry)
end
T[69] = function(c) -- 花坛(干涸·待浇水)
c.rect(0, 0, 16, 16, PAL.grass)
c.rect(1, 1, 14, 14, PAL.stoneD); c.rect(2, 2, 12, 12, C("b09068"))
c.px(5, 5, C("8a7a5a")); c.px(10, 7, C("8a7a5a")); c.px(6, 10, C("8a7a5a"))
end
T[70] = function(c) -- 白色层·窗
T[42](c)
c.rect(4, 2, 8, 7, PAL.wgrayDD); c.rect(5, 3, 6, 5, C("c8ccd4")); c.vline(7, 3, 5, PAL.wgrayDD)
end
T[71] = function(c) -- 门垫
c.rect(0, 0, 16, 16, PAL.grass)
c.rect(2, 4, 12, 8, C("c8a878")); c.rect(3, 5, 10, 6, C("dcc094"))
end
T[72] = function(c) -- 高草
c.rect(0, 0, 16, 16, PAL.grass)
for i = 1, 14, 3 do c.vline(i, 6, 8, PAL.grassD); c.vline(i + 1, 8, 6, PAL.grassL) end
end
T[73] = function(c) -- 广场中心砖
c.rect(0, 0, 16, 16, PAL.path)
c.rect(2, 2, 12, 12, C("f0c88a")); c.rect(5, 5, 6, 6, PAL.path)
c.px(7, 7, PAL.roofD); c.px(8, 8, PAL.roofD)
end
T[74] = function(c) -- 白色层·帘
c.rect(0, 0, 16, 16, PAL.wgray)
for i = 0, 15, 3 do c.vline(i, 0, 16, C("d8dade")) end
c.hline(0, 0, 16, PAL.wgrayDD)
end
T[75] = function(c) -- 白色层·监护仪
c.rect(0, 0, 16, 16, PAL.wgrayD)
c.rect(3, 2, 10, 9, PAL.wdark); c.rect(4, 3, 8, 6, C("354048"))
c.px(5, 6, C("7ae0a0")); c.px(6, 5, C("7ae0a0")); c.px(7, 6, C("7ae0a0")); c.px(8, 6, C("7ae0a0")); c.px(9, 4, C("7ae0a0")); c.px(10, 6, C("7ae0a0"))
c.rect(6, 11, 4, 4, PAL.wgrayDD)
end
T[76] = function(c) -- 石墙(村界)
c.rect(0, 0, 16, 16, PAL.stone)
c.hline(0, 5, 16, PAL.stoneD); c.hline(0, 11, 16, PAL.stoneD)
c.vline(4, 0, 5, PAL.stoneD); c.vline(10, 6, 5, PAL.stoneD); c.vline(6, 12, 4, PAL.stoneD)
end
T[77] = function(c) -- 雾中草地(边缘)
c.rect(0, 0, 16, 16, PAL.grass)
c.dither(0, 0, 16, 16, PAL.fog, 2)
end
T[78] = function(c) -- 黑
c.rect(0, 0, 16, 16, PAL.black)
end
T[79] = function(c) -- 纯白灰
c.rect(0, 0, 16, 16, PAL.wgray)
end
local tilesImg = Image(160, 128, ColorMode.RGB)
for idx = 0, 79 do
local col, row = idx % 10, math.floor(idx / 10)
T[idx](makeCtx(tilesImg, col * 16, row * 16))
end
saveSheet(tilesImg, "tiles")
-- ============ 主角:16×24 × 16 帧(下/上/左/右 × 4 ============
local SKIN = C("f6d2a8")
local HAIR = C("6b4632")
local SHIRT = C("e87858")
local SHIRT_D = C("cc5f42")
local PANTS = C("4a6a9a")
local SHOE = C("7a4a2a")
-- 画一帧主角;dir: 0下 1上 2左 3右;f: 0..3 行走帧(0 兼待机)
local function paintPlayer(c, dir, f)
-- 头(x4..11, y0..7):发 0..2,脸 3..7
c.rect(4, 0, 8, 3, HAIR)
c.px(4, 0, pc.rgba(0, 0, 0, 0)); c.px(11, 0, pc.rgba(0, 0, 0, 0))
if dir == 1 then
c.rect(4, 3, 8, 5, HAIR) -- 背面全是头发
c.px(4, 7, SKIN); c.px(11, 7, SKIN)
else
c.rect(4, 3, 8, 5, SKIN)
c.vline(4, 3, 2, HAIR); c.vline(11, 3, 2, HAIR) -- 刘海两侧
if dir == 0 then
c.px(6, 5, PAL.black); c.px(9, 5, PAL.black)
c.px(7, 7, PAL.black); c.px(8, 7, PAL.black) -- 永远的微笑
c.px(6, 6, C("f8b8a0")); c.px(9, 6, C("f8b8a0"))
elseif dir == 2 then
c.rect(9, 3, 3, 5, HAIR) -- 侧脸:后脑
c.px(6, 5, PAL.black); c.px(5, 7, PAL.black); c.px(6, 7, PAL.black)
elseif dir == 3 then
c.rect(4, 3, 3, 5, HAIR)
c.px(9, 5, PAL.black); c.px(9, 7, PAL.black); c.px(10, 7, PAL.black)
end
end
-- 身体(y8..16
c.rect(5, 8, 6, 9, SHIRT)
c.hline(5, 16, 6, SHIRT_D)
-- 手臂:随帧摆动
local sway = ({ 0, 1, 0, -1 })[f + 1]
if dir == 0 or dir == 1 then
c.vline(4, 9 + (sway > 0 and 1 or 0), 5, SHIRT_D); c.px(4, 14 + (sway > 0 and 1 or 0), SKIN)
c.vline(11, 9 + (sway < 0 and 1 or 0), 5, SHIRT_D); c.px(11, 14 + (sway < 0 and 1 or 0), SKIN)
else
c.vline(dir == 2 and 7 or 8, 9 + math.abs(sway), 5, SHIRT_D)
c.px(dir == 2 and 7 or 8, 14 + math.abs(sway), SKIN)
end
-- 腿(y17..20)与鞋(y21..23
local lLift = (f == 1) and 1 or 0
local rLift = (f == 3) and 1 or 0
c.rect(5, 17, 3, 4 - lLift, PANTS)
c.rect(8, 17, 3, 4 - rLift, PANTS)
c.rect(5, 21 - lLift, 3, 2, SHOE)
c.rect(8, 21 - rLift, 3, 2, SHOE)
end
local playerImg = Image(256, 24, ColorMode.RGB)
for dir = 0, 3 do
for f = 0, 3 do
paintPlayer(makeCtx(playerImg, (dir * 4 + f) * 16, 0), dir, f)
end
end
saveSheet(playerImg, "player")
-- ============ 主角头像:24×24(微笑 / 真实的脸) ============
local function paintPortrait(c, real)
c.rect(0, 0, 24, 24, C("f8ecd0")) -- 背景
c.rect(4, 2, 16, 6, HAIR) -- 头发
c.px(4, 2, C("f8ecd0")); c.px(19, 2, C("f8ecd0"))
c.rect(4, 8, 16, 12, SKIN) -- 脸
c.vline(4, 8, 4, HAIR); c.vline(5, 8, 2, HAIR)
c.vline(19, 8, 4, HAIR); c.vline(18, 8, 2, HAIR)
c.rect(6, 20, 12, 4, SHIRT) -- 肩
if real then
-- 真实的脸:疲惫——平直的嘴、眼下阴影、微乱的头发
c.px(3, 4, HAIR); c.px(20, 5, HAIR); c.px(2, 5, HAIR)
c.rect(8, 12, 2, 2, PAL.black); c.rect(14, 12, 2, 2, PAL.black)
c.hline(8, 15, 2, C("c8a080")); c.hline(14, 15, 2, C("c8a080")) -- 眼袋
c.hline(10, 18, 4, C("9a6a52")) -- 平直疲惫的嘴
else
-- 永远的微笑
c.rect(8, 12, 2, 2, PAL.black); c.rect(14, 12, 2, 2, PAL.black)
c.px(7, 14, C("f8b8a0")); c.px(16, 14, C("f8b8a0")) -- 腮红
c.px(9, 17, C("9a6a52")); c.px(14, 17, C("9a6a52"))
c.hline(10, 18, 4, C("9a6a52")) -- 上弯的微笑
end
end
local p1 = Image(24, 24, ColorMode.RGB); paintPortrait(makeCtx(p1, 0, 0), false); saveSheet(p1, "portrait_smile")
local p2 = Image(24, 24, ColorMode.RGB); paintPortrait(makeCtx(p2, 0, 0), true); saveSheet(p2, "portrait_real")
-- ============ 具名 NPC16×24 × 4 帧(smile/flat/off/blink ============
-- off 档与 smile 档仅差 1–2 像素(右眼低 1 像素),绝不画成鬼脸。
local function paintAnimal(c, spec, expr)
local top = spec.headTop or 2
-- 头
c.rect(2, top, 12, 10, spec.fur)
c.px(2, top, pc.rgba(0, 0, 0, 0)); c.px(13, top, pc.rgba(0, 0, 0, 0))
c.px(2, top + 9, pc.rgba(0, 0, 0, 0)); c.px(13, top + 9, pc.rgba(0, 0, 0, 0))
-- 口鼻
c.rect(5, top + 5, 6, 4, spec.muzzle)
-- 眼睛
local ey = top + 4
if expr == "blink" then
c.px(5, ey + 1, PAL.black); c.px(10, ey + 1, PAL.black)
else
c.px(5, ey, PAL.black); c.px(5, ey + 1, PAL.black)
local roff = (expr == "off") and 1 or 0 -- 崩坏档:右眼低 1 像素
c.px(10, ey + roff, PAL.black); c.px(10, ey + 1 + roff, PAL.black)
end
-- 鼻子
if spec.nose then c.px(7, top + 6, PAL.black); c.px(8, top + 6, PAL.black) end
-- 嘴:微笑(小 U)或平直
local my = top + 8
if expr == "flat" then
c.hline(6, my, 4, PAL.black)
else
c.px(6, my - 1, PAL.black); c.px(9, my - 1, PAL.black); c.px(7, my, PAL.black); c.px(8, my, PAL.black)
end
-- 身体
c.rect(4, 12, 8, 8, spec.cloth)
-- 手
c.vline(3, 13, 4, spec.fur); c.vline(12, 13, 4, spec.fur)
-- 脚
c.rect(4, 20, 3, 3, spec.furD); c.rect(9, 20, 3, 3, spec.furD)
if spec.extra then spec.extra(c, expr) end
end
local NPCS = {
{ file = "npc_afu", fur = C("c49058"), furD = C("a87840"), muzzle = C("ecd0a8"), cloth = C("e0b060"), nose = true,
extra = function(c) -- 狗村长:垂耳+红领巾
c.rect(1, 3, 2, 5, C("a87840")); c.rect(13, 3, 2, 5, C("a87840"))
c.hline(4, 12, 8, PAL.red); c.px(7, 13, PAL.red); c.px(8, 13, PAL.red)
end },
{ file = "npc_yuanyuan", fur = C("f7f2ea"), furD = C("ddd4c8"), muzzle = C("ffffff"), cloth = C("f8c8d8"), headTop = 4,
extra = function(c) -- 兔面包师:长耳+围裙
c.rect(4, 0, 2, 4, C("f7f2ea")); c.px(5, 1, C("f8b8c8")); c.px(5, 2, C("f8b8c8"))
c.rect(10, 0, 2, 4, C("f7f2ea")); c.px(10, 1, C("f8b8c8")); c.px(10, 2, C("f8b8c8"))
c.rect(5, 14, 6, 6, C("fff8f0")); c.px(6, 10, C("f0a0b0")) -- 围裙+腮红
c.px(9, 10, C("f0a0b0"))
end },
{ file = "npc_dabao", fur = C("9a6b42"), furD = C("7e5530"), muzzle = C("c89a68"), cloth = C("5a88c8"), nose = true,
extra = function(c) -- 熊邮差:圆耳+挎包带
c.rect(3, 0, 3, 2, C("9a6b42")); c.rect(10, 0, 3, 2, C("9a6b42"))
c.px(4, 1, C("c89a68")); c.px(11, 1, C("c89a68"))
for i = 0, 6 do c.px(4 + i, 13 + i > 19 and 19 or 13 + math.floor(i * 0.9), C("3a5a88")) end
end },
{ file = "npc_mimi", fur = C("c8c0d8"), furD = C("a8a0c0"), muzzle = C("ece8f4"), cloth = C("e8a04a"), nose = true,
extra = function(c) -- 猫店主:尖耳+胡须
c.px(3, 1, C("c8c0d8")); c.rect(3, 2, 2, 1, C("c8c0d8")); c.px(4, 1, C("c8c0d8"))
c.px(12, 1, C("c8c0d8")); c.rect(11, 2, 2, 1, C("c8c0d8")); c.px(11, 1, C("c8c0d8"))
c.hline(1, 8, 2, C("8a84a0")); c.hline(13, 8, 2, C("8a84a0"))
end },
{ file = "npc_fu", fur = C("d8b088"), furD = C("bc9268"), muzzle = C("f0d8b8"), cloth = C("f4f4f4"), nose = true,
extra = function(c) -- 鹿医生:小角+白大褂
c.px(4, 1, C("8a6a4a")); c.px(4, 0, C("8a6a4a")); c.px(3, 0, C("8a6a4a"))
c.px(11, 1, C("8a6a4a")); c.px(11, 0, C("8a6a4a")); c.px(12, 0, C("8a6a4a"))
c.vline(7, 12, 8, C("d8d8d8")) -- 大褂门襟
end },
{ file = "npc_gui", fur = C("a8c078"), furD = C("88a058"), muzzle = C("c8d8a0"), cloth = C("7a9a4a"),
extra = function(c) -- 龟爷爷:龟壳纹+老花镜
c.hline(5, 14, 6, C("5f7c38")); c.hline(5, 17, 6, C("5f7c38")); c.vline(7, 13, 6, C("5f7c38"))
local gy = 2 + 4
c.px(4, gy, C("6a6a6a")); c.px(6, gy, C("6a6a6a")); c.px(9, gy, C("6a6a6a")); c.px(11, gy, C("6a6a6a"))
c.px(7, gy, C("6a6a6a")); c.px(8, gy, C("6a6a6a"))
end },
}
for _, spec in ipairs(NPCS) do
local img = Image(64, 24, ColorMode.RGB)
local exprs = { "smile", "flat", "off", "blink" }
for i, e in ipairs(exprs) do
paintAnimal(makeCtx(img, (i - 1) * 16, 0), spec, e)
end
saveSheet(img, spec.file)
end
-- ============ 双子鸟:16×16 × 2 帧(闭嘴/张嘴) ============
local function paintBird(c, open)
local B = C("6ab0e8")
local BD = C("4a90c8")
c.rect(4, 5, 8, 7, B) -- 身体
c.rect(8, 2, 6, 6, B) -- 头
c.px(11, 4, PAL.black) -- 眼
c.rect(2, 6, 3, 4, BD) -- 尾
c.rect(5, 7, 4, 3, BD) -- 翅
if open then
c.px(14, 5, PAL.yellow); c.px(15, 4, PAL.yellow); c.px(15, 6, PAL.yellow)
else
c.px(14, 5, PAL.yellow); c.px(15, 5, PAL.yellow)
end
c.vline(7, 12, 2, PAL.yellow); c.vline(10, 12, 2, PAL.yellow) -- 脚
end
local birdImg = Image(32, 16, ColorMode.RGB)
paintBird(makeCtx(birdImg, 0, 0), false)
paintBird(makeCtx(birdImg, 16, 0), true)
saveSheet(birdImg, "bird")
-- ============ 灰麻雀(E3 结局,一只真的鸟):16×16 ============
local function paintSparrow(c)
local G = C("9a9088")
local GD = C("6e6660")
c.rect(4, 6, 8, 6, G)
c.rect(8, 3, 6, 5, G)
c.px(11, 5, PAL.black)
c.rect(2, 7, 3, 3, GD)
c.rect(5, 8, 4, 2, GD)
c.px(14, 6, C("7a6a58")); c.px(15, 6, C("7a6a58"))
c.vline(7, 12, 2, GD); c.vline(10, 12, 2, GD)
end
local sparrowImg = Image(16, 16, ColorMode.RGB)
paintSparrow(makeCtx(sparrowImg, 0, 0))
saveSheet(sparrowImg, "sparrow")
-- ============ 群演 ×516×16 × 2 帧循环 ============
local function paintCritter(c, spec, f)
local bob = f -- 第二帧整体下移 1px 形成循环
c.rect(4, 6 + bob, 8, 6 - bob, spec.body)
c.rect(5, 2 + bob, 6, 5, spec.body)
c.px(6, 4 + bob, PAL.black); c.px(9, 4 + bob, PAL.black)
c.px(7, 6 + bob, spec.snout); c.px(8, 6 + bob, spec.snout)
c.rect(4, 12, 3, 2, spec.dark); c.rect(9, 12, 3, 2, spec.dark)
if spec.extra then spec.extra(c, f) end
end
local CRITTERS = {
{ file = "extra1", body = C("e0904a"), dark = C("b87030"), snout = C("f8c890"), -- 松鼠:大尾巴
extra = function(c, f) c.rect(12, 1 + f, 3, 9, C("b87030")); c.px(13, 2 + f, C("e0904a")) end },
{ file = "extra2", body = C("b09068"), dark = C("8a6a48"), snout = C("e8cca8"), -- 刺猬:背刺
extra = function(c, f) for i = 4, 11, 2 do c.px(i, 1 + f + (i % 4 == 0 and 0 or 1), C("6e5236")) end end },
{ file = "extra3", body = C("f4e04a"), dark = C("d8b830"), snout = C("f4a030"), -- 鸭子:扁嘴
extra = function(c, f) c.px(6, 5 + f, C("f4a030")); c.px(9, 5 + f, C("f4a030")) end },
{ file = "extra4", body = C("b8b8c4"), dark = C("8e8e9c"), snout = C("e8c8c8"), -- 老鼠:圆耳
extra = function(c, f) c.rect(4, 0 + f, 2, 2, C("b8b8c4")); c.rect(10, 0 + f, 2, 2, C("b8b8c4")) end },
{ file = "extra5", body = C("7ac86a"), dark = C("58a048"), snout = C("aee89e"), -- 青蛙:凸眼
extra = function(c, f) c.px(5, 1 + f, C("7ac86a")); c.px(10, 1 + f, C("7ac86a")) end },
}
for _, spec in ipairs(CRITTERS) do
local img = Image(32, 16, ColorMode.RGB)
paintCritter(makeCtx(img, 0, 0), spec, 0)
paintCritter(makeCtx(img, 16, 0), spec, 1)
saveSheet(img, spec.file)
end
-- ============ 向日葵表盘:24×24 × 14 帧 ============
-- 帧 0..12 = 点亮花瓣数;帧 13 = 满格+花心黑籽
local function paintSunflower(c, lit, seeds)
local cx, cy = 11.5, 11.5
for k = 0, 11 do
local ang = (k / 12) * math.pi * 2 - math.pi / 2
local col = (k < lit) and PAL.yellow or C("7a7264")
local colD = (k < lit) and C("e0b830") or C("635c50")
for r = 7, 10 do
local x = math.floor(cx + math.cos(ang) * r + 0.5)
local y = math.floor(cy + math.sin(ang) * r + 0.5)
c.px(x, y, r > 9 and colD or col)
local px2 = math.floor(cx + math.cos(ang + 0.14) * r + 0.5)
local py2 = math.floor(cy + math.sin(ang + 0.14) * r + 0.5)
c.px(px2, py2, col)
end
end
-- 花心
for j = -4, 4 do for i = -4, 4 do
if i * i + j * j <= 18 then c.px(12 + i, 12 + j, C("9a6a2a")) end
end end
for j = -3, 3 do for i = -3, 3 do
if i * i + j * j <= 9 then c.px(12 + i, 12 + j, C("b8843a")) end
end end
if seeds then -- 过熟的黑籽
c.px(11, 11, PAL.black); c.px(13, 12, PAL.black); c.px(12, 14, PAL.black)
c.px(10, 13, PAL.black); c.px(14, 10, PAL.black); c.px(12, 11, C("4a3a20"))
end
end
local sunImg = Image(24 * 14, 24, ColorMode.RGB)
for f = 0, 12 do paintSunflower(makeCtx(sunImg, f * 24, 0), f, false) end
paintSunflower(makeCtx(sunImg, 13 * 24, 0), 12, true)
saveSheet(sunImg, "sunflower")
print("ALL ART GENERATED OK")