summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop
diff options
context:
space:
mode:
authorHermes Leader <v@v>2026-09-22 08:41:14 +0800
committerHermes Leader <v@v>2026-09-22 08:41:14 +0800
commitaa3d749ed2a63f9409290c564649f9718756fd6e (patch)
tree864a7f3d8d750158513e71739091e72204d23da3 /src/LivingVillage.Desktop
parent1fb9f9de8f7a6e9124f9a650e44b2f27ed2cac7a (diff)
parent6afc9041e9aeb9f51a3b9ea73b5a6d493862ee1c (diff)
downloadliving-village-aa3d749ed2a63f9409290c564649f9718756fd6e.tar.gz
merge: P24 512x384 民居密度/村民可辨(writer hash 保留)
Diffstat (limited to 'src/LivingVillage.Desktop')
-rw-r--r--src/LivingVillage.Desktop/Game.fs14
-rw-r--r--src/LivingVillage.Desktop/MapGen.fs55
-rw-r--r--src/LivingVillage.Desktop/VillageArt.fs21
3 files changed, 80 insertions, 10 deletions
diff --git a/src/LivingVillage.Desktop/Game.fs b/src/LivingVillage.Desktop/Game.fs
index 8309867..bcc4094 100644
--- a/src/LivingVillage.Desktop/Game.fs
+++ b/src/LivingVillage.Desktop/Game.fs
@@ -215,6 +215,8 @@ type LivingVillageGame() as this =
let mutable avatarMoving = false
let unlockFps = Environment.GetEnvironmentVariable("LV_UNLOCK_FPS") = "1"
let perfSummaryMode = Environment.GetEnvironmentVariable("LV_PERF_SUMMARY") = "1"
+ // P24 证据钩子:每秒打印屏幕内可见 NPC 数,证明大世界村民确实被渲染。
+ let npcVisibleLog = Environment.GetEnvironmentVariable("LV_NPC_VISIBLE_LOG") = "1"
let mutable fpsSamples: float list = []
let mutable fpsFrames = 0
let mutable fpsSeconds = 0.0
@@ -751,6 +753,18 @@ type LivingVillageGame() as this =
if perfSummaryMode then
fpsSamples <- fps :: fpsSamples
printfn $"fps={fps:F1} tick={world.Tick} pos=({world.Avatar.Pos.X:F0},{world.Avatar.Pos.Y:F0})"
+ if npcVisibleLog then
+ let viewport = this.GraphicsDevice.Viewport
+ let margin = float32 Sim.tilePixels
+ let visible =
+ world.Npcs
+ |> Array.filter (fun npc ->
+ let sx = npc.Pos.X - camera.X
+ let sy = npc.Pos.Y - camera.Y
+ sx >= -margin && sx <= float32 viewport.Width + margin
+ && sy >= -margin && sy <= float32 viewport.Height + margin)
+ |> Array.length
+ printfn $"visible-npcs={visible} of {world.Npcs.Length}"
prevKb <- kb
override this.EndRun() =
diff --git a/src/LivingVillage.Desktop/MapGen.fs b/src/LivingVillage.Desktop/MapGen.fs
index ce9bccf..1e63ed2 100644
--- a/src/LivingVillage.Desktop/MapGen.fs
+++ b/src/LivingVillage.Desktop/MapGen.fs
@@ -30,7 +30,9 @@ module MapGen =
RiverWidth: int
CoreSide: int
SpawnColumns: int
- SpawnRows: int }
+ SpawnRows: int
+ /// P24:大图额外沿河民居数量(沿现有路网泊松散布)。默认 0,64x48/256x192 不受影响。
+ ExtraRiversideHouses: int }
/// P20 第二步:一条贯穿全图的东西向河道(每列恰好一段,宽度下限 RiverWidth)。
type River =
@@ -70,16 +72,20 @@ module MapGen =
RiverWidth = 3
CoreSide = 14
SpawnColumns = 5
- SpawnRows = 6 }
+ SpawnRows = 6
+ ExtraRiversideHouses = 0 }
/// P23 尺寸自适应参数:河道数量随高度增长(约每 96 行一条),其余沿用默认。
/// 64x48 / 256x192 仍得到 2 条河道,与既有输出逐字节一致;512x384 得到 4 条。
+ /// P24:仅对大于 256x192 的图追加沿河民居(泊松散布),保证既有 checksum 不变。
let paramsForSize (width: int) (height: int) (seed: uint64) : Params =
let base_ = defaultParams seed
+ let large = width > 256 || height > 192
{ base_ with
Width = width
Height = height
- RiverCount = max base_.RiverCount (max 1 (height / 96)) }
+ RiverCount = max base_.RiverCount (max 1 (height / 96))
+ ExtraRiversideHouses = (if large then 10 else 0) }
// ---- splitmix64 与 value noise(无外部依赖、无时钟) ----
@@ -275,6 +281,49 @@ module MapGen =
buildings <- building :: buildings
addPath building.DoorX building.DoorY
+ // 5b) P24:大图沿河民居加密。从既有路网瓦片出发,向四邻定向落位 3x2 白墙黛瓦民居,
+ // 门开向相邻路格并接入路网(BFS 可达);仅在 ExtraRiversideHouses>0 时执行,
+ // 且只在核心区外的草地落位,故 64x48/256x192 输出逐字节不变。
+ if p.ExtraRiversideHouses > 0 then
+ let footprintFree (left: int) (top: int) =
+ left >= 1 && left + 2 <= w - 2 && top >= 1 && top + 1 <= h - 2
+ && [ for dx in 0 .. 2 do
+ for dy in 0 .. 1 -> (left + dx, top + dy) ]
+ |> List.forall (fun (x, y) ->
+ tiles.[idx x y] = int GroundTile.Grass
+ && not (inCore x y))
+ let overlaps (left: int) (top: int) =
+ buildings
+ |> List.exists (fun b ->
+ left < b.Left + b.Width && b.Left < left + 3
+ && top < b.Top + b.Height && b.Top < top + 2)
+ let directions = [ (0, -1); (0, 1); (-1, 0); (1, 0) ]
+ let mutable extraPlaced = 0
+ let mutable extraAttempts = 0
+ let maxAttempts = p.ExtraRiversideHouses * 400
+ while extraPlaced < p.ExtraRiversideHouses && extraAttempts < maxAttempts && paths.Length > 0 do
+ extraAttempts <- extraAttempts + 1
+ let nextState, raw = splitmix structureRng
+ structureRng <- nextState
+ if paths.Length > 0 then
+ let pathX, pathY = List.item (int (raw % uint64 paths.Length)) paths
+ let dirX, dirY = directions.[int ((raw >>> 32) % 4UL)]
+ let doorX = pathX + dirX
+ let doorY = pathY + dirY
+ let left = doorX - 1
+ let top = doorY - 1
+ if footprintFree left top && not (overlaps left top) then
+ let building =
+ { Left = left
+ Top = top
+ Width = 3
+ Height = 2
+ DoorX = doorX
+ DoorY = doorY }
+ buildings <- building :: buildings
+ addPath building.DoorX building.DoorY
+ extraPlaced <- extraPlaced + 1
+
// 6) 30 个 spawn:核心区内的规则网格。
let sc = max 2 p.SpawnColumns
let sr = max 2 p.SpawnRows
diff --git a/src/LivingVillage.Desktop/VillageArt.fs b/src/LivingVillage.Desktop/VillageArt.fs
index 3ad5b19..12c17ba 100644
--- a/src/LivingVillage.Desktop/VillageArt.fs
+++ b/src/LivingVillage.Desktop/VillageArt.fs
@@ -616,13 +616,20 @@ module VillageArt =
for (x, y) in map.Bridges do
spriteBatch.Draw(textures.TileAtlas, XnaRectangle(tileX x, tileY y, cell, cell), bridgeSource, XnaColor.White)
for building in map.Buildings do
- for row in building.Top .. building.Top + building.Height - 1 do
- // 白墙朝向道路一行(门所在行),其余为黑瓦屋顶,两侧河岸朝向一致。
- let color =
- if row = building.DoorY then XnaColor(236, 232, 220)
- else XnaColor(48, 52, 60)
- fillRect spriteBatch pixel color (tileX building.Left) (tileY row) (building.Width * cell) (cell)
- fillRect spriteBatch pixel (XnaColor(96, 60, 42)) (tileX building.DoorX) (tileY building.DoorY) cell cell
+ let left = building.Left
+ let right = building.Left + building.Width - 1
+ // P24:改用 256x192 同款民宅部件——黛瓦(脊/檐)+ 木墙/窗 + 门。
+ for row in building.Top .. building.DoorY - 1 do
+ let roofSprite = if row = building.Top then RoofRidgeSprite else RoofEaveSprite
+ let source = tileSourceRectangle roofSprite
+ for x in left .. right do
+ spriteBatch.Draw(textures.TileAtlas, XnaRectangle(tileX x, tileY row, cell, cell), source, XnaColor.White)
+ for x in left .. right do
+ let wallSprite =
+ if x = building.DoorX then DoorSprite
+ elif (x - left) % 2 = 0 then WallWindowSprite
+ else WallWoodSprite
+ spriteBatch.Draw(textures.TileAtlas, XnaRectangle(tileX x, tileY building.DoorY, cell, cell), tileSourceRectangle wallSprite, XnaColor.White)
/// 桥头灯笼瓦片:每座桥每列的最上/最下一格(桥两端),用于挂灯笼与夜间暖光。
let bridgeLanternTiles (map: MapGen.Result) : (int * int) list =