Localizing Strings in Daggerfall Unity – Part 6

Series
Localizing Strings in Daggerfall Unity – Part 1
Localizing Strings in Daggerfall Unity – Part 2
Localizing Strings in Daggerfall Unity – Part 3
Localizing Strings in Daggerfall Unity – Part 4
Localizing Strings in Daggerfall Unity – Part 5
Localizing Strings in Daggerfall Unity – Part 6

Custom Fonts

Note: Before progressing to this tutorial, please update source project to Daggerfall Unity 0.11.3 or later. This version has fixes required to complete this part in series.

In Part 3 of this series, we noted the Korean (ko) locale would print out question mark characters (?? ???) instead of proper glyphs.

This happens because Daggerfall Unity does not yet have an appropriate font asset to render these characters. The default fonts have a full complement of Latin characters but non-Latin languages require a new font to display their unique alphabet.

This tutorial will demonstrate how to add a custom Korean font with a Hangul alphabet to Daggerfall Unity, but the same process can be used to add Cyrillic fonts, Kanji fonts, or even just a custom default font.

The font we’ll be using is Noto Serif KR from Google Fonts. Click Download family on that page to download the font used in this tutorial, or substitute with another TTF/OTF font for your language.

Once downloaded, unzip the whole font family and locate NotoSerifKR-Regular.otf. This is the specific font we’ll be using. To start with, we’ll add this font to our Unity project.

  1. In Project view, navigate to our DemoTranslationMod/Resources folder.
  2. Drag and drop the NotoSerifKR-Regular.otf font into this folder with other resources. Unity will import this like below.

Create TextMeshPro Font

Daggerfall Unity uses TextMeshPro (TMP) fonts. Before we can see this font in game, we need to create a TMP font asset.

  1. Click Window menu > TextMeshPro > Font Asset Creator
  2. Set NotoSerifKR-Regular in Source Font File by clicking the circle selector on right-hand side then selecting font

Now we need to determine which character codes our TMP font will support. This will vary based on many factors unique to the target language, but the core concept is that we want to inform Font Asset Creator which character codes from your alphabet to compile into your TMP font. Any characters not added to TMP font asset will continue to display as question marks in game.

There are multiple ways to describe which characters to use. You can select from a basic list of settings, use decimal/hex ranges, import from another TMP font, import from a file, etc. Whatever method you use, keep track of the settings so you can add to it later if any characters are found missing.

In this example, we’re going to use a subset of Hangul alphabet by directly entering the characters required. It’s better to start with a subset of characters for the region/dialect you’re targeting rather than just trying to include everything. Some languages have thousands of characters which can result in an unusable TMP atlas without enough fidelity.

  1. In Font Asset Creator, drop down Character Set selector and choose Custom Characters
  2. Copy and paste the following custom characters into the Custom Character List text box. Note the very first character is a space character.
 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz가개갸거게겨고괴괘교구귀궤규그긔기나내냐너네녀노뇌놰뇨누뉘눼뉴느늬니다대댜더데뎌도되돼됴두뒤뒈류드듸디라래랴러레려로뢰뢔료루뤼뤠류르릐리마매먀머메며모뫼뫠묘무뮈뭬뮤므믜미바배뱌버베벼보뵈봬뵤부뷔붸뷰브븨비사새샤서세셔소쇠쇄쇼수쉬쉐슈브븨비아애야어에여오외왜요우위웨유으의이자재쟈저제져조죄좨죠주쥐줴쥬즈즤지차채챠처체쳐초최쵀쵸추취췌츄츠츼치카캐캬커케켜코쾨쾌쿄쿠퀴퀘큐크킈키타태탸터테텨토퇴퇘툐투튀퉤튜트틔티파패퍄퍼페펴포푀퐤표푸퓌풰퓨프픠피하해햐허헤혀호회홰효후휘훼휴흐희히역을선택십시1234567890‘?’“!”(%)[#]{@}/&\<-+÷×=>®©$€£¥¢:;,.*…

The above characters are enough for this tutorial, but will need to be expanded for full language support. This is how it looks in Font Asset Creator. We’re also adding the basic English alphabet to have a well-rounded font that can still display any untranslated English text.

Click Generate Font Atlas to render all the specified characters into a TMP atlas. Once you’ve created the atlas, you’ll see all the characters packed like below. Click for full size.

TextMeshPro renders glyphs using signed distance fields (SDF). How this works is outside the scope of this tutorial, but to summarise it’s a way of representing fonts using mathematical values that can maintain smooth shapes at any resolution.

In the output field, take note of the Missing characters, Excluded characters, and “characters missing from font file”. This will display any character codes you requested but could not be found in the provided TTF/OTF font. If your source font is missing any important characters, then it’s necessary to locate another TTF/OTF font containing those characters.

Depending on your font requirements, you might want to increase Atlas Resolution, e.g. to 4096×4096 or higher to support more characters with good fidelity. You can also adjust packing method, sample size, etc. for your specific font needs.

For this tutorial, the atlas generated above is good enough. Click the Save button and save your new TMP font to DemoTranslationMod/Resources. We’ll use the default name in a moment, please don’t change it. This creates a new TMP font asset like below with the name “NotoSerifKR-Regular SDF”.

This new SDF asset contains the atlas and other information required to render the TMP font in Daggerfall Unity. You can come back and recreate the font later with different properties or more characters if needed.

Associate Font With Locale

There’s one more step before can see this font in game. We need to associate our custom font with the correct locale so that Daggerfall Unity knows to use it. This is done using the StartupScript.cs code we created previously.

Open StartupScript.cs in your code editor and replace the entire contents with below. Don’t forget to save your changes!

using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.Localization.Settings;
using DaggerfallWorkshop.Game;
using DaggerfallWorkshop.Game.Utility.ModSupport;
using DaggerfallWorkshop.Game.UserInterface;

namespace DemoTranslationMod
{
    public class StartupScript : MonoBehaviour
    {
        // Define the names of translated string table collections
        const string runtimeInternalStrings = "Demo_Strings";
        const string runtimeRSCStrings = "Demo_RSC";

        public static Mod mod;

        [Invoke(StateManager.StateTypes.Start, 0)]
        public static void Init(InitParams initParams)
        {
            mod = initParams.Mod;
            var go = new GameObject(mod.Title);
            go.AddComponent<StartupScript>();
        }

        private void Awake()
        {
            // Set TextManager properties to use translated string table collections
            TextManager.Instance.RuntimeInternalStrings = runtimeInternalStrings;
            TextManager.Instance.RuntimeRSCStrings = runtimeRSCStrings;

            // Load KO font
            DaggerfallFont font_ko = new DaggerfallFont(DaggerfallFont.FontName.FONT0003);
            font_ko.LoadSDFFontAsset("NotoSerifKR-Regular SDF");

            // Assign KO font
            Locale ko = LocalizationSettings.AvailableLocales.GetLocale("ko");
            if (ko)
            {
                TextManager.Instance.RegisterLocalizedFont(ko, DaggerfallFont.FontName.FONT0003, font_ko);
            }

            Debug.Log("StartScript has completed.");
        }
    }
}

Let’s take a look at what changed. To start with, we added some new using statements. These import some extra functionality to the script.

using UnityEngine.Localization;
using UnityEngine.Localization.Settings;
using DaggerfallWorkshop.Game.UserInterface;

Then we added the following code to associate our custom font with the KO locale to Daggerfall’s FONT003. This is the default font used in most parts of the game.

// Load KO font
DaggerfallFont font_ko = new DaggerfallFont(DaggerfallFont.FontName.FONT0003);
font_ko.LoadSDFFontAsset("NotoSerifKR-Regular SDF");

// Assign KO font
Locale ko = LocalizationSettings.AvailableLocales.GetLocale("ko");
if (ko)
{
    TextManager.Instance.RegisterLocalizedFont(ko, DaggerfallFont.FontName.FONT0003, font_ko);
}

First, we create a custom DaggerfallFont called font_ko. By default we’re loading FONT003, but we’ll replace this in a moment with our custom font.

Second, we tell font_ko to load our custom font asset using LoadSDFFontAsset(). Any font created using the TextMeshPro Font Asset Creator should load OK. Reminder: you need to be on Daggerfall Unity 0.11.3 or later.

Finally, we try to get the “ko” locale and only continue if this locale is found. If the locale is found, we associate our custom DaggerfallFont with the “ko” locale using RegisterLocalizedFont().

When this code executes at startup, Daggerfall Unity now knows it should use a different font when rendering FONT003 within the Korean (ko) locale. You can associate the same SDF font with FONT001, FONT002, etc. but you might want to use different styles of typefaces to better match the desired look and feel in your translation.

Make sure your changes are saved, then we can try this font in game.

  1. Click Play to start the game and click through to title menu.
  2. Use the locale drop-down to select Korean (ko) in Game view.
  3. Click Start New Game in title menu. If everything works as expected, our custom font will be selected and used with our translated text example.

Click Play again to stop game.

To summarise, creating a custom font requires the following steps:

  1. Locate appropriate TTF/OTF source fonts for your language. These fonts must contain all the alphabet characters you need for your translation.
  2. Import the TTF/OTF font into Unity project.
  3. Use the Font Asset Creator to generate a custom TMP font asset with the required characters and resolution.
  4. Associate new font with your custom locale and related Daggerfall font in startup C# script.

You might need to recreate font several times when creating your translation mod, either to add new characters, change resolution, or just finetune the look and feel. If you change the asset name, don’t forget to update this in your startup script.

Now that we have a custom font, we can move on to packaging our localisation to standalone files. This will be covered in Localizing Strings in Daggerfall Unity – Part 7.

Daggerfall Unity Beta 0.11.3

Daggerfall Unity 0.11.3 is now available on Live Builds page. This is another general bug-fix release with some updates to mod system.

Starting from this release, I’m attaching zipped builds along with patch notes to Releases page on GitHub. This will eventually become a longer-term archive for older builds even after they’re cleaned up from Live Builds page. If there’s enough demand, I can work through attaching older builds to previous releases on this page.

Now that I’m finally caught up with code review, I’ll be able to get back into the localization tutorial series. This series was pending some fixes rolled out in 0.11.2 and 0.11.3, so please be sure to update before the next article in series is published.

Quest Debugger Input Changes

The quest debugger has a few changes to input settings. These changes are to prevent accidentally opening debugger during normal play, free up the Tab key for other uses, and to make shortcuts easier on non-US keyboards. For example, the square bracket keys [ and ] are not so easy to use on AZERTY keyboards.

If you are not a quest author or don’t care about quest debugger, you can ignore these changes.

  • Quest debugger is now disabled by default. Must edit EnableQuestDebugger=True in settings.ini to activate it again.
  • Ctrl+Shift+D now cycles through debugger display states off/partial/full
  • Ctrl+Shift+LeftArrow now opens previous active quest while debugger active
  • Ctrl+Shift+RightArrow now opens next active quest while debugger active
  • Ctrl+Shift+UpArrow now teleports to next dungeon quest marker while debugger active inside dungeon
  • Ctrl+Shift+DownArrow now teleports to previous dungeon quest marker while debugger active inside dungeon

Note: Some inputs might move character, e.g. Ctrl+Shift+D will also move character slightly to right when activating debugger. This is expected and not considered a breaking issue for a debug/development feature that is normally disabled.

General Fixes & Improvements

Pango

  • Potion maker: Fix issues tracking ingredient stack count between cauldron and player inventory
  • Conjured arrows: Don’t stack conjured arrows with different time-to-live
  • Conjured arrows: Always shoot conjured arrows first, priority to lowest time-to-live
  • Conjured arrows: Change colour of arrows to show if conjured or real in inventory and arrow counter on HUD
  • Conjured arrows: Prevent shooting a conjured arrow converting to a real arrow

Ferital

  • World: Fix classic bug relating to %ef macro not using correct local race for building names

Hazelnut

  • DaggerfallQuestJournalWindow: Change access permissions to allow overriding by mods
  • DaggerfallQuestJournalWindow: Allow events to be overridden by mods
  • DaggerfallLoot: Allow mods to add or modify loot items via FormulaHelper.ModifyFoundLootItems() override
  • Inventory UI: Right-click now works as described in Daggerfall manual to perform complement of equip/remove, appearance cycling moved to middle-mouse

KABoissonneault

  • DaggerfallInventoryWindow: Change access permissions to allow overriding by mods
  • Biographies: Fix issues parsing patched BIOG files
  • Biographies: Add ability to add custom biographies
  • FormulaHelper: Add hook to override CalculateCasterLevel
  • FormulaHelper: Add hooks to override spell costing formulas CalculateTotalEffectCosts, CalculateEffectCosts, ApplyTargetCostMultiplier
  • Magic: Fix spells with multiple effects ending too early

Bl4ckh34d

  • World: Fix incorrect nature billboards at climate borders

XJDHDR

  • ActionDoor: Add support for MeshCollider
  • RuntimeMaterials: Fix bug where dungeon texture not applied if runtime material not attached to base object
  • MeshReplacement: Fix regressions of new scripts not added to mod-added models

TheLacus

  • TextBox: Do not draw defaultText if null

Interkarma

  • World: Extend town borders by 4096 units – increases distance at which flavour text is displayed, guards will chase player out of town, etc.
  • Magic: Fix player not receiving payload of Damage Fatigue effect
  • Formula: Fix randomness of levelup health roll so this is always different, even after a reload
  • Localization: Fix exception when determining size of new font atlas using deprecated fields
  • Engine: Update Addressables package to 1.17.17