diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/LivingVillage.Desktop/Game.fs | 27 | ||||
| -rw-r--r-- | src/LivingVillage.Desktop/HudLayout.fs | 85 |
2 files changed, 100 insertions, 12 deletions
diff --git a/src/LivingVillage.Desktop/Game.fs b/src/LivingVillage.Desktop/Game.fs index cb28e58..8e58727 100644 --- a/src/LivingVillage.Desktop/Game.fs +++ b/src/LivingVillage.Desktop/Game.fs @@ -1581,14 +1581,33 @@ type LivingVillageGame() as this = spriteBatch.Draw(pixel, Rectangle(0, 0, viewport.Width, barHeight / 2), palette.BarTop) spriteBatch.Draw(pixel, Rectangle(0, barHeight / 2, viewport.Width, barHeight - barHeight / 2), palette.BarBottom) spriteBatch.Draw(pixel, Rectangle(0, barHeight, viewport.Width, HudLayout.accentHeight), palette.Accent) + // 图标用 pixel 贴图按 7x7 点阵绘制(无外部素材),先描边后填充,保证昼夜可读。 + let drawIcon (iconX: int) (iconY: int) (color: Color) (icon: HudLayout.HudIcon) = + HudLayout.iconRows icon + |> List.iteri (fun row bits -> + bits + |> Seq.iteri (fun column bit -> + if bit = '1' then + spriteBatch.Draw( + pixel, + Rectangle( + iconX + column * HudLayout.iconScale, + iconY + row * HudLayout.iconScale, + HudLayout.iconScale, + HudLayout.iconScale), + color))) + let iconY = (barHeight - HudLayout.iconHeight) / 2 for (x, width, segment) in placed do spriteBatch.Draw(pixel, HudLayout.segmentBackdrop x width, palette.SegmentBackdrop) + let textX = HudLayout.textOrigin x let labelAdvanced = FloatingPrompt.measureWidth (segment.Label + " ") HudLayout.textScale for (dx, dy) in HudLayout.textOutlineOffsets do - PixelText.draw spriteBatch pixel cjkAtlas (x + dx) (textY + dy) HudLayout.textScale palette.Outline segment.Label - PixelText.draw spriteBatch pixel cjkAtlas (x + labelAdvanced + dx) (textY + dy) HudLayout.textScale palette.Outline segment.Value - PixelText.draw spriteBatch pixel cjkAtlas x textY HudLayout.textScale palette.Label segment.Label - PixelText.draw spriteBatch pixel cjkAtlas (x + labelAdvanced) textY HudLayout.textScale palette.Value segment.Value + drawIcon (x + dx) (iconY + dy) palette.Outline segment.Icon + PixelText.draw spriteBatch pixel cjkAtlas (textX + dx) (textY + dy) HudLayout.textScale palette.Outline segment.Label + PixelText.draw spriteBatch pixel cjkAtlas (textX + labelAdvanced + dx) (textY + dy) HudLayout.textScale palette.Outline segment.Value + drawIcon x iconY palette.Value segment.Icon + PixelText.draw spriteBatch pixel cjkAtlas textX textY HudLayout.textScale palette.Label segment.Label + PixelText.draw spriteBatch pixel cjkAtlas (textX + labelAdvanced) textY HudLayout.textScale palette.Value segment.Value spriteBatch.End() /// 常驻情境状态:顶部条下方的小药丸,仅 WorldPanel 且瞬态未过期时出现。 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 |
