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:
- A Screen Space - Overlay canvas for HUD elements and another for menus (or a single canvas with clear hierarchy)
- TextMeshPro labels bound to gameplay data through events or a small UI bridge script
- 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
- When Unity prompts, import TMP Essentials (Window > TextMeshPro > Import TMP Essential Resources).
- Create fonts under
Assets/_Project/UI/Fontsso later skinning stays organized.
Step 2: Build the HUD canvas
- Create UI > Canvas. Set Render Mode to Screen Space - Overlay.
- Add a Canvas Scaler with Scale With Screen Size (reference 1920x1080 or your target).
- Child a Panel anchored top-left with:
- TMP_Text for health (
HUD_HealthText) - Optional TMP_Text for score or wave counter
- TMP_Text for health (
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
- Duplicate canvas structure or use one canvas with a MenuRoot panel set inactive by default.
- Map your Pause action (Lesson 4) to toggle visibility and flip Time.timeScale between
0and1if your design uses global pause. - When paused, disable the
Playeraction map and enable aUImap 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
- Ensure an Event System exists (UI > Event System).
- Replace Standalone Input Module with Input System UI Input Module.
- 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
Imagetint 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
- Set Sort Order higher on menu canvas than HUD if split.
- 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_Gameplayso 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
InputAPI 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
- Lesson 5: Enemy Behaviors and Encounter Design
- Unity Game Engine guide
- Game UI Animation - Smooth Interface Transitions
- Unity New Input System Actions Not Working in Build Only - How to Fix
Bookmark this lesson when you start polishing feel; UI bugs are the fastest way to lose trust during a first playtest.