1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
|
namespace LivingVillage.Desktop
open System
open Microsoft.Xna.Framework
open Microsoft.Xna.Framework.Graphics
open LivingVillage.Kernel
/// P47 场景细节渲染层:把 `SceneDetail` 的纯函数结果落成画面。
/// 倒影为每个光源正下方一条暖色垂直光柱(核心 + 外晕,离光源越远越淡),
/// 岸芦苇/漂浮元素用既有瓦片精灵与原创点阵图集,近景屋顶按统一光源分受光面/背光面。
/// 全部只读,不改瓦片/可达性。
module SceneDetailRender =
let private tileX (camera: Vector2) (x: int) = x * Sim.tilePixels - int camera.X
let private tileY (camera: Vector2) (y: int) = y * Sim.tilePixels - int camera.Y
/// 河道倒影:每个光源在正下方水面格画一段暖色垂直光柱(外晕 20px + 核心 10px),
/// alpha 随离光源距离递减、逐格 ±1px 微抖,连成一条可辨认的暖色垂直条纹
/// (明显区别于蓝灰横向波纹)。
let private drawReflections
(spriteBatch: SpriteBatch)
(pixel: Texture2D)
(camera: Vector2)
(map: MapGen.Result)
(tick: int64) =
for (x, y, alpha, _) in SceneDetail.reflectionStreaks map do
let wobble = SceneDetail.reflectionWobbleAt tick x y
let cx = tileX camera x
let cy = tileY camera y
let halo = VillageArt.premultiply 255 150 70 (alpha * 3 / 5)
let core = VillageArt.premultiply 255 208 120 alpha
spriteBatch.Draw(pixel, Rectangle(cx + 4 + wobble, cy, 24, Sim.tilePixels), halo)
spriteBatch.Draw(pixel, Rectangle(cx + 10 + wobble, cy, 12, Sim.tilePixels), core)
/// 岸边芦苇加密度:用既有芦苇精灵补绘一层贴水芦苇。
let private drawBankReeds (spriteBatch: SpriteBatch) (textures: VillageArt.ArtTextures) (camera: Vector2) (map: MapGen.Result) =
match textures.Cc0TileAtlas with
| Some cc0 ->
for (x, y) in SceneDetail.bankReedTiles map do
spriteBatch.Draw(cc0, Rectangle(tileX camera x, tileY camera y, Sim.tilePixels, Sim.tilePixels), Nullable(VillageArt.cc0TileRect VillageArt.cc0TileIndexReeds), Color.White)
| None ->
let source = VillageArt.atlasTileRectangle 18
for (x, y) in SceneDetail.bankReedTiles map do
spriteBatch.Draw(textures.TileAtlas, Rectangle(tileX camera x, tileY camera y, Sim.tilePixels, Sim.tilePixels), Nullable(source), Color.White)
/// 漂浮元素:原创点阵鸭/叶,按 tick 慢漂移。
let private drawFloaters (spriteBatch: SpriteBatch) (textures: VillageArt.ArtTextures) (camera: Vector2) (map: MapGen.Result) (tick: int64) =
for (x, y, kind) in SceneDetail.floatingProps map do
let seed = x * 31 + y * 7
let dx, dy = SceneDetail.floatDrift tick seed
let frame = SceneDetail.floatFrame tick seed
let kindIndex =
match kind with
| SceneDetail.DuckFloat -> FloaterArt.duckIndex
| SceneDetail.FallenLeaf -> FloaterArt.leafIndex
let source = FloaterArt.sourceRectangle kindIndex frame
spriteBatch.Draw(
textures.FloaterAtlas,
Rectangle(tileX camera x + dx + (Sim.tilePixels - FloaterArt.cellWidth) / 2, tileY camera y + dy + (Sim.tilePixels - FloaterArt.cellHeight) / 2, FloaterArt.cellWidth, FloaterArt.cellHeight),
Nullable(source),
Color.White)
/// 近景楼层层错落:屏幕空间前景屋顶带。统一光源来自左上,故每段屋顶
/// 左半受光(暖亮)、右半背光(压暗),并整体投影到右下(shadowOffsetPx)。
let private drawForeground (spriteBatch: SpriteBatch) (textures: VillageArt.ArtTextures) (pixel: Texture2D) (viewportWidth: int) (viewportHeight: int) (tick: int64) =
let ridgeSource = VillageArt.atlasTileRectangle 8
let eaveSource = VillageArt.atlasTileRectangle 9
let shadowColor = VillageArt.premultiply 8 12 18 120
let litColor = VillageArt.premultiply 255 236 196 46
let darkColor = VillageArt.premultiply 6 10 18 110
let dx, dy = SceneDetail.shadowOffsetPx
for (x, y, w) in SceneDetail.foregroundRooftops viewportWidth viewportHeight tick do
// 统一方向的投影:先把 eave 的暗色镜像平移一份到右下,再画本体。
spriteBatch.Draw(textures.TileAtlas, Rectangle(x + dx, y + dy, w, Sim.tilePixels), Nullable(eaveSource), shadowColor)
spriteBatch.Draw(textures.TileAtlas, Rectangle(x, y - Sim.tilePixels, w, Sim.tilePixels), Nullable(ridgeSource), Color.White)
spriteBatch.Draw(textures.TileAtlas, Rectangle(x, y, w, Sim.tilePixels), Nullable(eaveSource), Color.White)
// 受光面 / 背光面:左半加暖亮、右半压暗,形成两档以上 luma 差。
let half = max 1 (w / 2)
spriteBatch.Draw(pixel, Rectangle(x, y - Sim.tilePixels, half, 2 * Sim.tilePixels), litColor)
spriteBatch.Draw(pixel, Rectangle(x + half, y - Sim.tilePixels, w - half, 2 * Sim.tilePixels), darkColor)
/// 一次性绘制全部 P47 场景细节(倒影 → 岸边芦苇 → 漂浮元素 → 近景前景层)。
let draw
(spriteBatch: SpriteBatch)
(textures: VillageArt.ArtTextures)
(pixel: Texture2D)
(camera: Vector2)
(viewportWidth: int)
(viewportHeight: int)
(map: MapGen.Result)
(tick: int64) =
drawReflections spriteBatch pixel camera map tick
drawBankReeds spriteBatch textures camera map
drawFloaters spriteBatch textures camera map tick
drawForeground spriteBatch textures pixel viewportWidth viewportHeight tick
|