From fd429fffb9328515beb120da2807faf5305a6e0c Mon Sep 17 00:00:00 2001 From: "Somhairle H. Marisol" Date: Mon, 28 Sep 2026 08:42:59 +0800 Subject: feat(art): P34 素材扩展切片(水面/屋顶/墙体/栏杆) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - cc0-tiles.png 由 4 格扩到 12 格:0-3 仍 Kenney 地面,4-11 为自绘构件 (4 水A/5 水B/6 屋脊/7 屋檐/8 木构墙/9 木格窗/10 栏杆/11 门) - 自绘 2 相位水面 cc0WaterTileIndexAt=(tick/32+x+2y)%2,单帧覆盖双相位; 河道与水田/水网共用;江南 fallback 不变 - drawWorld 接入:cc0 模式下民居屋顶/墙/窗/门、桥栏杆、水面走真素材,其余道具仍江南 - AssetPackTests 更新图集尺寸(12 格)+整数缩放,新增构件索引与双水相位用例 - docs/素材来源.md 记录自绘清单;同步 assets-and-licenses 与维护说明 P34 小节 - docs/evidence/p34-*.png 昼/夜/2x 特写/水波/对照(check-no-white-blocks 0 命中) --- scripts/make-cc0-art.py | 141 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 127 insertions(+), 14 deletions(-) (limited to 'scripts') diff --git a/scripts/make-cc0-art.py b/scripts/make-cc0-art.py index b26cba1..44bd013 100644 --- a/scripts/make-cc0-art.py +++ b/scripts/make-cc0-art.py @@ -1,12 +1,18 @@ #!/usr/bin/env python3 """CC0 asset pack integration (third_party -> game atlases). -Reads the two provenanced CC0 packs in third_party/ (Kenney Tiny Farm tiles, -Puny Characters) and emits Deterministic CC0 Atlases for the prototype: +Reads the provenanced CC0 packs in third_party/ (Kenney Tiny Farm tiles, Puny +Characters) and emits deterministic atlases for the prototype: - src/LivingVillage.Desktop/Assets/cc0-tiles.png 128x32 (4 ground tiles, 32px each) + src/LivingVillage.Desktop/Assets/cc0-tiles.png 384x32 (12 tiles, 32px each) src/LivingVillage.Desktop/Assets/cc0-characters.png 3840x48 (5 variants x 4 directions x 6 frames, 32x48 cells) +Ground tiles (indices 0-3) come from Kenney Tiny Farm via integer 2x NEAREST of +the 16px source. Kenney Tiny Farm ships no water / roof / wall / railing / door +art (it is a farm pack), so indices 4-11 are *self-drawn* 32px pixel art in a +consistent Jiangnan palette (white plaster, dark slate tile, warm wood). Each +tile's provenance is recorded in docs/素材来源.md. + Mapping strategy (16 -> 32): integer 2x nearest-neighbour upscale of every source sprite; ground tiles fill the 32x32 cell exactly; Puny sprites keep their 16x24-ish content band (2x -> 32x48) anchored to the cell bottom. @@ -40,6 +46,19 @@ class Canvas: def get(self, x, y): return self.pixels[y][x] + def fill(self, x, y, w, h, color): + for yy in range(y, y + h): + for xx in range(x, x + w): + self.set(xx, yy, color) + + def vline(self, x, y, h, color): + for yy in range(y, y + h): + self.set(x, yy, color) + + def hline(self, x, y, w, color): + for xx in range(x, x + w): + self.set(xx, y, color) + def blit(self, img, ox, oy, scale=2): px = img.load() for y in range(img.height): @@ -95,16 +114,102 @@ def verify_png(path, expected_w, expected_h): print(f"ok {path.name} {width}x{height} bytes={len(data)}") -def build_cc0_tile_atlas(): - """Four 32x32 ground tiles from Kenney Tiny Farm, upscaled 2x. +# --- self-drawn Jiangnan structure palette (indices 4-11) --------------------- +WATER_DEEP = (36, 84, 138, 255) +WATER_MID = (54, 110, 170, 255) +WATER_HI = (104, 158, 210, 255) +SLATE = (52, 58, 72, 255) +SLATE_HI = (96, 104, 122, 255) +SLATE_DARK = (33, 37, 47, 255) +PLASTER = (234, 228, 212, 255) +PLASTER_SH = (204, 197, 181, 255) +WOOD = (112, 74, 46, 255) +WOOD_DARK = (74, 47, 28, 255) +LATTICE = (52, 34, 22, 255) +CLEAR = (0, 0, 0, 0) + + +def draw_water(c, ox, phase): + """One 32x32 water tile; `phase` in {0,1} shifts the wave crests by 4px.""" + c.fill(ox, 0, 32, 32, WATER_MID) + shift = phase * 4 + for yy in (6, 10, 20, 26): + for x in range(32): + if ((x + shift) // 4) % 2 == 0: + c.set(ox + x, yy, WATER_HI) + for yy in (4, 8, 18, 24): + for x in range(32): + if ((x + shift + 2) // 4) % 2 == 0: + c.set(ox + x, yy, WATER_DEEP) + + +def draw_roof_ridge(c, ox): + c.fill(ox, 0, 32, 32, SLATE) + c.fill(ox, 6, 32, 4, SLATE_HI) + for x in range(0, 32, 4): + c.vline(ox + x, 0, 6, SLATE_DARK) + c.vline(ox + x + 2, 10, 22, SLATE_DARK) + c.fill(ox, 0, 32, 2, SLATE_DARK) + + +def draw_roof_eave(c, ox): + c.fill(ox, 0, 32, 32, SLATE) + for x in range(0, 32, 4): + c.vline(ox + x, 0, 22, SLATE_DARK) + c.fill(ox, 22, 32, 3, SLATE_HI) + c.fill(ox, 25, 32, 3, SLATE_DARK) + for x in range(0, 32, 4): + c.set(ox + x + 3, 28, SLATE_HI) + + +def draw_wall(c, ox, window): + c.fill(ox, 0, 32, 32, PLASTER) + c.fill(ox, 0, 32, 4, WOOD) + c.fill(ox, 28, 32, 4, WOOD) + c.vline(ox, 0, 32, WOOD) + c.vline(ox + 31, 0, 32, WOOD) + if window: + c.fill(ox + 6, 6, 20, 20, WOOD_DARK) + c.fill(ox + 8, 8, 16, 16, LATTICE) + for i in range(8, 24, 4): + c.vline(ox + i, 8, 16, WOOD_DARK) + c.hline(ox + 8, i, 16, WOOD_DARK) + else: + c.fill(ox + 6, 12, 20, 2, PLASTER_SH) + c.fill(ox + 6, 18, 20, 2, PLASTER_SH) + + +def draw_railing(c, ox): + c.fill(ox, 0, 32, 32, CLEAR) + c.fill(ox, 6, 32, 3, WOOD) + c.fill(ox, 16, 32, 3, WOOD) + c.fill(ox, 2, 32, 2, WOOD_DARK) + for px in (2, 15, 28): + c.fill(ox + px, 2, 3, 26, WOOD) + c.set(ox + px + 1, 28, WOOD_DARK) + + +def draw_door(c, ox): + c.fill(ox, 0, 32, 32, CLEAR) + c.fill(ox + 4, 2, 24, 30, WOOD_DARK) + c.fill(ox + 7, 5, 18, 27, WOOD) + c.fill(ox + 14, 5, 4, 27, WOOD_DARK) + c.fill(ox + 20, 17, 3, 3, (222, 190, 120, 255)) + - Source is the *packed* sheet (tilemap_packed.png) which has NO spacing between - tiles: 192x176 = 12x11 tiles at a 16px pitch (the spaced tilemap.png is 203x186 - at 17px pitch; mixing the two misaligns every non-zero cell). Tile selection is - fixed by grid coordinates: two full grass shades (9,8)/(10,8) for the checker - ground, then two grey stone slabs (4,8)/(4,9) for the paved path. +def build_cc0_tile_atlas(): + """Twelve 32x32 tiles: 4 Kenney ground + 8 self-drawn structure tiles. + + Ground source is the *packed* sheet (tilemap_packed.png) which has NO spacing + between tiles: 192x176 = 12x11 tiles at a 16px pitch (the spaced tilemap.png + is 203x186 at 17px pitch; mixing the two misaligns every non-zero cell). Grid + coordinates: two full grass shades (9,8)/(10,8) then two grey stone slabs + (4,8)/(4,9). Indices 4-11 are self-drawn (Kenney Tiny Farm has no water, roof, + wall, railing or door tiles), layout fixed by the P34 atlas contract: + 4 water A / 5 water B / 6 roof ridge / 7 roof eave + 8 wall wood / 9 wall window / 10 railing / 11 door """ - c = Canvas(128, 32) + c = Canvas(384, 32) packed = Image.open(THIRD / "kenney_tiny-farm" / "Tilemap" / "tilemap_packed.png").convert("RGBA") def tile(grid_x, grid_y): @@ -112,8 +217,16 @@ def build_cc0_tile_atlas(): choices = [(9, 8), (10, 8), (4, 8), (4, 9)] for index, (gx, gy) in enumerate(choices): - t = tile(gx, gy) - c.blit(t, index * 32, 0, scale=2) + c.blit(tile(gx, gy), index * 32, 0, scale=2) + + draw_water(c, 4 * 32, 0) + draw_water(c, 5 * 32, 1) + draw_roof_ridge(c, 6 * 32) + draw_roof_eave(c, 7 * 32) + draw_wall(c, 8 * 32, window=False) + draw_wall(c, 9 * 32, window=True) + draw_railing(c, 10 * 32) + draw_door(c, 11 * 32) return c @@ -168,7 +281,7 @@ def build_cc0_character_atlas(): def main(): ASSETS.mkdir(parents=True, exist_ok=True) targets = [ - ("cc0-tiles.png", build_cc0_tile_atlas(), 128, 32), + ("cc0-tiles.png", build_cc0_tile_atlas(), 384, 32), ("cc0-characters.png", build_cc0_character_atlas(), 3840, 48), ] for name, canvas, w, h in targets: -- cgit v1.2.3