# ADR-0007: HUD Implementation — uGUI over UI Toolkit

**Status:** Accepted  
**Date:** 2026-06-23  
**Deciders:** Mahmoud Aglan  
**Engine:** Unity 6 (URP 17.3)  

---

## Context

IStalk has two HUD systems: Runner HUD (SYS-26) and Eye HUD (SYS-27). Both are owner-only (only the local player sees their own HUD), update at varying frequencies (per-frame for weight/abilities, per-event for inventory, per-second for timer), and require animated transitions (extraction bar slide-in, phase banners, charge ring fill).

Unity offers two UI systems:
1. **uGUI (UnityEngine.UI)** — Canvas-based, GameObject-driven, mature and stable.
2. **UI Toolkit (UIElements)** — Retained-mode, USS/UXML-based, newer, designed for editor-like UIs.

---

## Decision

**Use uGUI (Canvas + Image + TMP_Text) with Screen Space Overlay for all in-game HUDs. Use DOTween for animations.**

---

## Rationale

| Criterion | uGUI (chosen) | UI Toolkit |
|-----------|---------------|------------|
| Runtime game HUD maturity | Battle-tested for 10+ years in shipped games | Primarily designed for editor/tools UI; runtime game HUD support is newer and less documented |
| Animation support | DOTween integrates natively (RectTransform, CanvasGroup, Image.fillAmount) | USS transitions are limited; custom C# animation needed for complex sequences |
| DOTween compatibility | First-class — `DOFade()`, `DOFillAmount()`, `DOAnchorPos()`, `DOScale()` | No direct DOTween support for UIElements — requires manual property binding |
| Cursor-space UI (Eye charge ring) | `rectTransform.position = Input.mousePosition` — trivial | Requires `style.left/top` manipulation or absolute positioning — more verbose |
| FishNet integration | Standard MonoBehaviour — works naturally with NetworkBehaviour lifecycle | Requires separate VisualElement management code outside the NetworkBehaviour pattern |
| Team familiarity | Universal Unity knowledge | Newer, requires learning USS/UXML |
| Performance at HUD scale | Trivial — 20-50 elements, one canvas, negligible overhead | Also trivial at this scale — no advantage |

**UI Toolkit advantages that don't apply here:**
- Responsive layout for complex menus (we have minimal HUD, not complex panels).
- Style reuse via USS (our HUDs are bespoke, not reusable components).
- Better for editor extensions (irrelevant for runtime HUD).

---

## Implementation Pattern

### Canvas Setup

```
Hierarchy per player:
  RunnerHUD (Canvas - Screen Space Overlay, Sort Order 0)
    WeightMeter (Image, fillAmount driven)
    AbilityIcons (horizontal layout)
    InventoryStrip (horizontal layout, dynamic children)
    PickupPrompt (CanvasGroup for fade)
    ExtractionBar (anchored bottom, slides in/out)
    MatchTimer (top-center)
    PhaseBanner (top-center, DOTween scale+fade)

  EyeHUD (Canvas - Screen Space Overlay, Sort Order 0)
    ChargeRing (Image, fillAmount, follows mouse position)
    RunnerStatusPanel (vertical layout, dynamic rows)
    ExtractionAlert (top banner, slides down)
    PhaseBanner (top-center)
    DreadMeter (stub - grey, ALPHA label)
    IncarnationList (stub - grey, ALPHA label)
```

### Owner-Only Initialization

HUDs are only activated for the owning player. Non-owners never see another player's HUD.

```csharp
public class RunnerController : NetworkBehaviour
{
    [SerializeField] private RunnerHUD _hudPrefab;
    private RunnerHUD _hud;

    public override void OnStartClient()
    {
        base.OnStartClient();
        if (!IsOwner) return;

        _hud = Instantiate(_hudPrefab);
        _hud.Initialize(this);
    }

    public override void OnStopClient()
    {
        base.OnStopClient();
        if (_hud != null) Destroy(_hud.gameObject);
    }
}
```

The HUD prefab starts with `gameObject.SetActive(false)` and is activated inside `Initialize()`. This prevents any `Update()` or `OnEnable()` code from running before the HUD has a valid reference to its player controller.

### Update Strategy

| Data Source | Update Frequency | Mechanism |
|-------------|-----------------|-----------|
| Weight/tier | Per-frame | `Update()` reads `WeightComponent.CurrentWeight` |
| Ability availability | Per-frame | `Update()` reads `RunnerController.AirDashAvailable` etc. |
| Pickup prompt | Per-frame | `Update()` checks `RunnerInteractor.NearestInteractable` |
| Inventory | On-change | `PlayerInventory.CarriedItems.OnChange += RebuildInventoryStrip` |
| Match timer | Per-second | `InvokeRepeating("UpdateTimer", 0f, 1f)` |
| Extraction bar | On-event + per-frame | SyncVar change starts bar; `Update()` animates fill during channel |
| Charge ring (Eye) | Per-frame | `Update()` positions at `Input.mousePosition`, sets `fillAmount` from `EyeLaserController.ChargeProgress` |

### DOTween Animation Examples

```csharp
// Extraction bar slide-in from bottom
_extractionBar.DOAnchorPosY(targetY, 0.3f).SetEase(Ease.OutBack);

// Phase banner: scale up + fade in, hold, then fade out
_phaseBanner.transform.DOScale(1f, 0.2f).SetEase(Ease.OutElastic);
_phaseBanner.GetComponent<CanvasGroup>().DOFade(1f, 0.2f)
    .OnComplete(() => DOVirtual.DelayedCall(2f, () =>
        _phaseBanner.GetComponent<CanvasGroup>().DOFade(0f, 0.5f)));

// Eye extraction alert: screen flash + banner slide
_flashOverlay.DOFade(0.3f, 0.05f).SetLoops(2, LoopType.Yoyo);
_alertBanner.DOAnchorPosY(0f, 0.25f).SetEase(Ease.OutQuart);
```

---

## Consequences

**Positive:**
- Immediate productivity — no learning curve, no new tooling.
- DOTween provides professional-quality animations with minimal code.
- Cursor-space charge ring is trivial (`rectTransform.position = Input.mousePosition`).
- Owner-only pattern is clean and prevents cross-player HUD bugs.
- HUD is destroyed with the player's NetworkObject on despawn — no cleanup needed.

**Negative:**
- uGUI Canvas rebuild cost if too many dynamic elements change per frame. At MVP scale (< 50 elements, < 5 changing per frame), this is negligible.
- No style inheritance — each element's colors/fonts are set individually. Acceptable for 2 bespoke HUDs.
- DOTween is a third-party dependency. Well-maintained, MIT-licensed, widely used — low risk.

---

## GDD Requirements Addressed

- TR-UI-001: uGUI Canvas (Screen Space Overlay) for all HUDs
- TR-UI-002: DOTween for HUD animations

---

## References

- `design/gdd/sys-26-runner-hud.md` — Runner HUD specification
- `design/gdd/sys-27-eye-hud.md` — Eye HUD specification
- `design/gdd/sys-33-fishnet-replication.md` — NetworkBehaviour lifecycle (OnStartClient owner check)
