summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop
diff options
context:
space:
mode:
Diffstat (limited to 'src/LivingVillage.Desktop')
-rw-r--r--src/LivingVillage.Desktop/Assets/cjk-glyph-atlas.pngbin60594 -> 63292 bytes
-rw-r--r--src/LivingVillage.Desktop/Assets/cjk-glyphs.txt10
-rw-r--r--src/LivingVillage.Desktop/ChineseText.fs4
-rw-r--r--src/LivingVillage.Desktop/CjkGlyphAtlas.fs2
-rw-r--r--src/LivingVillage.Desktop/Game.fs110
-rw-r--r--src/LivingVillage.Desktop/LaunchScreen.fs32
-rw-r--r--src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj1
-rw-r--r--src/LivingVillage.Desktop/MenuState.fs12
8 files changed, 137 insertions, 34 deletions
diff --git a/src/LivingVillage.Desktop/Assets/cjk-glyph-atlas.png b/src/LivingVillage.Desktop/Assets/cjk-glyph-atlas.png
index 598c2bc..af83cd7 100644
--- a/src/LivingVillage.Desktop/Assets/cjk-glyph-atlas.png
+++ b/src/LivingVillage.Desktop/Assets/cjk-glyph-atlas.png
Binary files differ
diff --git a/src/LivingVillage.Desktop/Assets/cjk-glyphs.txt b/src/LivingVillage.Desktop/Assets/cjk-glyphs.txt
index cdfc550..701200b 100644
--- a/src/LivingVillage.Desktop/Assets/cjk-glyphs.txt
+++ b/src/LivingVillage.Desktop/Assets/cjk-glyphs.txt
@@ -72,6 +72,7 @@
屋
工
已
+币
帮
年
庄
@@ -116,9 +117,11 @@
日
时
明
+昏
易
是
晚
+晨
景
暂
更
@@ -128,15 +131,18 @@
板
林
标
+格
档
桥
歉
+段
民
水
求
江
没
法
+清
渔
游
物
@@ -153,6 +159,7 @@
社
离
移
+空
竹
笑
粮
@@ -193,7 +200,9 @@
货
资
足
+跳
转
+过
近
返
还
@@ -223,6 +232,7 @@
食
饥
饿
+黄
黛
,
:
diff --git a/src/LivingVillage.Desktop/ChineseText.fs b/src/LivingVillage.Desktop/ChineseText.fs
index 82418e6..0d988fe 100644
--- a/src/LivingVillage.Desktop/ChineseText.fs
+++ b/src/LivingVillage.Desktop/ChineseText.fs
@@ -83,6 +83,10 @@ module ChineseText =
"开始新游戏"
"读取存档"
"退出"
+ "开始"
+ "读档"
+ "继续"
+ "ESC 或 空格 跳过"
"移动"
"互动"
"对话"
diff --git a/src/LivingVillage.Desktop/CjkGlyphAtlas.fs b/src/LivingVillage.Desktop/CjkGlyphAtlas.fs
index 07895c8..b6e6947 100644
--- a/src/LivingVillage.Desktop/CjkGlyphAtlas.fs
+++ b/src/LivingVillage.Desktop/CjkGlyphAtlas.fs
@@ -11,7 +11,7 @@ module CjkGlyphAtlas =
// Keep this table sorted and stable so the generated PNG has a reproducible layout.
let private characterTable =
- "×一不业中主乐乡书买事于互交人今从价任传体作使保倍停入关内农准出到前力功务动助单卖南原友取受可同和回围圃土在墙备夜大天夫失好始存完家察对就层居屋工已帮年庄库应度开式当录待得心必态怒总恼意戏成或打找报拒择挑捕据接提播操收效敌数新方无日时明易是晚景暂更最有村板林标档桥歉民水求江没法渔游物状理瓦生用留白目石确社离移竹笑粮精系紧给绝继绪续置翻聊职能范获菜衅行观认讨议记讲论设话询说请读谅谈败货资足转近返还进迫退送选速道郎量金鉴钱门闭闲间闻附零需面项须食饥饿黛,:"
+ "×一不业中主乐乡书买事于互交人今从价任传体作使保倍停入关内农准出到前力功务动助单卖南原友取受可同和回围圃土在墙备夜大天夫失好始存完家察对就层居屋工已币帮年庄库应度开式当录待得心必态怒总恼意戏成或打找报拒择挑捕据接提播操收效敌数新方无日时明昏易是晚晨景暂更最有村板林标格档桥歉段民水求江没法清渔游物状理瓦生用留白目石确社离移空竹笑粮精系紧给绝继绪续置翻聊职能范获菜衅行观认讨议记讲论设话询说请读谅谈败货资足跳转过近返还进迫退送选速道郎量金鉴钱门闭闲间闻附零需面项须食饥饿黄黛,:"
let characters = characterTable.ToCharArray()
let rowCount = (characters.Length + columns - 1) / columns
diff --git a/src/LivingVillage.Desktop/Game.fs b/src/LivingVillage.Desktop/Game.fs
index 1b9508b..8402a72 100644
--- a/src/LivingVillage.Desktop/Game.fs
+++ b/src/LivingVillage.Desktop/Game.fs
@@ -140,6 +140,9 @@ type LivingVillageGame() as this =
let mutable simulationControl = startSimulationControl
let mutable legacyStepsPerFrame = (if sampleMode then None else initialLegacyStepsPerFrame)
let mutable menu = MenuState.create startSimulationControl false
+ let mutable splashActive = true
+ let mutable splashFrame = 0L
+ let mutable menuEntranceFrame = 0L
let mutable autoplayFrames = 0
let mutable flowStep = 0
let mutable flowHold = 0
@@ -481,34 +484,21 @@ type LivingVillageGame() as this =
printfn "flow=return-menu"
flowStep <- 2
| 2 when menu.Page = MainMenu ->
- moveDown 3
+ moveDown 1
this.DispatchMenuInput Confirm
- printfn "flow=settings=open"
- this.DispatchMenuInput Down
- this.DispatchMenuInput Confirm
- this.DispatchMenuInput Back
- printfn "flow=settings=back"
+ printfn "flow=load"
flowStep <- 3
- | 3 when menu.Page = MainMenu ->
- moveDown 4
- this.DispatchMenuInput Confirm
- printfn "flow=controls=open"
- flowStep <- 30
- | 30 ->
- flowHold <- flowHold + 1
- if flowHold >= 60 || menu.Page <> Controls then
- flowHold <- 0
- flowStep <- 31
- | 31 ->
+ | 3 ->
this.DispatchMenuInput Back
- printfn "flow=controls=back"
+ printfn "flow=back"
flowStep <- 4
| 4 when menu.Page = MainMenu ->
- this.DispatchMenuInput Confirm
this.SaveWorld "flow=save" |> ignore
flowStep <- 5
- | 5 when menu.Page = Playing ->
- this.LoadWorld "flow=load" |> ignore
+ | 5 when menu.Page = MainMenu ->
+ moveDown 1
+ this.DispatchMenuInput Confirm
+ printfn "flow=load-again"
flowStep <- 6
| 6 ->
this.OpenMainMenu()
@@ -540,12 +530,22 @@ type LivingVillageGame() as this =
let kb = Keyboard.GetState()
let pressed (key: Keys) = kb.IsKeyDown(key) && not (prevKb.IsKeyDown(key))
let pressedAny (keys: Keys list) = keys |> List.exists pressed
- if autoplayFlow then
- this.RunAutoplayFlow()
- elif menu.Page = Playing then
- this.UpdatePlaying gameTime kb pressed pressedAny
+ if splashActive then
+ splashFrame <- splashFrame + 1L
+ let skip = LaunchScreen.skipRequested (pressed Keys.Escape) (pressed Keys.Space)
+ if skip || LaunchScreen.isComplete splashFrame then
+ splashActive <- false
+ menuEntranceFrame <- 0L
+ printfn (if skip then "splash=skip frame=%d" else "splash=done frame=%d") splashFrame
else
- this.UpdateMenu pressed
+ if autoplayFlow then
+ this.RunAutoplayFlow()
+ elif menu.Page = Playing then
+ this.UpdatePlaying gameTime kb pressed pressedAny
+ else
+ this.UpdateMenu pressed
+ if menu.Page <> Playing then
+ menuEntranceFrame <- menuEntranceFrame + 1L
match pauseHelpControl with
| Some original when menu.Page = Playing ->
let _, restoredControl = MenuState.exitPauseHelp original menu
@@ -606,7 +606,9 @@ type LivingVillageGame() as this =
spriteBatch.End()
override this.Draw(gameTime: GameTime) =
- if menu.Page <> Playing then
+ if splashActive then
+ this.DrawSplash()
+ elif menu.Page <> Playing then
this.DrawMenu()
elif relationView then
this.DrawRelationView()
@@ -629,6 +631,58 @@ type LivingVillageGame() as this =
use stream = System.IO.File.Create(path)
texture.SaveAsPng(stream, width, height)
+ /// 启动画面:分层天色 + 屋脊/水面剪影 + 标题淡入上浮 + 跳过提示,全部为帧计数纯函数。
+ member private this.DrawSplash() =
+ let viewport = this.GraphicsDevice.Viewport
+ let width = viewport.Width
+ let height = viewport.Height
+ let layout = TitleScreen.layout width height
+ let reveal = LaunchScreen.revealProgress splashFrame
+ let veil = LaunchScreen.veilAlpha splashFrame
+ let alpha (value: int) = int (float32 value * reveal)
+ spriteBatch.Begin()
+
+ // 分层天色:沿用标题画面色带,绝不整屏纯色或大黑块。
+ let bandCount = List.length layout.SkyBands
+ let bandHeight = height / bandCount
+ List.iteri (fun index (band: TitleScreen.ColorRgb) ->
+ let y0 = index * bandHeight
+ let y1 = if index = bandCount - 1 then height else y0 + bandHeight
+ spriteBatch.Draw(pixel, Rectangle(0, y0, width, y1 - y0), Color(band.R, band.G, band.B))) layout.SkyBands
+
+ // 屋脊剪影随暗幕退场而上浮就位。
+ let drop = int (28.0f * veil)
+ for x in 0 .. Sim.tilePixels .. width - 1 do
+ spriteBatch.Draw(artTextures.TileAtlas, Rectangle(x, layout.RidgeY + drop, Sim.tilePixels, Sim.tilePixels), VillageArt.atlasTileRectangle (fst TitleScreen.roofSlots), Color.White)
+ spriteBatch.Draw(artTextures.TileAtlas, Rectangle(x, layout.EaveY + drop, Sim.tilePixels, Sim.tilePixels), VillageArt.atlasTileRectangle (snd TitleScreen.roofSlots), Color.White)
+
+ // 水面色带反射天色,随显现度提高而淡出。
+ let waterColor = Color(150, 170, 178, int (255.0f * (1.0f - reveal)))
+ for row in layout.WaterRows do
+ for x in 0 .. Sim.tilePixels .. width - 1 do
+ spriteBatch.Draw(artTextures.TileAtlas, Rectangle(x, row, Sim.tilePixels, Sim.tilePixels), VillageArt.atlasTileRectangle TitleScreen.waterSlot, waterColor)
+
+ // 标题:淡入 + 上浮,先画深色投影再画亮面。
+ let titleRise = int (24.0f * (1.0f - reveal))
+ PixelText.draw spriteBatch pixel cjkAtlas (layout.TitleX + 4) (layout.TitleY + titleRise + 4) layout.TitleScale (Color(12, 20, 28, alpha 255)) LaunchScreen.title
+ PixelText.draw spriteBatch pixel cjkAtlas layout.TitleX (layout.TitleY + titleRise) layout.TitleScale (Color(235, 246, 232, alpha 255)) LaunchScreen.title
+
+ // 英文副标题保持同一 CJK/点阵字体路径。
+ let subtitleScale = 2
+ let subtitleWidth = FloatingPrompt.measureWidth LaunchScreen.subtitle subtitleScale
+ PixelText.draw spriteBatch pixel cjkAtlas ((width - subtitleWidth) / 2) (layout.TitleY + 78) subtitleScale (Color(180, 210, 214, alpha 255)) LaunchScreen.subtitle
+
+ // 跳过提示:轻微呼吸闪烁,落在底部独立窄条上(非整屏黑块)。
+ let blink = if (splashFrame / 20L) % 2L = 0L then 220 else 130
+ let hintScale = 2
+ let hintWidth = FloatingPrompt.measureWidth LaunchScreen.skipHint hintScale
+ let hintX = (width - hintWidth) / 2
+ let hintY = height - 56
+ spriteBatch.Draw(pixel, Rectangle(hintX - 10, hintY - 8, hintWidth + 20, 8 * hintScale + 16), Color(10, 16, 24, int (170.0f * (0.5f + 0.5f * reveal))))
+ spriteBatch.Draw(pixel, Rectangle(hintX - 10, hintY - 8, hintWidth + 20, 2), Color(92, 180, 190, alpha 220))
+ PixelText.draw spriteBatch pixel cjkAtlas hintX hintY hintScale (Color(220, 235, 225, alpha blink)) LaunchScreen.skipHint
+ spriteBatch.End()
+
member private this.DrawMenu() =
let viewport = this.GraphicsDevice.Viewport
let width = viewport.Width
@@ -678,7 +732,7 @@ type LivingVillageGame() as this =
spriteBatch.Draw(pixel, Rectangle(0, y0, width, y1 - y0), Color(band.R, band.G, band.B))) layout.SkyBands
// Deterministic 2s entrance: skyline settles from above as the progress unwinds.
- let entrance = TitleScreen.entranceProgress hudClock
+ let entrance = TitleScreen.entranceProgress menuEntranceFrame
let skylineDrop = int (36.0f * entrance)
List.iteri (fun column slot ->
let x = column * Sim.tilePixels
diff --git a/src/LivingVillage.Desktop/LaunchScreen.fs b/src/LivingVillage.Desktop/LaunchScreen.fs
new file mode 100644
index 0000000..0977777
--- /dev/null
+++ b/src/LivingVillage.Desktop/LaunchScreen.fs
@@ -0,0 +1,32 @@
+namespace LivingVillage.Desktop
+
+/// 启动画面:固定的 60Hz 步进下,全部为帧计数的纯函数,确定性可复现。
+/// 标题淡入(revealProgress)、整屏暗幕退场(veilAlpha),Esc/空格可随时跳过。
+module LaunchScreen =
+
+ let durationFrames = 150L
+ let fadeInFrames = 45L
+ let title = "江南水乡"
+ let subtitle = "Living Village"
+ let skipHint = "ESC 或 空格 跳过"
+
+ let private clamp01 (value: float32) : float32 =
+ if value < 0.0f then 0.0f
+ elif value > 1.0f then 1.0f
+ else value
+
+ /// 0 = 完全未显示,1 = 完全显现。帧计数纯函数。
+ let revealProgress (frame: int64) : float32 =
+ if frame <= 0L then 0.0f
+ else clamp01 (float32 frame / float32 fadeInFrames)
+
+ /// 整屏暗幕:起手 1,随进程退到 0(1 -> 0)。
+ let veilAlpha (frame: int64) : float32 =
+ if frame <= 0L then 1.0f
+ elif frame >= durationFrames then 0.0f
+ else 1.0f - clamp01 (float32 frame / float32 durationFrames)
+
+ let isComplete (frame: int64) : bool = frame >= durationFrames
+
+ /// Esc 或 空格 触发跳过(调用方用“按下沿”结果,不做按键状态判定)。
+ let skipRequested (escapePressed: bool) (spacePressed: bool) : bool = escapePressed || spacePressed
diff --git a/src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj b/src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj
index 093f7ae..27e5da6 100644
--- a/src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj
+++ b/src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj
@@ -12,6 +12,7 @@
<Compile Include="VillageArt.fs" />
<Compile Include="ChineseText.fs" />
<Compile Include="CjkGlyphAtlas.fs" />
+ <Compile Include="LaunchScreen.fs" />
<Compile Include="MenuState.fs" />
<Compile Include="Interaction.fs" />
<Compile Include="SampleScript.fs" />
diff --git a/src/LivingVillage.Desktop/MenuState.fs b/src/LivingVillage.Desktop/MenuState.fs
index 619ac58..0155472 100644
--- a/src/LivingVillage.Desktop/MenuState.fs
+++ b/src/LivingVillage.Desktop/MenuState.fs
@@ -80,11 +80,13 @@ module MenuState =
Settings = simulationControl
Error = None }
+ /// P16 开始界面收敛为三项:开始(无存档)或继续(回到进行中的世界)/ 读档 / 退出。
+ /// 设置与操作说明不再占用开始界面,仍可在游戏内通过 F1/F2/F3 与 P 打开。
let items (state: MenuState) : MenuItem list =
if state.HasCurrentWorld then
- [ ContinueItem; NewGameItem; LoadItem; SettingsItem; ControlsItem; ExitItem ]
+ [ ContinueItem; LoadItem; ExitItem ]
else
- [ NewGameItem; LoadItem; SettingsItem; ControlsItem; ExitItem ]
+ [ NewGameItem; LoadItem; ExitItem ]
let settingsLabel setting : string =
match setting with
@@ -95,9 +97,9 @@ module MenuState =
let itemLabel item : string =
match item with
- | ContinueItem -> "继续游戏"
- | NewGameItem -> "开始新游戏"
- | LoadItem -> "读取存档"
+ | ContinueItem -> "继续"
+ | NewGameItem -> "开始"
+ | LoadItem -> "读档"
| SettingsItem -> "设置"
| ControlsItem -> "操作说明"
| ExitItem -> "退出"