summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/素材来源.md41
-rw-r--r--scripts/make-cc0-art.py201
-rw-r--r--src/LivingVillage.Desktop/Assets/cc0-tiles.pngbin816 -> 1523 bytes
3 files changed, 233 insertions, 9 deletions
diff --git a/docs/素材来源.md b/docs/素材来源.md
index 31494e9..98c2538 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 384x32 (12 个 32x32 格)
+ # -> src/LivingVillage.Desktop/Assets/cc0-tiles.png 608x32 (19 个 32x32 格)
# -> src/LivingVillage.Desktop/Assets/cc0-characters.png 3840x48 (5 变体 x 4 向 x 6 帧, 32x48 格)
```
- `cc0-tiles.png` 前 4 格来自 Kenney(源 16x16,**整数 2x NEAREST** 到 32x32),后 8 格为
- P34 自绘(详见第 3 节);角色图为 Puny 源 16x24 整数 2x 到 32x48。缩放断言见
- `src/LivingVillage.Desktop.Tests/AssetPackTests.fs`。
+ `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`。
## 1. Kenney Tiny Farm(地块)
@@ -67,10 +67,41 @@ Kenney Tiny Farm 无合适的瓦屋顶 / 白墙 / 木格窗 / 栏杆 / 水波 /
确定性、可复现,无外部素材。
- 水网/水田(生成器 `code=4`)与样例河道共用同一自绘 2 相位水面。
+## 4. 自绘中式元素补绘(P39,原创,无第三方来源)
+
+在不引入新付费素材的前提下扩展 cc0 图集自绘部分,让江南味道更足:追加 7 个 32px 自绘格到
+`cc0-tiles.png` **尾部(索引 12-18)**,**旧格 0-11 索引与像素逐字节不变**(向后兼容)。与 P34
+同一江南调色板(青灰瓦、白灰墙、暖木构件、碧水),全部**原创、无外部来源、无需第三方许可**。
+逐格:
+
+| 索引 | 内容 | 说明 |
+| --- | --- | --- |
+| 12 | 石桥中段 | 拱桥石桥面 + 木栏杆,下层留透明拱洞露出水波 |
+| 13 | 石桥桥台 | 岸边石阶(上桥台阶)+ 端柱 |
+| 14 | 乌篷船(左半) | 深色船体 + 竹篷篷顶;浮在水面画法(船帮以下留水面波纹) |
+| 15 | 乌篷船(右半) | 与左半连续成 2 格整船 |
+| 16 | 石灯笼 | 石座/石身 + 暖色发光窗芯(夜帧接光晕) |
+| 17 | 路摊/菜摊 | 红白条纹棚 + 柜台 + 果蔬筐 |
+| 18 | 芦苇 | 多株茎秆 + 叶 + 穗,贴水岸格 |
+
+- 自绘代码位于 `scripts/make-cc0-art.py`(`draw_bridge_center`/`draw_bridge_end`/`draw_boat`/
+ `draw_stone_lantern`/`draw_stall`/`draw_reeds`),确定性、可复现,无外部素材;全部在最终 1x
+ 分辨率下直接落像素(无重采样),故 2x NEAREST 放大后无混色。
+- 生成器:`python3 scripts/make-cc0-art.py` -> `cc0-tiles.png` **608x32(19 格)**;
+ 二次运行 byte-identical。
+- 摆放规则(`src/LivingVillage.Desktop/VillageArt.fs`,均为 (map, seed) 纯函数,不写瓦片、不改可达性):
+ - 桥(12/13):每座桥列的最上一格铺中段、再上一格铺桥台——桥跨河,桥台贴岸。
+ - 船(14/15):开阔水面泊 2 格船,优先靠近某座桥(桥/船/苇常同框),距离平局按 hash 裁决。
+ - 石灯笼(16):沿主路中心线每隔 12 列一盏;并入 `nightLightTiles`,复用 P29/P32 夜晚光晕路径。
+ - 路摊(17):主路灯笼之间(错开 6 列)一格,仅美术,不接交互逻辑。
+ - 芦苇(18):陆地格且上下邻格为水(贴岸),每 4 列一格。
+ - **fallback(非 cc0)模式不摆放这些新元素**,仍走原程序绘制江南图集。
+
+
## 说明
- Kenney/Puny 原始 zip 均随仓库记录于 `third_party/`(`PROVENANCE.md` 为总来源记录),仓库
分发的只是上述再生成图集,不声称拥有未包含素材;自绘构件为原创。
- 版权/许可复核:见 `third_party/` 内原始文件与发布页;重跑 `make-cc0-art.py` 前后对
`third_party/*.zip` 重新 `sha256sum` 校验。
-- 范围仍为切片:动画全套 / 全村 / 更多中式元素补绘留待后续。
+- 范围仍为切片:动画全套 / 全村 / 更多中式元素补绘留待后续。 \ No newline at end of file
diff --git a/scripts/make-cc0-art.py b/scripts/make-cc0-art.py
index 44bd013..7c5868c 100644
--- a/scripts/make-cc0-art.py
+++ b/scripts/make-cc0-art.py
@@ -4,7 +4,7 @@
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 384x32 (12 tiles, 32px each)
+ src/LivingVillage.Desktop/Assets/cc0-tiles.png 608x32 (19 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
@@ -13,6 +13,14 @@ 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.
+P39 (indices 12-18) appends more self-drawn Jiangnan elements after the P34
+block, keeping every older index byte-for-byte backwards compatible:
+ 12 bridge centre / 13 bridge end / 14 boat left / 15 boat right
+ 16 stone lantern / 17 market stall / 18 reeds
+Bridge/boat/lantern/stall/reeds are all self-drawn (Kenney Tiny Farm has none
+of them), same palette, same 32px grid, NEAREST-safe (no resampling: drawn at
+final 1x resolution, so pixel edges are exact).
+
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.
@@ -197,8 +205,181 @@ def draw_door(c, ox):
c.fill(ox + 20, 17, 3, 3, (222, 190, 120, 255))
+# --- P39 self-drawn Jiangnan elements (indices 12-18) -------------------------
+STONE = (150, 150, 142, 255)
+STONE_DARK = (104, 106, 102, 255)
+STONE_HI = (196, 196, 188, 255)
+BRIDGE_DECK = (128, 128, 122, 255)
+BOAT_HULL = (58, 48, 40, 255)
+BOAT_HULL_DARK = (38, 32, 27, 255)
+BOAT_ROOF = (46, 54, 42, 255)
+BOAT_ROOF_HI = (76, 88, 66, 255)
+LANTERN_PAPER = (228, 196, 128, 255)
+LANTERN_GLOW = (255, 208, 150, 255)
+LANTERN_GLOW_HI = (255, 232, 190, 255)
+LANTERN_STONE = (168, 166, 156, 255)
+REED_STEM = (74, 104, 60, 255)
+REED_LEAF = (92, 130, 72, 255)
+REED_TIP = (146, 158, 96, 255)
+STALL_AWNING = (176, 74, 58, 255)
+STALL_AWNING_2 = (196, 176, 132, 255)
+CARROT = (216, 126, 54, 255)
+CABBAGE = (120, 168, 84, 255)
+CLOTH = (208, 198, 172, 255)
+
+
+def draw_bridge_center(c, ox):
+ """Arch bridge span: stone deck with a railing and an arched underside.
+
+ The lower third is transparent so the water ripple still shows through the
+ arch; a warm railing sits on top of the deck.
+ """
+ c.fill(ox, 0, 32, 32, CLEAR)
+ # Deck (single stone span, bright top edge / dark underside).
+ c.fill(ox, 10, 32, 9, BRIDGE_DECK)
+ c.fill(ox, 10, 32, 2, STONE_HI)
+ c.fill(ox, 17, 32, 2, STONE_DARK)
+ # Arch cut-outs under the deck (transparent so water shows through).
+ c.fill(ox + 6, 19, 8, 13, CLEAR)
+ c.fill(ox + 18, 19, 8, 13, CLEAR)
+ c.fill(ox + 10, 19, 12, 5, BRIDGE_DECK)
+ # Wood railing above the deck.
+ c.fill(ox, 4, 32, 2, WOOD_DARK)
+ c.fill(ox, 7, 32, 2, WOOD)
+ for px in (3, 14, 25):
+ c.fill(ox + px, 4, 2, 6, WOOD)
+ c.set(ox + px, 9, WOOD_DARK)
+
+
+def draw_bridge_end(c, ox):
+ """Bridge end: stone abutment at the bank plus the last railing posts."""
+ c.fill(ox, 0, 32, 32, CLEAR)
+ # Stepped abutment: two stone steps widening down to the bank.
+ c.fill(ox + 4, 12, 24, 6, STONE)
+ c.fill(ox + 4, 12, 24, 2, STONE_HI)
+ c.fill(ox, 18, 32, 14, STONE)
+ c.fill(ox, 18, 32, 2, STONE_HI)
+ c.fill(ox, 29, 32, 3, STONE_DARK)
+ # End posts of the railing.
+ c.fill(ox + 2, 3, 3, 11, WOOD_DARK)
+ c.fill(ox + 2, 3, 3, 2, WOOD)
+ c.fill(ox + 27, 3, 3, 11, WOOD_DARK)
+ c.fill(ox + 27, 3, 3, 2, WOOD)
+ c.fill(ox + 2, 6, 28, 2, WOOD)
+
+
+def draw_boat(c, ox, right_half):
+ """Wu Peng boat half (2 tiles wide): dark hull + 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.
+ """
+ c.fill(ox, 0, 32, 32, CLEAR)
+ 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)
+ 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)
+
+
+def draw_stone_lantern(c, ox):
+ """Ishidoro-style stone lantern with a warm lit window (night glow anchor).
+
+ 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.
+ """
+ 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)
+
+
+def draw_stall(c, ox):
+ """Roadside market stall: striped awning + counter + produce baskets."""
+ c.fill(ox, 0, 32, 32, CLEAR)
+ # Awning: red/cream stripes with a scalloped lower edge.
+ for x in range(0, 32, 4):
+ stripe = STALL_AWNING if (x // 4) % 2 == 0 else STALL_AWNING_2
+ c.fill(ox + x, 6, 4, 8, stripe)
+ c.fill(ox, 13, 32, 2, WOOD_DARK)
+ for x in range(0, 32, 8):
+ c.fill(ox + x, 15, 8, 2, STALL_AWNING_2)
+ c.set(ox + x + 4, 17, WOOD_DARK)
+ # Support poles.
+ c.vline(ox + 2, 15, 13, WOOD_DARK)
+ c.vline(ox + 28, 15, 13, WOOD_DARK)
+ # Counter.
+ c.fill(ox + 2, 20, 28, 4, WOOD)
+ c.fill(ox + 2, 20, 28, 1, WOOD_DARK)
+ # Produce on the counter.
+ c.fill(ox + 6, 17, 4, 3, CARROT)
+ c.fill(ox + 7, 16, 2, 1, REED_LEAF)
+ c.fill(ox + 14, 16, 6, 4, CABBAGE)
+ c.fill(ox + 15, 15, 4, 1, REED_LEAF)
+ c.fill(ox + 24, 17, 4, 3, CLOTH)
+ # Base.
+ c.fill(ox + 2, 24, 28, 4, WOOD_DARK)
+
+
+def draw_reeds(c, ox):
+ """Waterside reeds: a few stems with leaf blades and seed tips."""
+ c.fill(ox, 0, 32, 32, CLEAR)
+ stems = [(6, 6, 24), (13, 10, 22), (21, 7, 23), (26, 13, 20)]
+ for (x, top, bottom) in stems:
+ c.fill(ox + x, top, 2, bottom - top, REED_STEM)
+ c.fill(ox + x, top, 2, 3, REED_TIP)
+ # Leaf blades angling off the stem.
+ c.fill(ox + x - 4, top + 5, 5, 1, REED_LEAF)
+ c.fill(ox + x - 3, top + 6, 4, 1, REED_LEAF)
+ c.fill(ox + x + 2, top + 9, 4, 1, REED_LEAF)
+ # Dense root cluster at the waterline (bottom of the tile).
+ c.fill(ox + 3, 28, 26, 4, REED_LEAF)
+ c.fill(ox + 8, 30, 16, 2, REED_STEM)
+
+
+
def build_cc0_tile_atlas():
- """Twelve 32x32 tiles: 4 Kenney ground + 8 self-drawn structure tiles.
+ """Nineteen 32x32 tiles: 4 Kenney ground + 15 self-drawn Jiangnan 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
@@ -208,8 +389,12 @@ def build_cc0_tile_atlas():
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
+ P39 appends indices 12-18 (bridge / boat / lantern / stall / reeds) after the
+ 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
"""
- c = Canvas(384, 32)
+ c = Canvas(608, 32)
packed = Image.open(THIRD / "kenney_tiny-farm" / "Tilemap" / "tilemap_packed.png").convert("RGBA")
def tile(grid_x, grid_y):
@@ -227,6 +412,14 @@ def build_cc0_tile_atlas():
draw_wall(c, 9 * 32, window=True)
draw_railing(c, 10 * 32)
draw_door(c, 11 * 32)
+ # P39 appended block.
+ draw_bridge_center(c, 12 * 32)
+ draw_bridge_end(c, 13 * 32)
+ draw_boat(c, 14 * 32, right_half=False)
+ draw_boat(c, 15 * 32, right_half=True)
+ draw_stone_lantern(c, 16 * 32)
+ draw_stall(c, 17 * 32)
+ draw_reeds(c, 18 * 32)
return c
@@ -281,7 +474,7 @@ def build_cc0_character_atlas():
def main():
ASSETS.mkdir(parents=True, exist_ok=True)
targets = [
- ("cc0-tiles.png", build_cc0_tile_atlas(), 384, 32),
+ ("cc0-tiles.png", build_cc0_tile_atlas(), 608, 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 afa46a4..b8b808a 100644
--- a/src/LivingVillage.Desktop/Assets/cc0-tiles.png
+++ b/src/LivingVillage.Desktop/Assets/cc0-tiles.png
Binary files differ