From 326575888b9c8edf0ded1dcf1c01f5c78d3eca84 Mon Sep 17 00:00:00 2001 From: "Somhairle H. Marisol" Date: Mon, 21 Sep 2026 11:12:52 +0800 Subject: feat(desktop): 河畔植被与中式道具点缀 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 图集新增柳树(垂枝两段)、灌木、花丛、芦苇、石凳、灯笼七类图块并经复制规则进构建;场景加入两株垂柳、两丛灌木、两处花丛、水中两簇芦苇、河畔石凳与两盏岸边灯笼,芦苇立于水面、灯笼立于岸道,全部入图集无程序几何占位。 --- scripts/make-jiangnan-art.py | 126 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 123 insertions(+), 3 deletions(-) (limited to 'scripts/make-jiangnan-art.py') diff --git a/scripts/make-jiangnan-art.py b/scripts/make-jiangnan-art.py index 26db3fc..616928e 100644 --- a/scripts/make-jiangnan-art.py +++ b/scripts/make-jiangnan-art.py @@ -2,7 +2,7 @@ """Deterministic pixel-art atlases for LivingVillage.Desktop. Generates three RGBA PNGs into src/LivingVillage.Desktop/Assets: - jiangnan-world.png 448x32 (14 ground/prop tiles, 32px each) + jiangnan-world.png 672x32 (21 ground/prop tiles, 32px each) jiangnan-characters.png 1024x48 (4 variants x 4 directions x 2 frames, 32x48 cells) jiangnan-interior.png 392x96 (variable-size bottom-aligned interior cells) @@ -99,7 +99,7 @@ def verify_png(path, expected_w, expected_h): def build_world_atlas(): - c = Canvas(448, 32) + c = Canvas(672, 32) tile = 32 def slot(index): @@ -310,6 +310,126 @@ def build_world_atlas(): c.set(x0 + sx, sy, (214, 231, 238, 255)) c.set(x0 + sx + 1, sy, (214, 231, 238, 120)) + willow_leaf = (86, 138, 74, 255) + willow_leaf_light = (128, 172, 96, 255) + willow_leaf_dark = (62, 104, 58, 255) + trunk = (98, 64, 44, 255) + trunk_light = (132, 92, 60, 255) + + x0 = slot(14) + for cx, cy, rx, ry in ((16, 12, 13, 10), (8, 8, 7, 7), (24, 8, 7, 7), (16, 6, 6, 6)): + for py in range(cy - ry, cy + ry): + for px in range(cx - rx, cx + rx): + if ((px - cx) * (px - cx)) * 2 + ((py - cy) * (py - cy)) * 3 <= rx * ry: + shade = willow_leaf + if (px * 5 + py * 3) % 11 == 0: + shade = willow_leaf_light + elif (px * 3 + py * 7) % 13 == 0: + shade = willow_leaf_dark + c.set(x0 + px, py, shade) + for dx, dy in ((-9, 12), (-5, 16), (0, 19), (6, 16), (10, 12), (-2, 10), (3, 10)): + for drop in range(9): + c.set(x0 + 16 + dx, 18 + dy // 2 + drop, willow_leaf_dark if drop % 3 else willow_leaf_light) + c.rect(x0 + 14, 24, 4, 8, trunk) + c.rect(x0 + 14, 24, 1, 8, trunk_light) + for bx, by in ((10, 18), (20, 16)): + c.rect(x0 + bx, by, 3, 2, willow_leaf_dark) + + x0 = slot(15) + c.rect(x0 + 13, 8, 6, 20, trunk) + c.rect(x0 + 13, 8, 2, 20, trunk_light) + for bx, by in ((8, 22), (21, 19), (6, 26), (24, 25)): + c.rect(x0 + bx, by, 4, 2, trunk) + for ex, ey in ((-10, 12), (-6, 8), (-2, 6), (3, 7), (8, 11), (11, 15)): + for drop in range(14): + yy = ey + drop + if yy < 30: + c.set(x0 + 16 + ex, yy, willow_leaf_dark if drop % 3 else willow_leaf) + if drop % 4 == 1: + c.set(x0 + 17 + ex, yy, willow_leaf_light) + c.outline(x0, 0, 32, 32) + + shrub = (74, 118, 64, 255) + shrub_light = (108, 152, 84, 255) + shrub_dark = (56, 92, 54, 255) + x0 = slot(16) + for cx, cy, rx, ry in ((11, 22, 9, 8), (22, 24, 8, 7), (16, 17, 7, 6)): + for py in range(cy - ry, cy + ry): + for px in range(cx - rx, cx + rx): + if ((px - cx) * (px - cx)) + ((py - cy) * (py - cy)) * 2 <= rx * ry: + shade = shrub + if (px * 7 + py * 5) % 9 == 0: + shade = shrub_light + elif (px * 5 + py * 3) % 7 == 0: + shade = shrub_dark + c.set(x0 + px, py, shade) + for fx, fy in ((9, 17), (17, 14), (24, 21), (13, 24), (20, 27)): + c.set(x0 + fx, fy, sprout_light) + c.outline(x0, 0, 32, 32) + + x0 = slot(17) + flower_red = (196, 92, 96, 255) + flower_yellow = (222, 188, 92, 255) + flower_white = (232, 228, 214, 255) + flower_stem = (74, 118, 64, 255) + for fx, fy, fc in ((7, 22, flower_red), (12, 18, flower_yellow), (17, 23, flower_white), + (22, 19, flower_red), (26, 24, flower_yellow), (10, 27, flower_white), + (19, 27, flower_red)): + c.rect(x0 + fx, fy + 2, 1, 4, flower_stem) + c.rect(x0 + fx - 1, fy, 3, 3, fc) + c.set(x0 + fx, fy + 1, (150, 62, 58, 255) if fc == flower_red else (172, 132, 52, 255)) + for gx, gy in ((5, 25), (14, 21), (24, 26), (20, 15)): + c.rect(x0 + gx, gy, 2, 1, flower_stem) + c.outline(x0, 0, 32, 32) + + reed = (104, 142, 82, 255) + reed_light = (146, 178, 110, 255) + reed_seed = (172, 140, 92, 255) + x0 = slot(18) + for rx, height in ((6, 18), (11, 24), (15, 20), (20, 26), (25, 17)): + for drop in range(height): + yy = 31 - drop + sway = (drop // 5) if rx % 2 else -(drop // 5) + c.set(x0 + rx + sway, yy, reed if drop % 3 else reed_light) + c.rect(x0 + rx - 1, 31 - height, 3, 3, reed_seed) + c.rect(x0 + rx, 31 - height - 2, 1, 2, reed_light) + c.outline(x0, 0, 32, 32) + + stone = (150, 150, 146, 255) + stone_light = (178, 178, 172, 255) + stone_dark = (118, 118, 114, 255) + x0 = slot(19) + c.rect(x0 + 4, 18, 24, 7, stone) + c.rect(x0 + 4, 18, 24, 1, stone_light) + c.rect(x0 + 4, 24, 24, 1, stone_dark) + for sx in (10, 17, 24): + c.rect(x0 + sx, 19, 1, 5, stone_dark) + for lx in (7, 23): + c.rect(x0 + lx, 14, 3, 4, stone) + c.rect(x0 + lx, 14, 3, 1, stone_light) + c.rect(x0 + lx - 1, 17, 5, 2, stone_dark) + c.rect(x0 + 6, 25, 20, 4, stone_dark) + c.rect(x0 + 7, 29, 18, 2, (96, 96, 92, 255)) + c.outline(x0, 0, 32, 32) + + lantern_paper = (212, 92, 74, 255) + lantern_paper_light = (238, 140, 104, 255) + lantern_rib = (150, 54, 46, 255) + x0 = slot(20) + c.rect(x0 + 15, 4, 2, 22, wood_dark) + c.rect(x0 + 12, 2, 8, 3, wood) + c.rect(x0 + 12, 2, 8, 1, wood_light) + c.rect(x0 + 10, 8, 12, 12, lantern_paper) + c.rect(x0 + 10, 8, 12, 1, lantern_paper_light) + c.rect(x0 + 10, 19, 12, 1, lantern_rib) + for rx in (12, 15, 18): + c.rect(x0 + rx, 9, 1, 10, lantern_rib) + c.rect(x0 + 13, 12, 6, 4, (252, 216, 148, 255)) + c.rect(x0 + 11, 20, 10, 2, wood) + c.rect(x0 + 13, 22, 6, 2, (252, 216, 148, 255)) + c.rect(x0 + 14, 25, 4, 3, lantern_rib) + c.outline(x0, 0, 32, 32) + return c @@ -529,7 +649,7 @@ def build_interior_atlas(): def main(): ASSET_DIR.mkdir(parents=True, exist_ok=True) targets = [ - ("jiangnan-world.png", build_world_atlas(), 448, 32), + ("jiangnan-world.png", build_world_atlas(), 672, 32), ("jiangnan-characters.png", build_character_atlas(), 1024, 48), ("jiangnan-interior.png", build_interior_atlas(), 392, 96), ] -- cgit v1.2.3