summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-28 16:33:23 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-28 16:33:23 +0800
commitafefc9b6ed4f464753c1009c6594557b07b325e9 (patch)
tree73b704e3410af4f0fa65f807a4368d987258147d
parentcefe17a63d54762c2e59c62d5198984c39cdc896 (diff)
downloadliving-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/素材来源.md35
-rw-r--r--scripts/make-cc0-art.py196
-rw-r--r--src/LivingVillage.Desktop/Assets/cc0-tiles.pngbin1523 -> 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
index b8b808a..d26e8b3 100644
--- a/src/LivingVillage.Desktop/Assets/cc0-tiles.png
+++ b/src/LivingVillage.Desktop/Assets/cc0-tiles.png
Binary files differ