Unity Font: The 2026 Complete Guide to TextMesh Pro Font Assets, Fallbacks, and Localization

2025-10-31

Font Unity Hollow Knight Silksong Hero

Table of Contents

Summary: Unity font issues in CJK are common but are rarely due to bad translations. Instead, they typically happen because of fallback problems—TextMesh Pro stops at the first matching glyph. For instance, if a Japanese font sits above a Simplified Chinese one, you get JP-looking characters in a CN UI.

To avoid these issues, you can create per-language TMP font assets (SC/TC/JP/KR), put the correct regional font first in the fallback chain, and wire runtime switching through Unity’s localization package.

You’re deep in Hollow Knight: Silksong. A notification flashes by. You read, you move on.

Then, you double-take.

Something in the Chinese text feels… off. 门 looks unfamiliar. 将 has a different tilt. You zoom in, blink, scroll back. For a second, you can’t place it.

And then it clicks. It’s the font!

That tiny mismatch breaks the spell. Not a crash. Not a bug report. Just a hairline fracture in immersion that pulls the player out of the world you built.

And it isn’t a translation issue at all—it’s typography.

TextMesh Pro is doing exactly what it was told: grab the first glyph it finds in the fallback list. If a Japanese font sits above a Simplified Chinese one, Unity uses the JP form and never looks further.

The fix isn’t magic, but it is specific: clean fallback order, per-language font assets, and a right-sized atlas strategy.

In this guide, we’ll show you how to prevent that “Japanese-ish” look from sneaking into Chinese text—so players read what you meant, the way they expect.

Why your “translation” looks wrong (and quick fixes)

It’s not the translator; it’s your font. TextMesh Pro grabs glyphs in order and stops at the first match.

When a Japanese font sits above a Simplified Chinese font, you get Japanese drawings of Chinese characters. That’s why your text suddenly takes on a “Japaneseish vibe.

Unity meant well. The fallback system keeps atlases lean and load times snappy. It just wasn’t designed with CJK nuance in mind.

Fortunately, once you know what’s happening, it’s easy to fix. Here’s a triage box you can apply today:

Quick fix Description
Reorder your fallback list In the TMP font asset inspector or global settings, drag your Simplified Chinese font above the Japanese one. TMP searches fallbacks in the order listed and stops when it finds a glyph, so make sure the correct regional font is searched first.
Use per‑language TMP font assets Create separate font assets (and atlases) for Simplified Chinese, Traditional Chinese, Japanese and Korean. Avoid mixing Japanese fallbacks into your Chinese assets.
Adjust atlas size or use dynamic atlases Static atlases can run out of space quickly for CJK languages. Increase the atlas texture size, enable multi‑atlas support, or temporarily switch to dynamic atlases to avoid missing glyphs (“tofu”).

The key takeaway here is that not all CJK fonts are interchangeable. Han characters have region-specific shapes.

Modern families like Noto Sans and Source Han Sans ship separate variants for Simplified Chinese (SC), Traditional Chinese (TC), Japanese (JP), and Korean (KR). If your fallback order ignores that, your game will show the wrong glyphs.

So how does TextMesh Pro actually paint text? Let’s demystify it.

How TMP actually renders text: Font assets, atlases and fallback chains

TextMesh Pro doesn’t draw straight from a .ttf or .otf. It uses font assets:

  1. A source font.
  2. A character table.
  3. One or more atlas textures.

Each glyph is baked into an atlas alongside metadata about its shape and placement. When TMP needs to draw a character, it looks up the glyph in the

Static vs dynamic (and OS-referenced dynamic)

Font assets can be created with different atlas population modes:


1. Static font assets

You preselect characters and bake them into an atlas at build time. As you can tell by the name, static assets don’t need the original font file at runtime.

They’re fast and predictable, which is ideal for menus, HUDs and any text that won’t change. The trade-off?

You must include every glyph you’ll ever need—a tall order for CJK.

2. Dynamic font assets

It starts empty, and the TMP adds glyphs on demand from the source font file.

Dynamic fonts are flexible. You can type in user names or chat messages, and the TMP will add new glyphs on the fly. But they carry extra overhead. The source font file must be shipped with your build, and dynamic glyph additions can fragment your atlas.


3. Dynamic asset referencing an OS font

Similar to dynamic, but the font comes from the operating system rather than your project. It saves build size, but it depends on the fonts installed on the player’s machine. It’s best used as a fallback, not your primary CJK source.

In practice, most teams mix them:
  1. Static for known UI strings
  2. A small dynamic fallback for user-generated surprises.

If you rely solely on dynamic fonts, performance may suffer, and your build will include entire font files for every locale.

Fallback chain: “First glyph wins”

Since a single atlas can’t fit tens of thousands of glyphs, TMP uses a fallback system. Each font asset can have a list of fallback fonts, and there’s also a global list in the TMP settings. The fallback chain works like this:

  1. TMP looks up the character in the text object’s primary font asset.
  2. If it’s missing, TMP searches the font’s local fallback list in order. This search is recursive, i.e., fallbacks can have their own fallbacks.
  3. If still missing, TMP checks the text object’s sprite asset and the global fallback list.

The search stops at the first match. Order decides which regional glyph you get. Put a JP font above an SC font, and shared characters will render with JP shapes. Put SC first and you get SC.

It’s a simple rule with outsized consequences.

Atlases and performance considerations

A typical CJK font contains tens of thousands of glyphs. Fitting them into a single atlas is unrealistic, so keep it practical:

1. Use multi‑atlas support

Modern TMP versions can automatically create more atlases when the first fills up. Choose a texture size appropriate for your target platform (e.g., 2048² for mobile, 4096² for PC/console, but keep the memory budget in mind).

2. Limit your character set

If each locale only needs a few hundred unique characters, bake just those into a static asset. Tools like I2 Localization or your own script can extract the unique characters from your translated strings and generate a subset font.

3. Avoid cross‑loading unnecessary fonts

Each fallback referenced by a primary font asset adds overhead. TMP loads the atlas textures for all fallback assets it encounters while searching. Don’t include Japanese fonts as fallbacks for Chinese assets or vice versa unless you genuinely need to mix languages.

Regional variants: SC, TC, JP and KR

While Unicode unifies many Chinese, Japanese, and Korean characters under the same code points, fonts decide the regional glyph shape. That’s why two builds can show the “same” character differently. Here’s a quick comparison to show why your fallback order matters:

Character (Unicode) Simplified Chinese (SC) Japanese (JP) Korean (KR)
门 / 門 (U+95E8) Simplified radical appears (簡化) radical appears as two vertical strokes joined by a horizontal line. In some Japanese fonts it’s drawn with a small stroke in the middle (民间略字), giving the impression of an extra pillar Traditional form with balanced vertical strokes.
将 / 將 (U+5C06) Top‑right component is written with two strokes (丶 over 冫). The same component is written as a single hooked stroke, reflecting Japanese simplification. Traditional component similar to SC but more calligraphic.
关 / 關 (U+5173) Simplified Chinese combines the gate radical and the character 天. Japanese fonts draw the gate radical differently, often resembling the traditional form. Korean fonts use the traditional character 關 because Korean rarely uses Simplified Chinese.

Subtle? Yes, but players notice.

Mainland Chinese players expect SC glyphs, Japanese players expect JP glyphs, and Korean CJK fonts typically follow traditional-style forms.

In some Japanese fonts, you’ll even see a folk simplified (民间略字) version of 门, but it still uses the same Unicode code point (U+95E8) as the Chinese simplified 门. So, if Unity/TMP hits the JP font first, that JP-style 门 shows up in a Chinese UI.

Pick the regional variant that matches your audience and make sure it sits first in the fallback chain.

The right architecture: Per‑language font assets and clean fallbacks

Now that we’ve gotten the plumbing out of the way, let’s set a robust architecture. The goal? Big and appropriate CJK sets without sacrificing performance.

One primary asset per locale, plus a Latin fallback

Create one primary font asset for each language you support (Font_SC, Font_TC, Font_JP and Font_KR). To do so, follow these steps:

  1. Using TMP Font Asset Creator, bake only the characters your locale actually uses into a static atlas (extract uniques from your localized strings).
  2. Add a small Latin/symbol fallback for numbers and UI chrome. Place it after the locale font so Han glyphs never come from the Latin font.

And for the last time, don’t mix languages in the same fallback chain!

But, if, for some reason, you need to do so, i.e., Japanese character names inside Chinese text, render that run with a separate TMP component (or a <font=”…”> tag) that points to the JP asset.

Choosing families (Noto/Source Han) and licensing notes

Google’s Noto Sans CJK and Adobe’s Source Han Sans families are popular because they’re comprehensive and open source. Each family includes regional variants that you can pick from:

  • NotoSansSC/SourceHanSansCN for Simplified Chinese.
  • NotoSansTC/SourceHanSansTW and SourceHanSansHK for Traditional Chinese.
  • NotoSansJP/SourceHanSansJP for Japanese.
  • NotoSansKR/SourceHanSansKR for Korean.

Both families fall within the SIL Open Font License 1.1, so you can use them liberally. But you still need to include the license file with your build. If you use a commercial font, budget time and cost for a game-distribution license.

Runtime switching with the Localization package

Unity’s official localization package lets you switch text and assets based on the player’s selected locale. One of its hidden gems is the Localized Property Variants system, which lets you change component properties (e.g., font assets) per locale directly in the Inspector.

If you prefer code, the minimal script below listens for locale changes and swaps the font on your TMP_Text components. Assign the per-language font assets in the Inspector; the script handles the rest.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
                    <xmp>using TMPro;
using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.Localization.Settings;

public class FontRouter : MonoBehaviour
{
    [SerializeField] private TMP_Text[] targets;

    [Header("Primary TMP Font Assets per language")]
    [SerializeField] private TMP_FontAsset fontLatin;
    [SerializeField] private TMP_FontAsset fontSC;
    [SerializeField] private TMP_FontAsset fontTC;
    [SerializeField] private TMP_FontAsset fontJP;  
    [SerializeField] private TMP_FontAsset fontKR;  

    void OnEnable()
    {
        LocalizationSettings.SelectedLocaleChanged += OnLocaleChanged;
        Apply(LocalizationSettings.SelectedLocale);
    }

    void OnDisable()
    {
        LocalizationSettings.SelectedLocaleChanged -= OnLocaleChanged;
    }

    void OnLocaleChanged(Locale loc) => Apply(loc);

    void Apply(Locale loc)
    {
        if (targets == null || targets.Length == 0) return;

        TMP_FontAsset primary = fontLatin;
        var code = loc?.Identifier?.Code ?? "en";

        if (code.StartsWith("zh-Hans")) primary = fontSC;
        else if (code.StartsWith("zh-Hant")) primary = fontTC;
        else if (code.StartsWith("ja"))      primary = fontJP;
        else if (code.StartsWith("ko"))      primary = fontKR;

        if (primary != null)
        {
            primary.fallbackFontAssetTable.Clear();
            if (fontLatin != null) primary.fallbackFontAssetTable.Add(fontLatin);
        }

        foreach (var t in targets)
        {
            if (t == null || primary == null) continue;
            t.font = primary;
            t.ForceMeshUpdate();
        }
    }
}
</xmp>

A few notes:

  • Prefer Property Variants if your team is already using the Localization package. It keeps font routing declarative and visible in the Inspector.
  • If you do it in code, assign fonts explicitly per locale (as above) and keep fallback lists short. We’ll add automated checks for this in the LQA/CI section so it doesn’t regress later.

Now that we are clear on which font is used and when, let’s deal with the system with Unicode and layout rules so good fonts don’t break on bad characters.

Edge cases you’ll actually hit: Unicode and layout

You’ve set up per‑language fonts and nailed your fallback order. Great! But the font journey isn’t over. There are still tiny characters and layout quirks waiting to trip you up.

Invisible troublemakers

Zero-width space (U+200B) and zero-width no-break space (U+FEFF)

These special Unicode characters glue or separate graphemes. In some languages, they’re essential for ligatures; in CJK, they’re often invisible but can wreak havoc on search or rendering. Use a text editor that shows invisible characters when debugging.


Non-break space (U+00A0) and narrow non-break space (U+202F)

One stray non-break space (NBSP) can cause an overflow or prevent a line from wrapping. If a line won’t break, check for these first.


Zero-width joiner (U+200D) and non-joiner (U+200C)

Unicode includes many compatibility characters for backward compatibility with older standards. Modern fonts support them, but mixing compatibility forms with standard characters can break sorting or searching. In CJK, they’re usually accidents that can confuse search or glyph fallback. Remove them unless you need them for a very specific reason.

Compatibility ideographs and normalization

Unicode includes compatibility ideographs and half-width variants for legacy reasons. Mixing them with modern forms can break search, sort, or deduping.

To avoid this issue, normalize incoming text to NFC (or NFKC if you prefer compatibility folding) during your import and validation steps.

Han unification and variation selectors

Unicode encodes an abstract Han character once, and fonts render it in the regional shape. That’s why the same code point can look different in SC/TC/JP/KR.

If you must force a specific form, Ideographic Variation Sequences (IVS) can request a particular glyph (provided your font supports that sequence). The concern with IVS is that support is patchy, so use it as a last resort.

The ideal path is to use the right regional font first in your fallback chain.

Full‑width or half‑width punctuation?

Chinese and Japanese typically use full-width punctuation (, 。 、 : 「 」 『 』 ( ) 《 》) to match the square rhythm of Han characters. Half-width marks (, . ! ?) can look pasted-in or misaligned.

Korean is slightly more flexible and may use mixed half-width punctuation with Hangul. Regardless, pick one style per locale and stick with it.

Line breaking for Asian languages

By default, TMP uses Western line‑breaking rules. But these rules won’t work for CJK.

Thankfully, TMP’s settings include a Line Breaking for Asian Languages section, so certain characters don’t start or end a line (e.g., commas and closing quotes).

Test long paragraphs in each locale. What looks fine in English can stumble in Chinese or Japanese until kinsoku-style rules are set.

QA that saves launches: LQA and CI guardrails

With fonts and edge cases sorted, it’s time to test. Would you ship a game without playtesting? Of course not. Fonts deserve the same love. Here is what to test:

  1. Coverage: Extract the unique characters per locale from your string tables and compare against the assigned font assets. You can use Python scripts or localization packages to generate this list.
  2. Visuals: Review screenshots of all menus/HUD/dialogs. Are glyph shapes correct for the locale? Are numbers and symbols using the intended fallback?
  3. Layout: Verify wrapping and Asian line-breaking rules. Check long strings, narrow screens, and extreme aspect ratios.
  4. Platforms: Recheck console and mobile builds to ensure OS font substitution or platform differences don’t creep in. Ensure you’re not relying on missing system fonts.

 

If this feels to challenging, you can always rely on professionals.

Regression prevention (CI)

Finally, add a lightweight editor/CI check that asserts the fallback order for every TMP font asset (SC before JP, etc.) and verifies the asset GUIDs used by UI prefabs.

During build, generate a unique-character list per locale and fail the build if any glyph is missing in its assigned asset. That keeps “JP-over-SC” from sneaking back in later.

Keep in mind that while CI guardrails don’t replace human eyes, they give you confidence that a tweak on one screen doesn’t break the typography on another.

That’s the guide!

To help you recap it quickly, we’ve included a quick checklist to keep track of all the necessary steps as you work.

TL;DR: A Quick Checklist

  1. Inventory your languages: Decide which locales you’ll support and collect all translated strings.
  2. Pick regional fonts: Noto Sans or Source Han variants (SC/TC/JP/KR) or another font family with regional variants.
  3. Create one primary TMP font asset per locale: Bake the required characters into a static atlas (enable multi-atlas if needed).
  4. Add a small Latin or symbol fallback: Place it after the locale’s font in the fallback list. Don’t mix JP into CN chains (or vice versa).
  5. Set your fallback order: Make sure your target language comes before the others. Set the order globally.
  6. Implement runtime font switching: Use Unity’s localization package or a custom script to change fonts when the locale changes.
  7. Configure Asian line breaking: In the TMP Settings, pick Line Breaking for Asian Languages.
  8. Run an LQA: Extract unique characters, review screenshots, and test line wrapping for all languages.
  9. Add CI checks: Validate fallback order, asset references and glyph coverage on every build.
  10. Revisit as content grows: As you add more content or languages, revisit your fonts, atlases and scripts. Make sure everything scales

And if this still feels too daunting (understandably so), contact us for a helping hand. Translation aside, Transphere offers CJK font audits and LQA support.

Whether you’re launching tomorrow or planning your next project, we can help ensure your text feels right to every player. Reach out, and let’s make your localization shine.

Discussion

Propel Your Brand into

the Global Stage

At Transphere, we believe that the true measure of our success is the growth of our long-term partners. Reach out to our passionate members and start growing today!

Fill out the form to learn how we can help you grow.

Contact-us