diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-28 16:33:23 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-28 16:33:23 +0800 |
| commit | afefc9b6ed4f464753c1009c6594557b07b325e9 (patch) | |
| tree | 73b704e3410af4f0fa65f807a4368d987258147d | |
| parent | cefe17a63d54762c2e59c62d5198984c39cdc896 (diff) | |
| download | living-village-afefc9b6ed4f464753c1009c6594557b07b325e9.tar.gz | |
art(p40): 石板/红灯笼新格 + 重绘乌篷船与石灯笼(672x32, 21 格)
- make-cc0-art.py: 新增 draw_paving(19)/draw_red_lantern(20);重绘 draw_boat(14/15)
放大船体 + 醒目拱形竹篷,重绘 draw_stone_lantern(16) 三段石结构强化
- 索引 0-13、17-18 逐字节不变(git show 复核);二次生成 byte-identical
- docs/素材来源.md 更新为 21 格并新增第 5 节 P40
| -rw-r--r-- | docs/素材来源.md | 35 | ||||
| -rw-r--r-- | scripts/make-cc0-art.py | 196 | ||||
| -rw-r--r-- | src/LivingVillage.Desktop/Assets/cc0-tiles.png | bin | 1523 -> 1834 bytes |
3 files changed, 173 insertions, 58 deletions
diff --git a/docs/素材来源.md b/docs/素材来源.md index 98c2538..f41afd2 100644 --- a/docs/素材来源.md +++ b/docs/素材来源.md @@ -11,13 +11,13 @@ P34 扩展到水面 / 屋顶 / 墙体 / 栏杆等构件。仍是竖直切片,� ```bash python3 scripts/make-cc0-art.py - # -> src/LivingVillage.Desktop/Assets/cc0-tiles.png 608x32 (19 个 32x32 格) + # -> src/LivingVillage.Desktop/Assets/cc0-tiles.png 672x32 (21 个 32x32 格) # -> src/LivingVillage.Desktop/Assets/cc0-characters.png 3840x48 (5 变体 x 4 向 x 6 帧, 32x48 格) ``` - `cc0-tiles.png` 前 4 格来自 Kenney(源 16x16,**整数 2x NEAREST** 到 32x32),后 15 格为 - 自绘(P34 的 8 格 + P39 的 7 格,详见第 3、4 节);角色图为 Puny 源 16x24 整数 2x 到 32x48。 - 缩放断言见 `src/LivingVillage.Desktop.Tests/AssetPackTests.fs`。 + `cc0-tiles.png` 前 4 格来自 Kenney(源 16x16,**整数 2x NEAREST** 到 32x32),后 17 格为 + 自绘(P34 的 8 格 + P39 的 7 格 + P40 的 2 格,详见第 3、4、5 节);角色图为 Puny 源 16x24 + 整数 2x 到 32x48。缩放断言见 `src/LivingVillage.Desktop.Tests/AssetPackTests.fs`。 ## 1. Kenney Tiny Farm(地块) @@ -98,6 +98,33 @@ Kenney Tiny Farm 无合适的瓦屋顶 / 白墙 / 木格窗 / 栏杆 / 水波 / - **fallback(非 cc0)模式不摆放这些新元素**,仍走原程序绘制江南图集。 +## 5. 自绘美术深化(P40,原创,无第三方来源) + +P40 深化 P39 切片:石板路衔接、乌篷船特征、石灯笼/红灯笼形态区分。图集由 19 格扩到 +**21 格(672x32)**,追加索引 19-20,并**重绘 14/15(乌篷船)与 16(石灯笼)**;其余索引 +0-13、17-18 逐字节不变。全部**原创、无外部来源**。 + +| 索引 | 内容 | 说明 | +| --- | --- | --- | +| 19(新) | 石板路格 | 石板块 + 错缝接缝 + 明暗高光,拼成连贯石板带 | +| 20(新) | 红灯笼 | 红纸圆身 + 竖向竹骨 + 暖芯 + 金穗 + 木顶盖(三段:底座/灯室/顶盖) | +| 14/15(重绘) | 乌篷船左右半 | 放大到满格、船体加高,醒目**拱形竹篷**(篷顶高拱 + 竹骨 + 脊线),桥上/岸上一眼可辨 | +| 16(重绘) | 石灯笼 | 三段石结构强化:宽石座/石室(暖芯)/外挑石顶盖 + 宝顶,与红灯笼形态明确区分 | + +- 自绘代码新增 `draw_paving` / `draw_red_lantern`,`draw_boat` / `draw_stone_lantern` 重绘;均在最终 + 1x 分辨率直接落像素(无重采样),2x NEAREST 放大无混色。 +- 生成器:`python3 scripts/make-cc0-art.py` -> `cc0-tiles.png` **672x32(21 格)**;二次运行 + byte-identical。旧索引复验:`git show HEAD:...` 比对新图前 448px(0-13)与 544-608px(17-18) + 逐字节一致。 +- 摆放规则(`src/LivingVillage.Desktop/VillageArt.fs`,纯函数,不写瓦片、不改可达性): + - **石板路(19)**:`cc0PavingTiles map` 返回「本身或四邻属于 `map.Paths`(主路/桥引道/门口) + 或 `map.Bridges`(桥面)」的石板格;cc0 模式下只有这些格铺连贯石板带,稀疏 + `DecorativeStones` 孤立散布石板改画草地,故场地上不再出现游离石板。fallback 保持原样。 + - **红灯笼(20)**:`cc0RedLanternTiles map` 在每户民居门口(门格)挂一盏,与主路石灯笼形态区分; + 并入 `nightLightTilesWith`,与石灯笼复用同一条 **P14 预乘 alpha 径向光晕**路径,不新增光照系统。 +- **fallback(非 cc0)模式不摆放新元素**,仍走原程序绘制江南图集。 + + ## 说明 - Kenney/Puny 原始 zip 均随仓库记录于 `third_party/`(`PROVENANCE.md` 为总来源记录),仓库 diff --git a/scripts/make-cc0-art.py b/scripts/make-cc0-art.py index 7c5868c..351c20a 100644 --- a/scripts/make-cc0-art.py +++ b/scripts/make-cc0-art.py @@ -227,6 +227,19 @@ CARROT = (216, 126, 54, 255) CABBAGE = (120, 168, 84, 255) CLOTH = (208, 198, 172, 255) +# --- P40 palette: paving slab, Wu Peng bamboo awning, red paper lantern ------- +PAVE = (146, 144, 136, 255) +PAVE_HI = (188, 186, 176, 255) +PAVE_DARK = (98, 97, 92, 255) +PAVE_JOINT = (72, 72, 70, 255) +BAMBOO = (44, 58, 34, 255) +BAMBOO_HI = (86, 104, 62, 255) +BAMBOO_RIB = (28, 38, 22, 255) +RED_LANTERN = (208, 58, 46, 255) +RED_LANTERN_HI = (238, 104, 84, 255) +RED_LANTERN_DARK = (150, 34, 28, 255) +GOLD = (232, 196, 96, 255) + def draw_bridge_center(c, ox): """Arch bridge span: stone deck with a railing and an arched underside. @@ -269,69 +282,139 @@ def draw_bridge_end(c, ox): def draw_boat(c, ox, right_half): - """Wu Peng boat half (2 tiles wide): dark hull + curved bamboo awning. + """Wu Peng boat half (2 tiles wide): dark hull + tall curved bamboo awning. - Drawn as two halves so a 2-tile boat reads as one continuous silhouette. - The hull floats on the water: its top edge (gunwale) is 4px down, so open - water ripple stays visible above the boat on the tile. + P40: the boat was scaled up to fill the 64x32 footprint so the wu peng (拱形 + 竹篷) reads at a glance from a bridge or bank. Each half is 32px wide; the + awning is a tall elliptical bamboo arch spanning the middle of the 2-tile + boat with visible ribs, and the hull is a solid dark band with an upturned + bow/stern tip. The gunwale sits 8px down, leaving open water ripple above. """ c.fill(ox, 0, 32, 32, CLEAR) + # --- Hull: solid dark band spanning the full cell, 8px down to 30px. --- + c.fill(ox, 8, 32, 22, BOAT_HULL) + c.fill(ox, 8, 32, 2, BOAT_HULL_DARK) + c.fill(ox, 28, 32, 2, BOAT_HULL_DARK) + # --- Awning (wu peng): tall bamboo arch over the middle of the 2-tile boat. --- + # Arc rises from the outer edge up to a high ridge, so both halves join into + # one continuous arch. Left half: low at x=0, ridge near the inner (right) edge. + arc = [ + (0, 12), (2, 9), (4, 6), (6, 4), (8, 3), + (10, 2), (12, 1), (14, 1), (16, 1), (18, 1), + (20, 1), (22, 1), (24, 2), (26, 3), (28, 4), + (30, 5), (32, 6), + ] if not right_half: - # Left half: bow rising to the left, hull tapering. - for i in range(26): - half = 3 + i // 3 - c.fill(ox + 0, 10 + i, half, 1, BOAT_HULL) - c.fill(ox, 10, 3, 1, BOAT_HULL_DARK) - c.fill(ox + 12, 9, 20, 2, BOAT_HULL_DARK) - c.fill(ox + 12, 11, 20, 13, BOAT_HULL) - # Awning (wu peng) left edge, arch top. - c.fill(ox + 12, 1, 20, 8, BOAT_ROOF) - c.fill(ox + 12, 1, 20, 2, BOAT_ROOF_HI) - c.fill(ox + 12, 1, 2, 8, BOAT_ROOF_HI) - c.fill(ox + 12, 8, 20, 2, BOAT_HULL_DARK) - # Stern post at the extreme left of the visible half. - c.fill(ox, 6, 2, 6, BOAT_HULL_DARK) + # Left half: bow tip at the far left, rising; awning covers inner 20px. + for i in range(24): + half = 4 + i // 2 + c.fill(ox, 8 + i, min(32, half), 1, BOAT_HULL) + c.fill(ox, 6, 4, 3, BOAT_HULL_DARK) + c.fill(ox, 5, 2, 3, BOAT_HULL) + for (x, top) in arc: + c.fill(ox + x, top, 1, 32 - top, BAMBOO) + c.fill(ox + 12, 1, 20, 2, BAMBOO_HI) + for rib in (14, 18, 22, 26, 30): + c.fill(ox + rib, 1, 1, 30, BAMBOO_RIB) + c.fill(ox + 12, 30, 20, 2, BAMBOO_RIB) + c.fill(ox + 12, 6, 2, 24, BAMBOO_HI) else: - # Right half: stern rising to the right, mirrored continuous hull. - for i in range(26): - half = 3 + i // 3 - c.fill(ox + 32 - half, 10 + i, half, 1, BOAT_HULL) - c.fill(ox + 29, 10, 3, 1, BOAT_HULL_DARK) - c.fill(ox, 9, 20, 2, BOAT_HULL_DARK) - c.fill(ox, 11, 20, 13, BOAT_HULL) - c.fill(ox, 1, 20, 8, BOAT_ROOF) - c.fill(ox, 1, 20, 2, BOAT_ROOF_HI) - c.fill(ox + 18, 1, 2, 8, BOAT_ROOF_HI) - c.fill(ox, 8, 20, 2, BOAT_HULL_DARK) - c.fill(ox + 30, 6, 2, 6, BOAT_HULL_DARK) + # Right half: stern tip at the far right, mirrored continuous hull. + for i in range(24): + half = 4 + i // 2 + c.fill(ox + 32 - min(32, half), 8 + i, min(32, half), 1, BOAT_HULL) + c.fill(ox + 28, 6, 4, 3, BOAT_HULL_DARK) + c.fill(ox + 30, 5, 2, 3, BOAT_HULL) + for (x, top) in arc: + c.fill(ox + 31 - x, top, 1, 32 - top, BAMBOO) + c.fill(ox, 1, 20, 2, BAMBOO_HI) + for rib in (1, 5, 9, 13, 17): + c.fill(ox + rib, 1, 1, 30, BAMBOO_RIB) + c.fill(ox, 30, 20, 2, BAMBOO_RIB) + c.fill(ox + 18, 6, 2, 24, BAMBOO_HI) + + +def draw_paving(c, ox): + """P40 stone paving slab band: a coherent, walkable-looking stone tile. + + Used only where the ground follows a traffic path / doorway / bridge approach + (see VillageArt.cc0PavingTiles); the renderer drops the isolated decorative + scatter so paving reads as a continuous band instead of lone slabs. + """ + c.fill(ox, 0, 32, 32, PAVE) + # Irregular slab joints: a few horizontal seams + staggered verticals so the + # tile tiles seamlessly into a band without a repeating checker look. + for y in (8, 20, 30): + c.hline(ox, y, 32, PAVE_JOINT) + for (x, y0, y1) in [(10, 0, 8), (22, 8, 20), (6, 20, 30), (18, 20, 30), (27, 0, 8)]: + c.vline(ox + x, y0, y1 - y0, PAVE_JOINT) + # Bright top-left highlights on a couple of slabs, dark bottom-right corners. + c.fill(ox + 1, 1, 8, 1, PAVE_HI) + c.fill(ox + 13, 9, 8, 1, PAVE_HI) + c.fill(ox + 23, 21, 8, 1, PAVE_HI) + c.set(ox + 9, 7, PAVE_DARK) + c.set(ox + 21, 19, PAVE_DARK) + c.set(ox + 31, 31, PAVE_DARK) def draw_stone_lantern(c, ox): - """Ishidoro-style stone lantern with a warm lit window (night glow anchor). + """Ishidoro-style stone lantern: three clear tiers (base / chamber / roof). - The lit pane is a bright warm core (alpha opaque) sitting inside a stone - housing, so the renderer's radial glow layer adds a real halo on top. + P40: the three tiers are drawn with distinct stone bands and outlines so the + stone lantern form is unmistakable and clearly differs from the red paper + lantern (index 20). A bright warm pane anchors the renderer's existing P14 + premultiplied-alpha radial glow (no new lighting system). + """ + c.fill(ox, 0, 32, 32, CLEAR) + # Tier 1 — base: wide plinth + a narrower pedestal drum. + c.fill(ox + 6, 27, 20, 5, LANTERN_STONE) + c.fill(ox + 6, 27, 20, 1, STONE_HI) + c.fill(ox + 6, 31, 20, 1, STONE_DARK) + c.fill(ox + 10, 23, 12, 4, LANTERN_STONE) + c.fill(ox + 10, 23, 12, 1, STONE_HI) + c.fill(ox + 13, 17, 6, 6, LANTERN_STONE) + # Tier 2 — light chamber: wide stone housing with a bright warm pane. + c.fill(ox + 5, 8, 22, 10, LANTERN_STONE) + c.fill(ox + 5, 8, 22, 1, STONE_HI) + c.fill(ox + 5, 17, 22, 1, STONE_DARK) + c.fill(ox + 8, 10, 16, 7, LANTERN_GLOW) + c.fill(ox + 10, 11, 5, 5, LANTERN_GLOW_HI) + c.vline(ox + 8, 10, 7, STONE_DARK) + c.vline(ox + 23, 10, 7, STONE_DARK) + c.vline(ox + 15, 10, 7, STONE_DARK) + # Tier 3 — roof cap: flared stone eave + finial. + c.fill(ox + 3, 5, 26, 3, STONE_DARK) + c.fill(ox + 6, 3, 20, 2, LANTERN_STONE) + c.fill(ox + 6, 3, 20, 1, STONE_HI) + c.fill(ox + 14, 0, 4, 3, LANTERN_STONE) + c.set(ox + 15, 0, STONE_HI) + + +def draw_red_lantern(c, ox): + """P40 red paper lantern: base / lit chamber / roof in the same three tiers. + + A round red paper body with a golden tassel base and a dark wood cap; the + warm core reuses the same renderer glow path as the stone lantern, but the + silhouette (round red body vs grey stone housing) keeps the two distinct. """ c.fill(ox, 0, 32, 32, CLEAR) - # Base plinth. - c.fill(ox + 8, 27, 16, 5, LANTERN_STONE) - c.fill(ox + 8, 27, 16, 1, STONE_HI) - c.fill(ox + 11, 24, 10, 4, LANTERN_STONE) - # Shaft. - c.fill(ox + 14, 18, 4, 7, LANTERN_STONE) - # Light housing with a bright warm pane. - c.fill(ox + 6, 9, 20, 10, LANTERN_STONE) - c.fill(ox + 6, 9, 20, 1, STONE_HI) - c.fill(ox + 9, 11, 14, 7, LANTERN_GLOW) - c.fill(ox + 11, 12, 4, 5, LANTERN_GLOW_HI) - # Dark frame over the pane. - c.vline(ox + 9, 11, 7, STONE_DARK) - c.vline(ox + 22, 11, 7, STONE_DARK) - c.vline(ox + 15, 11, 7, STONE_DARK) - # Cap + finial. - c.fill(ox + 4, 6, 24, 4, STONE_DARK) - c.fill(ox + 6, 5, 20, 2, LANTERN_STONE) - c.fill(ox + 15, 1, 3, 4, LANTERN_STONE) + # Tier 1 — base: small wooden foot + short golden tassel (bottom rows stay + # clear so the door tile underneath is still readable when hung at a door). + c.fill(ox + 12, 22, 8, 2, WOOD_DARK) + c.fill(ox + 14, 19, 4, 3, GOLD) + c.fill(ox + 15, 17, 2, 2, GOLD) + # Tier 2 — paper body: round red lantern with vertical ribs and a lit core. + c.fill(ox + 7, 8, 18, 12, RED_LANTERN) + c.fill(ox + 6, 11, 20, 6, RED_LANTERN) + c.fill(ox + 9, 10, 14, 8, RED_LANTERN_HI) + c.fill(ox + 12, 12, 8, 5, (255, 226, 168, 255)) + for rib in (10, 14, 18, 22): + c.vline(ox + rib, 9, 10, RED_LANTERN_DARK) + c.fill(ox + 7, 19, 18, 1, RED_LANTERN_DARK) + # Tier 3 — roof: dark wooden cap + finial loop. + c.fill(ox + 9, 6, 14, 2, WOOD_DARK) + c.fill(ox + 12, 4, 8, 2, WOOD) + c.fill(ox + 15, 1, 2, 3, WOOD_DARK) def draw_stall(c, ox): @@ -393,8 +476,10 @@ def build_cc0_tile_atlas(): P34 block so every older index stays unchanged and backwards compatible: 12 bridge centre / 13 bridge end / 14 boat left / 15 boat right 16 stone lantern / 17 market stall / 18 reeds + P40 appends indices 19-20 (paving slab / red lantern): + 19 paving slab / 20 red lantern """ - c = Canvas(608, 32) + c = Canvas(672, 32) packed = Image.open(THIRD / "kenney_tiny-farm" / "Tilemap" / "tilemap_packed.png").convert("RGBA") def tile(grid_x, grid_y): @@ -420,6 +505,9 @@ def build_cc0_tile_atlas(): draw_stone_lantern(c, 16 * 32) draw_stall(c, 17 * 32) draw_reeds(c, 18 * 32) + # P40 appended block. + draw_paving(c, 19 * 32) + draw_red_lantern(c, 20 * 32) return c @@ -474,7 +562,7 @@ def build_cc0_character_atlas(): def main(): ASSETS.mkdir(parents=True, exist_ok=True) targets = [ - ("cc0-tiles.png", build_cc0_tile_atlas(), 608, 32), + ("cc0-tiles.png", build_cc0_tile_atlas(), 672, 32), ("cc0-characters.png", build_cc0_character_atlas(), 3840, 48), ] for name, canvas, w, h in targets: diff --git a/src/LivingVillage.Desktop/Assets/cc0-tiles.png b/src/LivingVillage.Desktop/Assets/cc0-tiles.png Binary files differindex b8b808a..d26e8b3 100644 --- a/src/LivingVillage.Desktop/Assets/cc0-tiles.png +++ b/src/LivingVillage.Desktop/Assets/cc0-tiles.png |
