summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop/VillageArt.fs
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-28 08:42:59 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-28 08:42:59 +0800
commitfd429fffb9328515beb120da2807faf5305a6e0c (patch)
tree3af2346a538fc4a4c05afce602c2030615b88e6c /src/LivingVillage.Desktop/VillageArt.fs
parent39f5c7d9951fb4c3ab8cb151665233ac1b6df76a (diff)
downloadliving-village-fd429fffb9328515beb120da2807faf5305a6e0c.tar.gz
feat(art): P34 素材扩展切片(水面/屋顶/墙体/栏杆)
- 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 命中)
Diffstat (limited to 'src/LivingVillage.Desktop/VillageArt.fs')
-rw-r--r--src/LivingVillage.Desktop/VillageArt.fs95
1 files changed, 81 insertions, 14 deletions
diff --git a/src/LivingVillage.Desktop/VillageArt.fs b/src/LivingVillage.Desktop/VillageArt.fs
index 9a034aa..80eed07 100644
--- a/src/LivingVillage.Desktop/VillageArt.fs
+++ b/src/LivingVillage.Desktop/VillageArt.fs
@@ -553,7 +553,20 @@ module VillageArt =
let cc0TileAtlasRelativePath = "Assets/cc0-tiles.png"
// Source ground tiles are 16x16, upscaled exactly 2x to one Sim.tilePixels cell.
let cc0TileSourcePixels = 16
- let cc0TileAtlasWidth = Sim.tilePixels * 4
+ // P34 structure slice: indices into cc0-tiles.png. Kenney Tiny Farm ships no
+ // water / roof / wall / railing / door art, so indices 4-11 are self-drawn
+ // (provenance in docs/素材来源.md), laid out by this fixed contract.
+ let cc0TileCount = 12
+ let cc0TileIndexWaterA = 4
+ let cc0TileIndexWaterB = 5
+ let cc0TileIndexRoofRidge = 6
+ let cc0TileIndexRoofEave = 7
+ let cc0TileIndexWallWood = 8
+ let cc0TileIndexWallWindow = 9
+ let cc0TileIndexRailing = 10
+ let cc0TileIndexDoor = 11
+ let cc0WaterFrameCount = 2
+ let cc0TileAtlasWidth = Sim.tilePixels * cc0TileCount
let cc0TileAtlasHeight = Sim.tilePixels
let cc0CharacterAtlasRelativePath = "Assets/cc0-characters.png"
// Source Puny band is 16x24, upscaled exactly 2x to a 32x48 cell; atlas packs
@@ -568,6 +581,16 @@ module VillageArt =
let cc0CharacterAtlasWidth =
cc0CharacterCellWidth * cc0CharacterVariantCount * cc0CharacterDirectionCount * cc0CharacterFrameCount
+ /// Deterministic 2-phase water per tile: `(tick/32 + x + 2y) mod 2`, so one frame
+ /// shows both ripple phases across the river. Mirrors the 3-phase Jiangnan
+ /// `waterFrameTickAt`, but only two self-drawn CC0 frames exist.
+ let cc0WaterTileIndexAt (tick: int64) (tileX: int) (tileY: int) : int =
+ let phase = int ((tick / 32L + int64 (tileX + 2 * tileY)) % 2L)
+ if phase = 0 then cc0TileIndexWaterA else cc0TileIndexWaterB
+
+ let cc0TileRect (index: int) : XnaRectangle =
+ XnaRectangle(index * Sim.tilePixels, 0, Sim.tilePixels, Sim.tilePixels)
+
/// Ground atlas selection: `Some cc0-tiles.png` when the pack is on, else `None`
/// so the renderer keeps its procedural Jiangnan ground as the fallback.
let selectedGroundAtlasRelativePath () : string option =
@@ -792,6 +815,20 @@ module VillageArt =
let draw position sprite =
drawWorldTile spriteBatch textures camera viewportWidth viewportHeight position sprite tint
+ // P34: with the CC0 pack on, individual sample structures draw from the
+ // real/self-drawn cc0-tiles atlas; otherwise every draw falls back to jiangnan.
+ let drawCc0At (position: TilePosition) (index: int) =
+ match textures.Cc0TileAtlas with
+ | Some cc0 ->
+ drawCc0Tile
+ spriteBatch
+ cc0
+ tint
+ (cc0TileRect index)
+ (position.X * Sim.tilePixels - int camera.X)
+ (position.Y * Sim.tilePixels - int camera.Y)
+ | None -> ()
+
// Each water tile picks its phase from its own world coordinates, so a single
// frame shows the three ripple phases across the river instead of one global frame.
let waterSpriteAt (position: TilePosition) =
@@ -800,15 +837,41 @@ module VillageArt =
| 1 -> WaterSpriteB
| _ -> WaterSpriteC
+ // P34 water: the self-drawn CC0 atlas only has two frames, so it picks the
+ // 2-phase index; the jiangnan atlas keeps its 3-phase ripple.
+ let drawWater (position: TilePosition) =
+ match textures.Cc0TileAtlas with
+ | Some _ -> drawCc0At position (cc0WaterTileIndexAt tick position.X position.Y)
+ | None -> draw position (waterSpriteAt position)
+
+ // P34 structures: real/self-drawn roof, wall, window and door cells replace
+ // the jiangnan house tiles only when the CC0 pack is on.
+ let houseSpriteAt (position: TilePosition) (sprite: TileSprite) =
+ let cc0Index =
+ match textures.Cc0TileAtlas with
+ | Some _ ->
+ match sprite with
+ | RoofRidgeSprite -> Some cc0TileIndexRoofRidge
+ | RoofEaveSprite -> Some cc0TileIndexRoofEave
+ | WallWoodSprite -> Some cc0TileIndexWallWood
+ | WallWindowSprite -> Some cc0TileIndexWallWindow
+ | DoorSprite -> Some cc0TileIndexDoor
+ | _ -> None
+ | None -> None
+ match cc0Index with
+ | Some index -> drawCc0At position index
+ | None -> draw position sprite
+
// Procedural terrain for the 512x384 large world: water/pebble/peat codes map
// onto the jiangnan atlas sprites with deterministic ripple continuity.
// 生成器世界:1 水 / 2 石板路 / 3 泥炭 / 0 草地。泥炭按草地渲染,路走石板贴图。
let drawTerrainTile (position: TilePosition) (code: int) =
match code with
- | 1 -> draw position (waterSpriteAt position)
+ | 1 -> drawWater position
| 2 -> draw position StonePathSprite
- // P30 乡村空区:水田(4)走静态水面素材、菜畦(5)走菜地素材;均为成熟 tile。
- | 4 -> draw position WaterSprite
+ // P30 乡村空区:水田(4)走水波动画、菜畦(5)走菜地素材;均为成熟 tile。
+ // P34:水田/水网在 CC0 包下同样走自绘 2 相位水面。
+ | 4 -> drawWater position
| 5 -> draw position GardenSprite
| _ -> draw position GrassSprite
@@ -851,7 +914,7 @@ module VillageArt =
for localPosition, kind in planGrounds do
let position = worldTile plan localPosition
match kind with
- | Water -> draw position (waterSpriteAt position)
+ | Water -> drawWater position
| StonePath ->
match textures.Cc0TileAtlas with
| Some cc0 -> drawCc0Tile spriteBatch cc0 tint cc0PathRect (position.X * Sim.tilePixels - int camera.X) (position.Y * Sim.tilePixels - int camera.Y)
@@ -860,18 +923,22 @@ module VillageArt =
let drawHouse doorTile =
let position = worldTile plan doorTile
for dx in -2 .. 1 do
- draw (offsetTile position dx -2) RoofRidgeSprite
- draw (offsetTile position dx -1) RoofEaveSprite
- draw (offsetTile position -2 0) WallWoodSprite
- draw (offsetTile position -1 0) WallWindowSprite
- draw (offsetTile position 0 0) DoorSprite
- draw (offsetTile position 1 0) WallWoodSprite
+ houseSpriteAt (offsetTile position dx -2) RoofRidgeSprite
+ houseSpriteAt (offsetTile position dx -1) RoofEaveSprite
+ houseSpriteAt (offsetTile position -2 0) WallWoodSprite
+ houseSpriteAt (offsetTile position -1 0) WallWindowSprite
+ houseSpriteAt (offsetTile position 0 0) DoorSprite
+ houseSpriteAt (offsetTile position 1 0) WallWoodSprite
let drawBridge localPosition =
let position = worldTile plan localPosition
- draw position BridgeSprite
- draw (offsetTile position 0 1) BridgeSprite
- draw (offsetTile position 0 2) BridgeSprite
+ let bridgeTile p =
+ match textures.Cc0TileAtlas with
+ | Some _ -> drawCc0At p cc0TileIndexRailing
+ | None -> draw p BridgeSprite
+ bridgeTile position
+ bridgeTile (offsetTile position 0 1)
+ bridgeTile (offsetTile position 0 2)
let drawBamboo localPosition =
let position = worldTile plan localPosition