summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rw-r--r--scripts/make-cc0-art.py141
1 files changed, 127 insertions, 14 deletions
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: