From 70394b3e4ded4811b1f482e6d036b23b7dcb1765 Mon Sep 17 00:00:00 2001 From: "Somhairle H. Marisol" Date: Mon, 28 Sep 2026 09:27:14 +0800 Subject: feat(desktop): P38 HUD 图标化升级(时段/时间/倍速/体力/钱币) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 顶部常驻条每个状态段前加 7x7 原创点阵图标(用 pixel 贴图绘制,无外部素材): 时段=太阳、时间=钟、倍速=双箭头、体力=心形、钱币=圆形钱眼。图标占 iconWidth+iconGap 后接文本,段总宽含图标;底色药丸与 1px 描边同覆盖图标与文本, 昼夜两套调色板均可读;仍为半透明窄条(barHeight 不变,非大黑块)。 --- src/LivingVillage.Desktop/HudLayout.fs | 85 ++++++++++++++++++++++++++++++---- 1 file changed, 77 insertions(+), 8 deletions(-) (limited to 'src/LivingVillage.Desktop/HudLayout.fs') diff --git a/src/LivingVillage.Desktop/HudLayout.fs b/src/LivingVillage.Desktop/HudLayout.fs index f3e4c12..05fce33 100644 --- a/src/LivingVillage.Desktop/HudLayout.fs +++ b/src/LivingVillage.Desktop/HudLayout.fs @@ -14,32 +14,101 @@ module HudLayout = let segmentGap = 20 let textScale = 2 + /// 常驻段的像素图标(7x7 点阵,按 iconScale 放大),全部用 pixel 贴图绘制、无外部素材。 + type HudIcon = + | PeriodIcon + | ClockIcon + | SpeedIcon + | EnergyIcon + | MoneyIcon + type Segment = { Label: string - Value: string } + Value: string + Icon: HudIcon } let segmentText (segment: Segment) : string = sprintf "%s %s" segment.Label segment.Value /// 常驻状态片段,数值全部取自世界真实状态,绝不编造。 let segments (world: World) (control: SimulationControl) : Segment list = let needs = world.Avatar.Mind.Needs - [ { Label = "时段"; Value = M6Presentation.timePeriodLabel world.Tick } - { Label = "时间"; Value = M6Presentation.clockTimeLabel world.Tick } - { Label = "倍速"; Value = M6Presentation.speedLabel control } - { Label = "体力"; Value = sprintf "%.0f" needs.Energy } - { Label = "钱币"; Value = sprintf "%.0f" needs.Money } ] + [ { Label = "时段"; Value = M6Presentation.timePeriodLabel world.Tick; Icon = PeriodIcon } + { Label = "时间"; Value = M6Presentation.clockTimeLabel world.Tick; Icon = ClockIcon } + { Label = "倍速"; Value = M6Presentation.speedLabel control; Icon = SpeedIcon } + { Label = "体力"; Value = sprintf "%.0f" needs.Energy; Icon = EnergyIcon } + { Label = "钱币"; Value = sprintf "%.0f" needs.Money; Icon = MoneyIcon } ] + + // ---- 图标几何与点阵(纯数据/纯函数,Game 与回归测试共用)---- + + let iconGrid = 7 + let iconScale = textScale + let iconWidth = iconGrid * iconScale + let iconHeight = iconGrid * iconScale + let iconGap = 6 + + /// 7x7 点阵,'1' 为实心。全部原创点阵,仅用现有像素贴图绘制。 + let iconRows (icon: HudIcon) : string list = + match icon with + | PeriodIcon -> + [ "0001000" + "1010101" + "0011100" + "0111110" + "0011100" + "1010101" + "0001000" ] + | ClockIcon -> + [ "0011100" + "0100010" + "1001001" + "1001000" + "1001000" + "0100010" + "0011100" ] + | SpeedIcon -> + [ "0100010" + "0110011" + "0011011" + "0001110" + "0011011" + "0110011" + "0100010" ] + | EnergyIcon -> + [ "0000000" + "0101010" + "1111111" + "1111111" + "0111110" + "0011100" + "0001000" ] + | MoneyIcon -> + [ "0011100" + "0111110" + "1110111" + "1100011" + "1110111" + "0111110" + "0011100" ] + + /// 文本起始 x(图标占 iconWidth + iconGap)。 + let textOrigin (segmentX: int) : int = segmentX + iconWidth + iconGap /// 段文本(含标签)的像素宽度。 let segmentWidth (segment: Segment) : int = FloatingPrompt.measureWidth (segmentText segment) textScale - /// 左对齐排布,返回 (起点 x, 宽度, 分段);纯函数,便于布局回归断言。 + /// 段总宽(图标 + 间隙 + 文本)。 + let segmentSpan (segment: Segment) : int = + iconWidth + iconGap + segmentWidth segment + + /// 左对齐排布,返回 (起点 x, 总宽, 分段);段内 x 起为图标,textOrigin 起为文本。 + /// 纯函数,便于布局回归断言。 let layout (segments: Segment list) : (int * int * Segment) list = let _, placed = segments |> List.fold (fun (x, acc) segment -> - let width = segmentWidth segment + let width = segmentSpan segment x + width + segmentGap, (x, width, segment) :: acc) (paddingX, []) List.rev placed -- cgit v1.2.3