This commit is contained in:
2026-06-07 13:28:25 -07:00
parent 0df0b45163
commit 25b53cb062
25 changed files with 1573 additions and 125 deletions
@@ -0,0 +1,128 @@
using ProjectM.Simulation;
using UnityEngine;
using UnityEngine.TextCore.Text;
using UnityEngine.UIElements;
namespace ProjectM.Client
{
/// <summary>
/// Curated Synty sci-fi-soldier HUD skin — serialized <see cref="Sprite"/> + <see cref="Font"/> references
/// harvested from the InterfaceSciFiSoldierHUD / InterfaceCore packs and authored into
/// <c>Assets/_Project/Resources/HudTheme.asset</c>. Those source sprites/fonts live under
/// <c>Assets/Synty/…</c> (NOT a Resources folder), so a serialized asset like this is the build-safe way to
/// pull a curated subset into the player build (the dependency walker follows the refs). Loaded null-safe via
/// <see cref="Get"/> (mirrors <see cref="MenuUi.LoadPanelSettings"/>); EVERY consumer must null-check the theme
/// AND each field and fall back to the flat-color HUD, so a missing asset/ref never breaks (or magenta-s) the
/// HUD. Fonts are applied as cached SDF font definitions: a <see cref="FontAsset"/> is built once per font from
/// the serialized <see cref="Font"/> (crisp at any size, supports outlines) and re-built per play session (the
/// static cache is reset on play-enter, like the project's other static presentation bridges).
/// </summary>
[CreateAssetMenu(menuName = "ProjectM/HUD Theme", fileName = "HudTheme")]
public class HudTheme : ScriptableObject
{
[Header("Fonts")]
public Font DisplayFont; // Orbitron-ExtraBold — numerics + phase words (the HUD's authoritative voice)
public Font BodyFont; // Exo 2.0 SemiBold — labels
public Font BodyLightFont; // Exo 2.0 Regular — captions / hints
[Header("Panels & meters")]
public Sprite PanelBox; // 9-sliced card background (tinted per cluster)
public Sprite BarTrack; // bar fill skin
public Sprite Vignette; // radial edge gradient (low-HP / hurt flash / downed)
public Sprite Glow; // soft glow accent (selection / pulse)
public Sprite PipActive; // filled hex pip (goal meter)
public Sprite PipInactive; // empty hex pip
[Header("Status / info icons")]
public Sprite HealthIcon;
public Sprite ShieldIcon;
public Sprite ThreatIcon;
public Sprite GoalIcon;
public Sprite CooldownIcon;
public Sprite LocationBaseIcon;
public Sprite LocationExpeditionIcon;
[Header("Resource icons")]
public Sprite AetherIcon;
public Sprite OreIcon;
public Sprite BioIcon;
[Header("Structure icons")]
public Sprite TurretIcon;
public Sprite WallIcon;
public Sprite PylonIcon;
public Sprite HarvesterIcon;
public Sprite FabricatorIcon;
public Sprite ConveyorIcon;
[Header("Build-mode control glyphs")]
public Sprite KbmPlace; // LMB
public Sprite KbmCancel; // RMB
public Sprite PadPlace; // gamepad A / south
public Sprite PadCancel; // gamepad B / east
public Sprite PadRotate; // gamepad LB
public Sprite PadExit; // gamepad Menu / start
// ---- null-safe load + cache (mirrors MenuUi.LoadPanelSettings Resources idiom) ----
static HudTheme _cached;
static bool _tried;
/// <summary>The loaded theme, or null if the asset is missing. Callers MUST null-check this and each field.</summary>
public static HudTheme Get()
{
if (_tried) return _cached;
_tried = true;
_cached = Resources.Load<HudTheme>("HudTheme");
return _cached;
}
/// <summary>Icon for a <see cref="StructureType"/> byte (null → caller falls back to the structure name text).</summary>
public Sprite StructureIcon(byte type)
{
switch (type)
{
case StructureType.Turret: return TurretIcon;
case StructureType.Wall: return WallIcon;
case StructureType.Pylon: return PylonIcon;
case StructureType.Harvester: return HarvesterIcon;
case StructureType.Fabricator: return FabricatorIcon;
case StructureType.Conveyor: return ConveyorIcon;
default: return null;
}
}
// ---- cached SDF font definitions (one FontAsset per font, built once, reset per play session) ----
static FontAsset _displayFa, _bodyFa, _bodyLightFa;
static bool _displayTried, _bodyTried, _bodyLightTried;
/// <summary>Apply the display font (Orbitron) to a style, if available. No-op otherwise (stock font).</summary>
public void ApplyDisplay(IStyle style) => Apply(style, DisplayFont, ref _displayFa, ref _displayTried);
/// <summary>Apply the body font (Exo 2.0 SemiBold) to a style, if available.</summary>
public void ApplyBody(IStyle style) => Apply(style, BodyFont, ref _bodyFa, ref _bodyTried);
/// <summary>Apply the light body font (Exo 2.0 Regular) to a style, if available.</summary>
public void ApplyBodyLight(IStyle style) => Apply(style, BodyLightFont, ref _bodyLightFa, ref _bodyLightTried);
static void Apply(IStyle style, Font font, ref FontAsset fa, ref bool tried)
{
if (!tried)
{
tried = true;
if (font != null) fa = FontAsset.CreateFontAsset(font); // dynamic SDF atlas, built once per session
}
if (fa != null)
style.unityFontDefinition = new StyleFontDefinition(FontDefinition.FromSDFFont(fa));
}
// Fast-enter-playmode keeps statics alive across sessions; the runtime FontAssets are destroyed on play-exit,
// so a stale cache would reference a destroyed atlas. Reset everything so it re-loads/re-builds per session.
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
static void ResetStatics()
{
_cached = null; _tried = false;
_displayFa = _bodyFa = _bodyLightFa = null;
_displayTried = _bodyTried = _bodyLightTried = false;
}
}
}