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

WordMeaning
CanvasThe parent object every UI element lives under.
RectTransformThe UI version of Transform: a rectangle with a position and size.
AnchorThe point (or area) of the parent the element sticks to.
PivotThe element's own centre point for positioning and rotating.
TMPTextMeshPro, Unity's text component. Sharp at any size.

Canvas modes

Render ModeUse it for
Screen Space - OverlayHUDs and menus drawn on top of everything. Start here.
Screen Space - CameraUI that should get the camera's effects (like post-processing).
World SpaceSigns, health bars over enemies, in-game screens.

1. Set up a HUD that fits every screen

  1. GameObject > UI > Canvas. An EventSystem is created with it; keep that too.
  2. 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.
  3. Add a Slider (health bar) and a Text - TextMeshPro label. Click Import TMP Essentials if asked.
  4. 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.
  5. Drag the Game view to different sizes. The bar stays pinned to the top-left corner.

Anchors in one picture

Scorewide screenScorenarrow screen
The bar is pinned top-left and the score top-right, so both stay in their corners at any width.

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);
    }
}
Damage()Changed eventHealthHud.Refresh()Bar & text update
The HUD only does work when something actually changes.

Layout helpers

ComponentDoes
Horizontal / Vertical Layout GroupLines children up in a row or column with spacing.
Grid Layout GroupArranges children in a grid, like an inventory.
Content Size FitterResizes an element to fit its content (e.g. a text bubble).
Scroll ViewA scrollable list. Put a Vertical Layout Group on its Content child.

TextMeshPro tips

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

See also