using ProjectM.Simulation; using UnityEngine; using UnityEngine.TextCore.Text; using UnityEngine.UIElements; namespace ProjectM.Client { /// /// Curated Synty sci-fi-soldier HUD skin — serialized + references /// harvested from the InterfaceSciFiSoldierHUD / InterfaceCore packs and authored into /// Assets/_Project/Resources/HudTheme.asset. Those source sprites/fonts live under /// Assets/Synty/… (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 /// (mirrors ); 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 is built once per font from /// the serialized (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). /// [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; /// The loaded theme, or null if the asset is missing. Callers MUST null-check this and each field. public static HudTheme Get() { if (_tried) return _cached; _tried = true; _cached = Resources.Load("HudTheme"); return _cached; } /// Icon for a byte (null → caller falls back to the structure name text). 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; /// Apply the display font (Orbitron) to a style, if available. No-op otherwise (stock font). public void ApplyDisplay(IStyle style) => Apply(style, DisplayFont, ref _displayFa, ref _displayTried); /// Apply the body font (Exo 2.0 SemiBold) to a style, if available. public void ApplyBody(IStyle style) => Apply(style, BodyFont, ref _bodyFa, ref _bodyTried); /// Apply the light body font (Exo 2.0 Regular) to a style, if available. 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; } } }