UnityTowerDefense/Assets/_Project/UI/HUD.uss
Matt F 16706a1ecf Scale difficulty by run position; wipe towers at phase end
Follow-up pass on the 2.0 refactor, closing the gap between "waves are drawn
at random" and "difficulty still lives in the enemy assets".

- Enemy health is no longer authored per enemy type. New EnemyScalingConfig
  scales a per-zone HP *budget* by encounter number; per-enemy health is that
  budget divided by the wave's enemy count. Scaling the total rather than the
  per-enemy value keeps enemy count a texture knob (few tanks vs many swarmers)
  instead of a second, uncontrolled difficulty axis. EnemyDefinition.MaxHp
  becomes HpMultiplier, a deviation around 1.0. Speed stays archetype-only and
  unscaled -- escalating it compounds with health and invalidates tower balance
  mid-run.
- GoldConfig entries no longer reference a WaveDefinition. Payout is a property
  of run position, not of which wave got drawn: WaveGoldEntry ->
  EncounterGoldEntry, Waves -> Encounters, keyed by global encounter number.
  Its inspector labels elements "Encounter N" and projects cumulative earnings.
- The wave's voted buffs now show as icon badges in the top bar, read from the
  same RunState slot the spawn path uses so the display can't drift from what
  the enemies actually carry. Hover names the buff; unillustrated cards fall
  back to a lettered badge rather than vanishing.
- Clearing a phase's boss destroys every built tower, unrefunded. Queued build
  jobs still refund -- those towers were never delivered. Runs with the field
  empty, so the walkability churn doesn't hit the re-path scheduler.
- Fixed an RPC codegen break: [ClientRpc] requires a ClientRpc suffix, unlike
  the newer [Rpc(SendTo...)] style this file doesn't use.
- Setup checklist reordered into dependency order; it previously asked for a
  RunDefinition two sections before creating one.

Also carries the editor-side asset reorganisation into Definitions/RunDefinitions
and the sprite move into Enemy/Player draft icon folders.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 00:32:33 -07:00

608 lines
15 KiB
Text

/* ============================================================
HUD.uss — Match HUD stylesheet
Layout mirrors wintermaul_ui_v4.html; visual style is
placeholder and will be replaced in Phase 2.
============================================================ */
/* Root: covers full screen, transparent so the 3D scene shows through.
PickingMode.Ignore is set in C# so clicks in the map area reach the scene. */
.hud-root {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
flex-direction: column;
background-color: rgba(0, 0, 0, 0);
}
/* ---- TOP BAR ------------------------------------------------ */
.top-bar {
height: 28px;
flex-shrink: 0;
flex-direction: row;
align-items: center;
padding: 0 10px;
background-color: rgba(22, 20, 10, 0.92);
border-bottom-width: 1px;
border-bottom-color: rgba(90, 80, 32, 1);
}
.res-group {
flex-direction: row;
align-items: center;
}
.gold-label,
.income-label,
.wave-label,
.dimmed-label {
margin-right: 10px;
}
.top-spacer {
flex: 1;
}
/* Gear button at the far right of the top bar. Explicit min/max width and zero
padding override the default Button theme, which would otherwise pad it out
to a text-sized control. background-image: none is required too — the default
runtime theme skins Button with a light rounded sprite, and setting only
background-color leaves that sprite on top (renders as a blank pale square). */
.menu-button {
width: 22px;
height: 22px;
min-width: 22px;
min-height: 22px;
max-width: 22px;
flex-shrink: 0;
margin: 0 0 0 12px;
padding: 0;
border-width: 1px;
border-color: rgba(90, 80, 32, 1);
background-image: none;
background-color: rgba(0, 0, 0, 0.25);
}
.menu-button:hover {
background-color: rgba(90, 80, 32, 0.55);
}
.gold-label {
color: rgb(255, 224, 102);
font-size: 12px;
-unity-font-style: bold;
}
.income-label {
color: rgb(136, 204, 255);
font-size: 11px;
}
.wave-label {
color: rgb(255, 170, 170);
font-size: 11px;
-unity-font-style: bold;
background-color: rgba(58, 26, 26, 0.85);
border-width: 1px;
border-color: rgba(138, 58, 58, 1);
border-radius: 3px;
padding: 1px 7px;
}
.dimmed-label {
color: rgb(136, 136, 136);
font-size: 11px;
}
/* ---- WAVE BUFF ICONS ----------------------------------------- */
/* Small badges in the top bar showing what the spawning wave carries.
Sized to sit inside the 28px top bar without stretching it. */
.wave-buff-row {
flex-direction: row;
align-items: center;
}
.wave-buff-icon {
width: 18px;
height: 18px;
margin-right: 3px;
border-width: 1px;
border-color: rgba(138, 58, 58, 1);
border-radius: 3px;
background-color: rgba(58, 26, 26, 0.85);
}
/* Shown in place of the sprite when a card has no icon authored yet, so an
unillustrated buff is still visible and countable rather than invisible. */
.wave-buff-fallback {
-unity-text-align: middle-center;
-unity-font-style: bold;
font-size: 10px;
color: rgb(255, 170, 170);
}
/* ---- MAIN AREA ---------------------------------------------- */
.main-area {
flex: 1;
flex-direction: row;
min-height: 0;
}
/* Transparent viewport area. PickingMode set to Ignore in C#. */
.map-area {
flex: 1;
background-color: rgba(0,0,0,0);
}
.rejection-label {
position: absolute;
bottom: 60px;
left: 0;
right: 0;
-unity-text-align: upper-center;
color: rgb(255, 90, 90);
font-size: 14px;
-unity-font-style: bold;
background-color: rgba(0, 0, 0, 0.65);
padding: 4px 14px;
border-radius: 4px;
display: none;
}
/* ---- RIGHT SCOREBOARD PANEL --------------------------------- */
.right-panel {
width: 172px;
flex-shrink: 0;
align-self: flex-start;
flex-direction: column;
background-color: rgb(22, 22, 16);
border-left-width: 1px;
border-left-color: rgba(58, 58, 32, 1);
border-bottom-width: 1px;
border-bottom-color: rgba(58, 58, 32, 1);
border-radius: 0 0 0 3px;
}
.panel-header {
padding: 4px 6px;
font-size: 10px;
color: rgb(255, 224, 102);
-unity-font-style: bold;
-unity-text-align: upper-center;
background-color: rgb(42, 36, 16);
border-bottom-width: 1px;
border-bottom-color: rgba(58, 58, 32, 1);
}
.section-label {
padding: 4px 6px 2px;
font-size: 9px;
color: rgb(102, 102, 80);
}
.player-list {
flex-direction: column;
}
.income-row {
flex-direction: row;
justify-content: space-between;
padding: 2px 6px;
}
.income-key {
font-size: 10px;
color: rgb(136, 136, 112);
}
.income-val {
font-size: 10px;
color: rgb(255, 224, 102);
}
/* ---- BOTTOM UI STRIP ---------------------------------------- */
/* Five separate framed sections, left to right:
1. Minimap — tall frame, 180w x 220h
2. Portrait — short frame, 90w x 165h (bottom-aligned)
3. Info Panel — short frame, 280w x 165h
4. Tool Tip — short frame, 120w x 165h
5. Action Menu — tall frame, 320w x 220h (hides via display:none
when no actions are available for the selection)
Sections sit on a transparent strip with 100px margins on each side; the
3D scene shows through above and beside them. */
.bottom-ui {
height: 220px; /* matches the tall sections */
flex-shrink: 0;
flex-direction: row;
align-items: flex-end; /* short sections hug the bottom */
justify-content: center; /* keep the command bar centered at ANY resolution — the row
auto-centers, and re-centers symmetrically when the spell
hotbar appears/disappears */
padding: 0; /* was 0 100px; reclaimed so a smaller reference resolution
(= bigger HUD) still fits the bar without clipping */
background-color: rgba(0, 0, 0, 0);
}
/* ----- Section 1: Minimap (tall) ----- */
.minimap-frame {
width: 180px;
height: 220px;
flex-shrink: 0;
padding: 4px;
background-color: rgb(35, 30, 15);
border-width: 3px;
border-color: rgb(120, 100, 50);
border-radius: 4px;
}
/* MinimapView injects .minimap-terrain and .minimap-entities into this. */
.minimap {
flex: 1;
background-color: rgb(8, 10, 12);
overflow: hidden;
}
.minimap-terrain {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
-unity-background-scale-mode: stretch-to-fill;
}
.minimap-entities {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
}
/* ----- Section 2: Portrait (short, bottom-aligned) ----- */
.portrait-frame {
width: 90px;
height: 165px;
flex-shrink: 0;
flex-direction: column;
align-items: center;
padding: 6px;
background-color: rgb(35, 30, 15);
border-width: 2px;
border-color: rgb(80, 65, 35);
border-radius: 3px;
}
.portrait-image {
width: 74px;
height: 110px;
background-color: rgb(13, 26, 13);
border-width: 1px;
border-color: rgb(58, 90, 58);
border-radius: 3px;
flex-shrink: 0;
}
.level-label {
font-size: 11px;
color: rgb(220, 220, 200);
-unity-text-align: upper-center;
margin-top: 6px;
flex-shrink: 0;
}
/* ----- Section 3: Info Panel (short, name + stat lines) ----- */
.info-frame {
width: 280px;
height: 165px;
flex-shrink: 0;
flex-direction: column;
padding: 8px 10px;
background-color: rgb(15, 15, 15);
border-width: 2px;
border-color: rgb(80, 65, 35);
border-radius: 3px;
}
.portrait-name {
font-size: 14px;
color: rgb(255, 224, 102);
-unity-font-style: bold;
-unity-text-align: upper-left;
margin-bottom: 8px;
flex-shrink: 0;
}
.stat-lines {
flex-direction: column;
}
.stat-line {
font-size: 11px;
color: rgb(220, 220, 200);
-unity-text-align: upper-left;
margin-bottom: 2px;
}
/* Build-progress block — shown in the info panel only when a BuildSiteVisual
is selected. Thin horizontal bar with a left-anchored fill, percent below. */
.build-progress {
flex-direction: column;
margin-top: 6px;
}
.build-progress-bg {
height: 10px;
background-color: rgb(30, 30, 24);
border-width: 1px;
border-color: rgb(80, 65, 35);
border-radius: 2px;
margin-bottom: 4px;
overflow: hidden;
}
/* The fill is anchored to the LEFT side via absolute positioning + 0 right
margin set in code (width is driven each frame from progress). Color is the
same green family as the selection ring for visual consistency. */
.build-progress-fill {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 0%;
background-color: rgb(120, 220, 120);
}
.build-progress-percent {
font-size: 11px;
color: rgb(220, 220, 200);
-unity-text-align: upper-left;
}
/* ----- Section 4: Tool Tip (short, hover-driven) ----- */
.tooltip-frame {
width: 120px;
height: 165px;
flex-shrink: 0;
flex-direction: column;
padding: 8px;
background-color: rgb(15, 15, 15);
border-width: 2px;
border-color: rgb(80, 65, 35);
border-radius: 3px;
}
.tt-title {
font-size: 12px;
color: rgb(255, 224, 102);
-unity-font-style: bold;
margin-bottom: 4px;
white-space: normal;
}
.tt-desc {
font-size: 10px;
color: rgb(170, 168, 144);
white-space: normal;
}
.tt-stats {
font-size: 10px;
color: rgb(136, 170, 136);
margin-top: 3px;
white-space: normal;
}
.tt-cost {
font-size: 10px;
color: rgb(255, 224, 102);
margin-top: 4px;
}
/* ----- Section 5: Action Menu (tall, hides when no actions are available) ----- */
.action-frame {
width: 320px;
height: 220px;
flex-shrink: 0;
padding: 6px;
background-color: rgb(35, 30, 15);
border-width: 3px;
border-color: rgb(120, 100, 50);
border-radius: 4px;
}
/* Build / Paint tab row. Shown only when the builder is selected; hidden
(display:none, set from HUDController) for tower/build-site selections. */
.command-tabs {
flex-direction: row;
flex-shrink: 0;
margin-bottom: 5px;
}
.cmd-tab {
flex: 1;
height: 22px;
font-size: 11px;
-unity-font-style: bold;
color: rgb(200, 190, 160);
background-color: rgb(26, 26, 10);
border-width: 1px;
border-color: rgb(58, 58, 26);
border-radius: 3px;
margin-right: 4px;
padding: 0;
}
.cmd-tab:hover {
background-color: rgb(42, 42, 21);
border-color: rgb(106, 106, 48);
}
.cmd-tab.active {
color: rgb(255, 224, 102);
background-color: rgb(42, 38, 16);
border-color: rgb(255, 224, 102);
}
/* Command grid: 3 row-containers injected at runtime, each holding 5 buttons. */
.command-grid {
flex: 1;
flex-direction: column;
}
.cmd-row {
flex: 1;
flex-direction: row;
margin-bottom: 3px;
}
/* Button is the cell. The icon-placeholder fills the cell; hotkey and cost
labels are absolute-positioned overlays in the top-left and bottom-left
corners. (Name label was removed — name lives in the Tool Tip box on hover.) */
.cmd-btn {
flex: 1;
align-items: stretch;
justify-content: flex-start;
padding: 3px;
background-color: rgb(26, 26, 10);
border-width: 1px;
border-color: rgb(58, 58, 26);
border-radius: 3px;
margin-right: 3px;
}
/* Placeholder icon — flat grey square with embossed borders (light top/left,
dark bottom/right) to suggest a raised 3D button. Swap for background-image
when art exists. */
.cmd-icon-placeholder {
flex: 1;
background-color: rgb(140, 140, 140);
border-top-width: 2px;
border-left-width: 2px;
border-bottom-width: 2px;
border-right-width: 2px;
border-top-color: rgb(220, 220, 220);
border-left-color: rgb(220, 220, 220);
border-bottom-color: rgb(70, 70, 70);
border-right-color: rgb(70, 70, 70);
border-radius: 2px;
}
.cmd-btn:hover {
background-color: rgb(42, 42, 21);
border-color: rgb(106, 106, 48);
}
.cmd-btn.active {
background-color: rgb(42, 38, 16);
border-color: rgb(255, 224, 102);
}
.cmd-btn.unaffordable {
opacity: 0.4;
}
.cmd-btn.empty-slot {
opacity: 0.12;
}
/* Hotkey badge — top-left corner of the button, overlapping the icon. */
.cmd-hotkey {
position: absolute;
top: 2px;
left: 4px;
font-size: 11px;
color: rgb(255, 255, 255);
-unity-font-style: bold;
/* Drop-shadow-ish backdrop via semi-opaque text background for legibility. */
background-color: rgba(0, 0, 0, 0.55);
padding: 0 3px;
border-radius: 2px;
}
/* Cost badge — bottom-left corner of the icon. */
.cmd-cost {
position: absolute;
bottom: 2px;
left: 4px;
font-size: 10px;
color: rgb(255, 224, 102);
-unity-font-style: bold;
background-color: rgba(0, 0, 0, 0.55);
padding: 0 3px;
border-radius: 2px;
}
/* ----- Section 6: Spell Hotbar (short, hidden until the player has spells) ----- */
.spell-hotbar-frame {
width: 220px;
height: 165px;
flex-shrink: 0;
flex-direction: column;
padding: 8px 10px;
background-color: rgb(15, 15, 15);
border-width: 2px;
border-color: rgb(80, 65, 35);
border-radius: 3px;
}
.spell-hotbar {
flex-direction: row;
margin-top: 4px;
}
/* One granted spell. Reuses the command-grid icon/badge look (embossed grey
placeholder + top-left hotkey badge) so spells read as the same visual
language as tower/build buttons. */
.spell-slot {
width: 48px;
height: 48px;
flex-shrink: 0;
margin-right: 4px;
position: relative;
}
.spell-slot-icon {
flex: 1;
background-color: rgb(140, 140, 140);
border-top-width: 2px;
border-left-width: 2px;
border-bottom-width: 2px;
border-right-width: 2px;
border-top-color: rgb(220, 220, 220);
border-left-color: rgb(220, 220, 220);
border-bottom-color: rgb(70, 70, 70);
border-right-color: rgb(70, 70, 70);
border-radius: 2px;
}
.spell-slot-hotkey {
position: absolute;
top: 2px;
left: 4px;
font-size: 11px;
color: rgb(255, 255, 255);
-unity-font-style: bold;
background-color: rgba(0, 0, 0, 0.55);
padding: 0 3px;
border-radius: 2px;
}
/* Remaining-cooldown seconds, bottom-left — same slot as .cmd-cost. Empty
string (no text) while the spell is ready. */
.spell-slot-cooldown-label {
position: absolute;
bottom: 2px;
left: 4px;
font-size: 10px;
color: rgb(255, 224, 102);
-unity-font-style: bold;
background-color: rgba(0, 0, 0, 0.55);
padding: 0 3px;
border-radius: 2px;
}
/* Whole-slot dim while on cooldown — same opacity convention as .cmd-btn.unaffordable. */
.spell-slot.on-cooldown {
opacity: 0.4;
}