UI & TextMeshPro in Depth
Build menus, health bars and text labels that stay in the right place on any screen size.
Think of a noticeboard. The Canvas is the board. Each UI element is a note pinned to it. Anchors are the pins: they decide which corner or edge a note sticks to when the board gets bigger or smaller. TextMeshPro is the pen that writes on the notes.
Words to know
| Word | Meaning |
|---|---|
| Canvas | The parent object every UI element lives under. |
| RectTransform | The UI version of Transform: a rectangle with a position and size. |
| Anchor | The point (or area) of the parent the element sticks to. |
| Pivot | The element's own centre point for positioning and rotating. |
| TMP | TextMeshPro, Unity's text component. Sharp at any size. |
Canvas modes
| Render Mode | Use it for |
|---|---|
| Screen Space - Overlay | HUDs and menus drawn on top of everything. Start here. |
| Screen Space - Camera | UI that should get the camera's effects (like post-processing). |
| World Space | Signs, health bars over enemies, in-game screens. |
1. Set up a HUD that fits every screen
- GameObject > UI > Canvas. An EventSystem is created with it; keep that too.
- On the Canvas's Canvas Scaler: set UI Scale Mode to Scale With Screen Size, Reference Resolution 1920 × 1080, Match 0.5. Now your UI grows and shrinks with the screen.
- Add a Slider (health bar) and a Text - TextMeshPro label. Click Import TMP Essentials if asked.
- Select the Slider, open the Anchor Presets box (top-left of the RectTransform) and pick top-left. Hold Alt+Shift while clicking to also move the pivot and position there.
- Drag the Game view to different sizes. The bar stays pinned to the top-left corner.
Anchors in one picture
If both anchor handles are in the same spot, the element keeps its size and sticks to that point. Drag them apart and it stretches with its parent, which is handy for backgrounds and panels.
2. Change text from code
using TMPro;
using UnityEngine;
public class CoinLabel : MonoBehaviour
{
[SerializeField] private TMP_Text label;
public void ShowCoins(int coins)
{
label.SetText("Coins: {0}", coins); // {0} is replaced by coins
}
}
Use the TMP_Text type for your field. It accepts both UI text and 3D world text. SetText with {0} avoids creating garbage every time the number changes.
3. Update the HUD only when health changes
Instead of setting the text every frame, let the health script announce changes, like a doorbell. The HUD listens and updates only when the bell rings. (More on this in Delegates & Events.)
using System;
using UnityEngine;
public class Health : MonoBehaviour
{
[SerializeField] private int max = 100;
public int Current { get; private set; }
public int Max => max;
public event Action Changed; // the doorbell
void Awake() => Current = max;
public void Damage(int amount)
{
Current = Mathf.Max(0, Current - amount);
Changed?.Invoke(); // ring it
}
}
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class HealthHud : MonoBehaviour
{
[SerializeField] private Health health;
[SerializeField] private Slider bar;
[SerializeField] private TMP_Text label;
void OnEnable() { health.Changed += Refresh; Refresh(); } // start listening
void OnDisable() { health.Changed -= Refresh; } // stop listening
void Start() { Refresh(); } // every Awake has run by now, so Current is set
void Refresh()
{
bar.value = (float)health.Current / health.Max; // slider range 0 to 1
label.SetText("{0} / {1}", health.Current, health.Max);
}
}
Layout helpers
| Component | Does |
|---|---|
| Horizontal / Vertical Layout Group | Lines children up in a row or column with spacing. |
| Grid Layout Group | Arranges children in a grid, like an inventory. |
| Content Size Fitter | Resizes an element to fit its content (e.g. a text bubble). |
| Scroll View | A scrollable list. Put a Vertical Layout Group on its Content child. |
TextMeshPro tips
- Missing characters (□)? The font doesn't include them. Add a fallback font in the TMP font asset.
- Long text? Choose what happens in Overflow: wrap, truncate or ellipsis ("…").
- Rich text:
<b>bold</b>,<color=red>red</color>. Turn it off for player names so nobody can inject tags. - Auto Size is handy but costs performance. Set a min and max.
Menus and clicking
- Buttons need an EventSystem in the scene. With the new Input System, its module should be InputSystemUIInputModule.
- Turn off Raycast Target on decorative images and labels, so they don't block clicks to buttons underneath.
- To hide a panel, use
SetActive(false), or a CanvasGroup with alpha 0 and Blocks Raycasts off. Alpha alone leaves an invisible panel that still eats clicks. - For gamepad users, select a default button when a menu opens:
EventSystem.current.SetSelectedGameObject(firstButton).
Performance in one line
When anything on a Canvas changes, Unity redraws that Canvas. Put fast-changing UI (timers, health) on its own child Canvas so the static parts aren't redrawn with it.
Common Mistakes
- Setting text in
Updateeven when nothing changed. - Subscribing to an event (
+=) but never unsubscribing (-=). - Everything anchored to the centre, so the HUD drifts on wide or tall screens.
- Two EventSystems in the scene (e.g. after loading a second scene with its own).
See also
This page is a simplified, AI-assisted interpretation. For the complete and authoritative reference, see the official documentation.