9 minute read

Making sure your game supports several writing systems across a bunch of languages can be tricky. Unreal Engine has a solution: Composite Fonts. They allow you to combine several font faces into one font, so you can have a single font that supports all the languages in your game. Here’s my experience with setting them up.

I’ll only cover setting up a composite font, not finding the font faces themselves.

Two types of fonts in Unreal

Unreal has two types of fonts: Offline (pre-computed Font Atlas) and Runtime (Composite Font). I’ll only cover the Composite Font here.

Composite font is a way to combine several font faces into one, so you can have a single font asseet that supports all the languages in your game. When setting up a composite font, you tell Unreal to use different font faces based on Unicode ranges and/or active game cultures.

The setup itself isn’t too complicated but there are some pitfalls, epecially when mixing alphabets or setting up fonts for Chinese Simplified and Chinese Traditional (and other languages that use Han characters).

Tips and tricks to work and test fonts

Trick to display Fonts only (not Font Faces) in Content Browser

Search for Type=Font & Type!=FontFace =) This is really useful when you have a lot of font faces and fonts in the project.

Trick to test culture-bound overrides in the font editor

Press `/~ (the backtick/tilde key) to open the console or access it via the Output Log window, then use the culture=<locale> command to test different cultures in the editor. E.g., you can test a culture-bound Chinese Traditional override with culture=zh-Hant command. Beware that in some locales the Editor UI might switch to another language—but you can always go back with culture=en.

Composite font basics

  1. There’s always a Default Font Family that will be used if no other rules are matched. Usually, it’s the font (and its variants) that supports your game’s native locale and as many other languages as possible. A lot of times it’s a fancy decorative font that only supports English, and then you have more work to do finding suitable suitable replacement fonts and setting up overrides.
  2. Set up one base font face per font. Add any variants of the same font as well, e.g., semibold, italic, etc., and give them descriptive names. Don’t combine several different font faces in one composite font. In other words, it’s okay to have Noto Sans Regular, Noto Sans Semibold, Noto Sans Italic, etc. in your Default Font Family, but it’s not okay to mix different font families, like Noto Sans and Noto Serif in one font family (even though technically, you can do that and it’ll work—until you need to introduce font overrides, when it becomes a mess).
  3. There’s also a Fallback Font Family that is used if a symbol is not found in the Default Font Family or it doesn’t match any of the rules in your Sub-Font Families. Here, you could use a font that supports a lot of languages and a wide range of Unicode characters, and be done with it, unless you have more than one Han language (Chinese, Japanese, etc.).
  4. There are as many Sub-Font Families as you like, where you specify the rules for when they override the default fonts:
    • Each Sub-Font must have one or more Unicode Ranges. If a character falls into one of these ranges, this Sub-Font will be used.
    • Each Sub-Font can have one or more Cultures specified, as a semicomma-separated list. If specified, this Sub-Font will only be used if the game is currently running in one of the specified cultures. This can be anything from a top-level locale like zh that will match all the more specific locales inside this language to a specific locale like zh-Hans-CN that will only match a specific locale.
  5. For the Fallback and Sub-Font Families, make sure to include the same variants, e.g., Regular, Bold, Italic, etc. unless you have a specific reason not to: for example, you probably won’t have Italic in the likes of Chinese and Arabic, etc. Still, it might be better to include them anyway with a font face that you want to use in this case—otherwise, Unreal will choose some other variant for you automatically.

This gives us a way to override the default font for specific Unicode ranges and cultures to support any number of languages in the game.

Example of a simple setup

Basic font setup for several Han languages

Here’s a simple case when the base font (Default Font Family) supports a bunch of the languages we need: EFIGS, Portuguese and Polish. Then we add a Chinese Simplified font as the fallback (Fallback Font Family), then we add a Cyrillic font to support Russian and Ukrainian with no culture specified since it mixes well with the base font, then we add Korean, and finally, we add Japanese and Chinese Traditional overrides with the respective fonts and cultures so that people playing in those languages would see the correct glyphs.

Unicode and Han Unification: Chinese Simplified and Traditional, Japanese, Korean, etc.

In short, Unicode has unified the vast majority of Han characters into the same code points. As a result, the same Unicode code point should be displayed differently depending on the language: Chinese Simplified and Traditional, Japanese, and Korean are the most prominent examples but the list goes on to include any other languages that use Han characters. If you want to know more, here’s the article on Wikipedia: Han Unification.

Differences for the same Unicode code point (U+8FD4) in regional versions of Source Han Sans

Displaying the correct Han character based on the language is achieved via using different fonts. This means that if you set up a composite font that includes more than one of these languages, you’ll have to be careful to make sure that the fonts are only used for the correct language.

When it comes to Han, Chinese Simplified is used by way more people than any other Han-based language. So I always set up the Chinese Simplified Sub-Font without any culture specifier (or use it as a Fallback font) and all the other Han overrides as Sub-Fonts with their respective cultures specified. That means that unless the game’s running in any of these specific Han locales (like zh-Hant, ja, ko, etc., including all their sublocales) the Chinese Simplified font will be used to display Han characters. That ensures that the game will be able to display Han symbols no matter the locale (good for chats, names, and any other user-generated content), while still displaying the correct characters to the people who play the game in a locale that uses some other variant of Han characters.

Overriding subsets of Unicode and mixing alphabets

When setting up a composite font, you can easily mix up fonts for different alphabets. This can be useful if you have a game that uses a lot of different languages and you want to make sure that each language is displayed correctly. But it can also lead to some weird results if the fonts aren’t that much alike and don’t look great when mixed in a single word or phrase. Sometimes, they don’t look great even if mixed on one screen. This usually happens when the target language uses extended Latin or something close to it, like Cyrillic, and uses basic Latin or numbers as well—which is very common, of course.

Here’s an example of such a basic setup and how bad it looks.

Setup that only overrides Cyrillic and ends up mixing different fonts in phrases and words

Example of a bad mix of Cyrrilic and Latin glyphs

In cases like this it might be worth it to have a Sub-Font Family with the Cultures filter specified that also overrides any problematic ranges (or the whole basic ASCII range) to completely avoid mixing with the defaut font in some locales.

Setup that overrides basic Latin as well so that words and phrases don't use several fonts unintentionally

Better-looking setup when you replace basic Latin as well

Scripts and Ranges to Override

I am a bit of a maximalist here and prefer to override as many ranges as possible, possibly doing some extra work that isn’t needed but since this is only done once per game, I think it’s worth it.

I’ll list what we have in our projects in Unreal terms.

Chinese and Japanese Ranges

  • CJK Compatibility: 0x33000x33ff
  • CJK Compatibility Forms: 0xfe300xfe4f
  • CJK Compatibility Ideographs: 0xf9000xfaff
  • CJK Compatibility Ideographs Supplement: 0x2f8000x2fa1f
  • CJK Radicals Supplement: 0x2e800x2eff
  • CJK Strokes: 0x31c00x31ef
  • CJK Symbols and Punctuation: 0x30000x303f
  • CJK Unified Ideographs: 0x4e000x9fff
  • CJK Unified Ideographs Extension A: 0x34000x4dbf
  • CJK Unified Ideographs Extension B: 0x200000x2a6df
  • CJK Unified Ideographs Extension C: 0x2a7000x2b73f
  • CJK Unified Ideographs Extension D: 0x2b7400x2b81f
  • CJK Unified Ideographs Extension E: 0x2b8200x2cea1
  • Enclosed CJK Letters and Months: 0x32000x32ff
  • Halfwidth and Fullwidth Forms: 0xff000xffef
  • Hiragana: 0x30400x309f
  • Katakana: 0x30a00x30ff
  • Katakana Phonetic Extensions: 0x31f00x31ff

As mentioned before, if neither the base font nor the fallback font covers Chinese in full, I create a sub-font family with these ranges and no culture specified that uses a Simplified Chinese font, e.g., Noto Sans SC.

Then I create sub-font families for Chinese Traditional and Japanese with these ranges and zh-Hant and ja cultures specified respectively that use the appropriate fonts, e.g., Noto Sans TC and Noto Sans JP.

alt text

Cyrillic

  • Cyrillic: 0x04000x04ff
  • Cyrillic Extended A: 0x2de00x2dff
  • Cyrillic Extended B: 0xa6400xa69f
  • Cyrillic Extended C: 0x1c800x1c87
  • Cyrillic Supplementary: 0x05000x052f

If the fonts are very close in how they look and behave or simply look well when mixed in a word or phrase, you can just override these ranges and skip specifying the locale. That’s a simpler way to ensure things work well across locales, especially with user-generated texts.

alt text

If the fonts are different and don’t look well when mixed, also override the basic Latin: 0x00000x007f and specify the locale.

Cyrillic with ASCII and culture override

Remember that if you have user-generated content in the game, you need to either make sure your fallback fonts support Cyrillic (even if not as pretty), or create another sub-font family that’ll take care of that without the culture override.

Cyrillic basic and all locales

Korean

  • Hangul Compatibility Jamo: 0x31300x318f
  • Hangul Jamo: 0x11000x11ff
  • Hangul Jamo Extended A: 0xa9600xa97f
  • Hangul Jamo Extended B: 0xd7b00xd7ff
  • Hangul Syllables: 0xac000xd7af

Korean

Other scripts

I override all the relevant ranges, finding fonts that support the script in full.

Overall, if we’re having a font that doesn’t support a few extended Latin symbols, we find a fallback font that does and make sure these two mix well in words and phrases. If that proves really difficult, we look at replacing the original font with something that supports at least extended Latin to make sure it looks good for all players.

Test String

Curtesy of ChatGPT with some edits. It’s not a 100% insurance that the font will support the language in full but it’s a good quick test to see if the font that claims to support a script doesn’t fail miserably in reality.

[zh-CN] 测试汉字字体 [zh-TW] 測試漢字字體 [ja] ー漢字ひらカナ字 [ko] 한글가나다라 [fr] ÀÉçœùîô [it] Èéìòù [de] ÄÖÜß [es] ¡¿Ñáéíóú [pt] ãõçÁÉÍÓÚ [pl] ĄĆĘŁŃÓŚŹŻ [ru] ыЮЯЁ [uk] іЇЄІҐ [tr] ÇĞİŞÖÜçğışöü [p] …«»“”‘’—–·