Stand Out in Unity! High-Quality Text with TextMeshPro and the Secrets of Optimization

Created: 2025-12-10Last updated: 2026-07-12

From setting up TextMeshPro to rich text features and performance optimization. Achieve high-quality text display.

The outlines of enlarged text look jagged. You display Japanese and part of it turns into "□". You update the score display every frame and somehow it gets heavy — Unity text woes boil down to about these three. And all three can be solved by understanding TextMeshPro (TMP) correctly.

This article covers the SDF mechanism at the core of TextMeshPro's quality, the font asset side where Japanese display trips people up, and the performance optimization of dynamic text that games use so heavily.

Conceptual image of TextMeshPro. A clay-style game screen shows crisp large text and damage numbers, with smooth outlines even under a magnifying glass

What You'll Learn

  • SDF — the mechanism behind text that doesn't blur when scaled up
  • Creating font assets and the missing-character (□) fix for Japanese
  • Scripting TextMeshProUGUI and rich text tags
  • Reducing draw calls (sharing font assets and material presets)
  • Update methods that avoid mesh rebuilds (the every-frame-update trap)

Sponsored

The Basic Concept of TextMeshPro: What Is SDF?

What decisively sets TextMeshPro apart from the standard text component is that it renders text using a technique called SDF (Signed Distance Field).

Standard text bakes the font into a texture as a bitmap image. With that approach, scaling text up stretches the pixels, so edges blur or turn jagged.

SDF, on the other hand, stores distance-from-the-outline information in the texture. The GPU uses that distance information to draw the character's outline smoothly in real time, so no matter how much you scale up or down, you get crisp, high-resolution text. This is the foundation of TextMeshPro's "high quality."

SDF comparison. Enlarge the same "A": with the bitmap approach the edges blur into jagged steps, but with SDF the outline stays smooth
Sponsored

Installing TextMeshPro

TextMeshPro is a standard Unity package. Open Window -> Package Manager and install Text Mesh Pro from Unity Registry. To place it in a scene, choose GameObject -> UI -> TextMeshPro - Text.

Sponsored

Common Beginner Pitfalls and Solutions

1. Forgetting to Create a Font Asset

TextMeshPro can't use ordinary font files (.ttf or .otf) as-is. You must create a dedicated Font Asset.

The pitfall: You place a TMP text in the scene, but the font stays the default "Liberation Sans SDF," and setting a Japanese font doesn't take effect.

The solution:

  1. Import the font file you want to use (e.g. NotoSansJP-Regular.otf) into your project.
  2. Right-click the imported font file and choose Create -> TextMeshPro -> Font Asset.
  3. Drag and drop the generated font asset into the Font Asset slot of the TMP component.
The font asset creation flow. From a NotoSansJP.otf font file, Create > TextMeshPro > Font Asset makes an atlased Font Asset, which the TMP component displays — three steps

2. Missing Characters (Insufficient Atlas Size)

When you use a language with many characters, like Japanese or Chinese, the font asset you created may not include every character, and some show up as "□" or "?". This happens because not all the needed characters are baked into the font asset's texture (atlas).

Diagram of why characters become □. A left panel where the game screen shows "attack power □ncreased," and a right panel where that one character is missing from the font atlas cells, connected by a dotted line

The solution: Review the Character Set setting when creating the font asset.

  • Use a custom character list: Listing only the characters you need in a text file and setting it in Custom Characters is the most efficient.
  • Use a dynamic atlas: Setting Atlas Population Mode to Dynamic automatically adds needed characters to the atlas at runtime. Watch out for the performance impact and increased memory usage, though.
Sponsored

Dynamic Text Manipulation and Rich Text

TextMeshPro is easy to manipulate from C# scripts just like the standard text component, but the component type you reference is different.

1. Basic Text Updates

To manipulate a TMP component, declare using TMPro; and reference TextMeshProUGUI (for uGUI) or TextMeshPro (for 3D).

using UnityEngine;
using TMPro; // required

public class TmpTextUpdater : MonoBehaviour
{
    // Attach a TextMeshProUGUI component from the Inspector
    [SerializeField]
    private TextMeshProUGUI scoreText;

    private int currentScore = 0;

    void Start()
    {
        // Set the initial text
        UpdateScoreText();
    }

    // Method that increases the score and updates the text
    public void AddScore(int amount)
    {
        currentScore += amount;
        UpdateScoreText();
    }

    private void UpdateScoreText()
    {
        // Update the text directly
        scoreText.text = "Current Score: " + currentScore.ToString();
    }
}

Update text event-driven (when the score changes, etc.) rather than inside Update() or LateUpdate() whenever possible. Updating text every frame triggers a mesh rebuild and is a major cause of performance drops.

2. Using Rich Text Tags

One of TextMeshPro's powerful features is HTML-like rich text tags. They let you dynamically change a character's color, size, style, and more from a script.

using UnityEngine;
using TMPro;

public class TmpRichTextExample : MonoBehaviour
{
    [SerializeField]
    private TextMeshProUGUI messageText;

    // Display an important message with rich text
    public void DisplayImportantMessage(string playerName, int damage)
    {
        // Use tags like <color>, <b>, <size>
        string richMessage = $"<color=#FF0000><b>{playerName}</b></color> took <size=150%>{damage}</size> damage!";

        messageText.text = richMessage;
    }

}

Here, <color=#FF0000> changes the color, <b> makes it bold, and <size=150%> changes the size dynamically.

tips: Avoid creating continuous changes like blinking by rewriting the text property every frame (the mesh rebuild described later runs every frame). For an alpha blink, changing the messageText.alpha or color property while leaving the string alone is far lighter.

Sponsored

Secrets of Performance Optimization

TextMeshPro is more performant than standard text, but misuse can hurt performance.

1. Reducing Draw Calls

TextMeshPro's biggest optimization point is that text objects using the same font asset and material can, as much as possible, be batched into a single draw call.

Diagram of reducing draw calls by sharing a font asset. Using scattered fonts runs the draw command 3 times for 3 texts, but sharing the same Font Asset batches it into 1
  • Unify font assets: Be thorough about using the same font asset across the whole UI.
  • Share material instances: Create text color, outline, and other settings as a material preset and share it across multiple text objects to reduce draw calls.

2. Avoiding Text Rebuilds

When the text content changes, TextMeshPro rebuilds the text mesh. This is a heavy operation.

  • Minimize string changes: For frequently updated text like a score display, optimize string generation (especially string.Format and concatenation) and update only when needed.
  • Disable hidden text: For off-screen or hidden text, instead of disabling it with gameObject.SetActive(false), use textComponent.enabled = false to disable just the component, which excludes it from Unity's rendering pipeline and improves performance.
Sponsored

Hands-On: Building Floating Damage Numbers

An RPG's attack hit, a roguelike's critical, an idle game's tap effect — "a number that floats up softly and fades above the enemy's head" is a signature use of TextMeshPro across every genre. Let's assemble the SDF, rich text, and lightweight update methods so far into a single effect.

Example of floating damage numbers. The moment an attack lands, a number appears and floats upward while gradually fading — three stages

The mechanism is "spawn a 3D TextMeshPro → raise it while lowering alpha → destroy it when gone." Prepare a damage-number prefab (with a TextMeshPro component) and attach the following script.

using UnityEngine;
using TMPro;

public class DamagePopup : MonoBehaviour
{
    [SerializeField] private float floatSpeed = 1.5f;   // Rise speed
    [SerializeField] private float lifetime = 0.8f;     // Display time

    private TextMeshPro tmp;   // 3D version (not uGUI)
    private float elapsed;

    void Awake() => tmp = GetComponent<TextMeshPro>();

    // Call right after spawning to set the damage value (criticals emphasized with rich text)
    public void Setup(int damage, bool isCritical)
    {
        tmp.text = isCritical
            ? $"<size=150%><b>{damage}</b></size>"
            : damage.ToString();
    }

    void Update()
    {
        // Float upward
        transform.position += Vector3.up * floatSpeed * Time.deltaTime;

        // Don't rewrite text; only lower alpha (no mesh rebuild runs)
        elapsed += Time.deltaTime;
        tmp.alpha = 1f - (elapsed / lifetime);

        if (elapsed >= lifetime) Destroy(gameObject);
    }
}

The caller just Instantiates the prefab where the enemy took damage and calls Setup(damage, isCritical).

Two points matter most.

  • Rewrite text only once, at spawn: Do the fade with tmp.alpha. Rewriting the text property every frame runs a mesh rebuild every frame, but changing alpha (vertex color) causes no rebuild. It's the practical form of the "update optimization" this article covered.
  • Games with lots of numbers should move to pooling: Instantiate/Destroy on every consecutive hit causes GC spikes. As the volume grows, the standard evolution is to move onto the object pooling mechanism.

Bonus: Good Things to Know in Advance

  • Fallback fonts: Characters not in your main font asset (emoji, other languages) are automatically looked up from another asset registered in Fallback Font Assets. It's practically mandatory for a Japanese + emoji combination.
  • Key Font Asset Creator settings: When making a Japanese asset, the balance of Sampling Point Size (the character's source resolution), Atlas Resolution (the atlas size, 2048–4096 for Japanese), and Padding determines quality. Start with Auto Sizing + 2048.
  • Text in 3D space: Separate from the uGUI TextMeshProUGUI, there's TextMeshPro (GameObject > 3D Object > Text - TextMeshPro) that you can place directly in 3D space. Overhead damage numbers and signboards use this one.
  • To revisit the UI foundation: For the basics of the Canvas and anchors where text is placed, see the UI basics guide.

Summary

  1. Understand SDF and create font assets: TextMeshPro's high-quality rendering comes from SDF, and the font you use must be created and set up as a dedicated font asset.
  2. Prevent Japanese missing characters with atlas settings: Handle it with a custom character list or Atlas Population Mode: Dynamic.
  3. Use rich text tags: Using tags like <color>, <size>, and <b> dynamically from C# scripts easily achieves an expressive UI.
  4. Be thorough about draw call optimization: Sharing the same font asset and material preset across multiple text objects to minimize rendering load is essential for maintaining performance in large-scale UI.
  5. Optimize dynamic updates: For frequently updated text, avoid rewriting text every frame, do fades with alpha, and minimize mesh rebuilds.

Start by creating one Japanese font asset, and get hands-on with a small effect like damage numbers.