Lesson 6: UI Flow - Menus, HUD, and Feedback

Gameplay without feedback feels broken even when the simulation is perfect. This lesson gives you a HUD that tracks health or score, a pause flow that respects your Input Actions from Lesson 4, and lightweight hit feedback players can read in a split second.

Lesson Objective

By the end of this lesson you will have:

  1. A Screen Space - Overlay canvas for HUD elements and another for menus (or a single canvas with clear hierarchy)
  2. TextMeshPro labels bound to gameplay data through events or a small UI bridge script
  3. Pause that disables the gameplay action map and enables a UI action map using the Input System UI Input Module

Why This Matters

Menus are state machines. If pause only hides the panel but leaves the player motor running, QA files a bug. If UI uses the wrong input module, builds fail while the Editor feels fine. Nail the plumbing now so Lessons 7–9 can focus on data, audio, and juice.

Step-by-Step

Step 1: Install TextMeshPro essentials

  1. When Unity prompts, import TMP Essentials (Window > TextMeshPro > Import TMP Essential Resources).
  2. Create fonts under Assets/_Project/UI/Fonts so later skinning stays organized.

Step 2: Build the HUD canvas

  1. Create UI > Canvas. Set Render Mode to Screen Space - Overlay.
  2. Add a Canvas Scaler with Scale With Screen Size (reference 1920x1080 or your target).
  3. Child a Panel anchored top-left with:
    • TMP_Text for health (HUD_HealthText)
    • Optional TMP_Text for score or wave counter

Keep contrast high. Indie readability beats fancy gradients.

Step 3: Bridge gameplay to UI without tight coupling

Expose events from your player or health component:

public event Action<int, int> HealthChanged; // current, max

public void TakeDamage(int amount)
{
    _hp = Mathf.Max(0, _hp - amount);
    HealthChanged?.Invoke(_hp, _maxHp);
}

Create HudBinder.cs on the canvas that subscribes in OnEnable and unsubscribes in OnDisable.

Common mistake: Using FindObjectOfType every frame. Cache references in Awake or inject via a small GameBootstrap you already created in Lesson 2.

Step 4: Pause menu stack

  1. Duplicate canvas structure or use one canvas with a MenuRoot panel set inactive by default.
  2. Map your Pause action (Lesson 4) to toggle visibility and flip Time.timeScale between 0 and 1 if your design uses global pause.
  3. When paused, disable the Player action map and enable a UI map with Navigate/Submit/Cancel bound.

Pro tip: Prefer two action maps over one giant map; it mirrors how shipped games isolate gameplay versus menu focus.

Step 5: Input System UI Input Module

  1. Ensure an Event System exists (UI > Event System).
  2. Replace Standalone Input Module with Input System UI Input Module.
  3. Point the module at the same Input Action Asset your player uses, specifically the UI action map.

If gameplay works but buttons ignore clicks in builds, revisit Unity New Input System Actions Not Working in Build Only.

Step 6: Feedback pass (non-VFX)

Before particles in Lesson 10, add:

  • Color flash on damage (simple Image tint coroutine)
  • Screen-edge vignette optional via UI Image alpha
  • Button hover/press animations using Animator or DOTween if you already depend on it

Keep each effect under ~200 ms so it reads as feedback, not a cutscene.

Step 7: Sorting and safe areas

  1. Set Sort Order higher on menu canvas than HUD if split.
  2. For mobile later, add Safe Area padding component or anchor script; even if you are desktop-first, document TODO in your task board.

Mini Challenge

Add a Settings submenu stub with a single Master Volume slider wired to AudioMixer exposed parameter (you will flesh out audio in Lesson 9). Focus on navigation with keyboard/gamepad, not perfect mix values.

Pro Tips

  • Namespace UI scripts under UI/ and keep them free of physics calls.
  • Use Object pooling for floating combat text only if spammy; otherwise TMP clones are fine for prototypes.
  • Snapshot UI hierarchy as a prefab UI_Canvas_Gameplay so scenes stay identical between testers.

Common Mistakes

  • Leaving GraphicRaycaster off the canvas that should receive clicks
  • Forgetting to re-enable gameplay map after closing pause
  • Mixing legacy Input API inside UI scripts when project is New Input only

Troubleshooting

Buttons highlight but never submit

Check Submit action binding and that the selected object is not null (add EventSystem.current.SetSelectedGameObject on menu open).

HUD shows zero at start

Subscribe after player Start completes or use Script Execution Order to ensure health initializes before UI binds.

Pause locks the game

You paused time but not audio or physics queries; decide explicitly what systems respect timeScale.

Recap

You separated HUD data from presentation, built a pause/UI map workflow, and aligned Event System with the Input System so player builds match Editor behavior.

Next Lesson Teaser

Lesson 7 formalizes your data model with ScriptableObjects so stats, loot tables, and UI strings share one source of truth.

FAQ

Should I use UI Toolkit instead of uGUI?
Either is valid in 2026. This course sticks to uGUI + TMP for maximum third-party tutorial compatibility.

Do I need Cinemachine for UI?
No. Keep camera work separate unless you are framing diegetic HUD elements.

What about localization?
Use String Tables or a simple CSV pipeline later; for now, centralize strings in ScriptableObjects to ease the migration.

Related Links

Bookmark this lesson when you start polishing feel; UI bugs are the fastest way to lose trust during a first playtest.