💬

Unity TextMeshProにおけるダイナミックフォントの扱い

2021/12/01に公開

Applibot Advent Calendar 2021」1日目の記事になります。
Applibotでゲームクライアントエンジニアとして働いている、@siguma_sigです。

本記事では、Unityの文字描画機能として公式で提供されているTextMeshProで、ダイナミックフォントとして文字を描画する場合のハマりポイントとその対応について紹介したいと思います。
前半はTextMeshProやダイナミックフォントについての紹介となるので、ハマリポイントだけ読みたい方は飛ばして読んでも構いません。

TextMeshProとは

TextMeshProとは、uGUI Textの代替として公式で提供されている文字描画機能です。
uGUI Textと比較したときの最大の特徴は、TextMeshProはSigned Distance Fieldと呼ばれる手法を用いて文字をテクスチャに書き出していることです。
TextMeshProでは、SDFテクスチャを文字の描画に用いることで、uGUI Textに比べて文字の拡大縮小に強く、Outline等のさまざまなスタイルの文字描画がやりやすくなっています。
詳細な説明は、こちらの記事を参考にしてください。

ビットマップフォントとダイナミックフォント

ビットマップフォントとダイナミックフォントについても軽く説明します。
文字の描画は、Fontデータを元に文字をテクスチャに書き出し、その書き出したテクスチャを参照して板ポリに貼り付けることで行っています。
簡潔に説明すると、ビットマップフォントの場合はFontデータからテクスチャへの文字の書き出しを実行前に、ダイナミックフォントの場合は実行時に行うところが大きな違いです。

下記はビットマップフォントの場合の文字描画の流れです。
ビットマップフォントの場合の流れ

ビットマップフォントには次のようなメリット/デメリットがあります。

  • メリット

    • フォントファイル(ttf/otf)をアプリに含める必要がなくなる
    • あらかじめテクスチャを用意するので動作は速い
    • テクスチャさえ用意すれば凝った文字表現ができる
  • デメリット

    • あらかじめ用意した文字以外を描画することができない
    • 文字の種類が多く必要な場合はテクスチャの容量が大きくなりメモリを圧迫する

続いて、下記がダイナミックフォントの場合の文字描画の流れです。
ダイナミックフォントの場合の流れ

ダイナミックフォントには次のようなメリット/デメリットがあります。

  • メリット
    • フォントデータ(ttf/otf)に入っている文字ならば描画できる
    • 描画したい文字の種類が多い場合も常に文字のための巨大なテクスチャをメモリに置いておかなくて済む
    • あらかじめテクスチャを書き出す手間がない
  • デメリット
    • 実行時にテクスチャに文字を書き込む分の時間がかかる
    • フォントデータ分は常にメモリを圧迫する

最近のソーシャルゲームでは、ユーザーが任意の文字を入力できるような機能を有していることが多く、ビットマップフォントのみでは描画したい文字の種類をカバーしきれないので、ダイナミックフォントを使用することが多いと思います。

TextMeshProにおけるダイナミックフォント

TextMeshProはFontAssetというものを元にして、文字の描画を行います。
FontAssetの作成は、下記の手順で行います。

  1. Window > TextMeshPro > Font Asset Creatorを開く
  2. FontAssetを作成したいFontを選択する
  3. ChracterSetなどの設定をし、GenerateFontAtlasで書き出す
  4. TextMeshProのコンポーネントに書き出したFontAssetを設定する

ダイナミックフォントとして描画を行うように設定するにはさらに下記の手順を行います。

  1. さきほど説明した方法でFontAssetを作成する
  2. 作られたFontAssetを選択し、Generation Settingsにダイナミックフォントとして利用したいFontを設定
  3. Atlas Population ModeをDynamicに設定

実際に設定したものが下記の画像になります。
Atlas Render ModeをSDF AAにするのも忘れないようにしてください。

Dynamic FontAsset

TextMeshProにおけるダイナミックフォントのハマリポイント

最後に、TextMeshProにおけるダイナミックフォントを利用する際のハマリポイントを紹介します。

FontTextureがいっぱいになったとき

さきほど説明したように、ダイナミックフォントでは動的にフォントテクスチャに対して描画したい文字を書き出し、それを元に文字の描画を行います。
ビットマップフォントと違い、あらかじめ文字列を書き出したテクスチャを用意しておく必要はありませんが、動的に書き出す対象となるフォントテクスチャはあらかじめ用意しておく必要があります。
TextMeshProでは、FontAssetのGenerationSettingsのAtlas WidthAtlas Height
にてフォントテクスチャのサイズを設定します。

uGUI Textでは、フォントテクスチャがさまざまな種類の文字を描画したことによっていっぱいになってしまったときには、リビルドされてテクスチャサイズを大きくした別のテクスチャを用意されます。その際に、既存の使われている文字は新しいテクスチャの方に勝手に書き込んでくれます。
しかし、TextMeshProではフォントテクスチャがいっぱいになってしまったときには、特に新しいテクスチャなどは用意されず、それ以降の文字列が□(描画できない文字のときの表示)で描画されてしまいます。

その状態を避けるには、フォントテクスチャがいっぱいにならないように、あらかじめ大きなサイズのフォントテクスチャの確保が必要になってしまいますが、そうなると常に大きなサイズのフォントテクスチャがメモリを専有することになり、ダイナミックフォントを使っている旨味が減ってしまいます。

そのため、回避策として、フォントテクスチャがいっぱいになったときにその時点で一度フォントテクスチャをクリアし、必要な文字列のみ再度テクスチャに書き込むことで、フォントテクスチャのサイズがある程度小さいままでも、さまざまな種類の文字を描画することができるようになります。

フォントテクスチャのクリアと必要な文字列の再登録は下記の方法で行うことができます。

using TMPro;
using UnityEngine;

namespace TextMeshProTest
{
    public class TextMeshProTest
    {
        [SerializeField]
        private TMP_FontAsset _fontAsset = null;

        public void ResetFontTexture()
        {
            // あらかじめ再描画必要な文字列をキャッシュしておく
            // TextMeshProでの文字描画の際に、常に今描画している文字を合わせてキャッシュしておく方法などがおすすめ
            var currentNeedString = "hogefugapiyo";
            _fontAsset.ClearFontAssetData();
            _fontAsset.TryAddCharacters(currentNeedString);
        }
    }
}

説明の簡略化のために、文字列のキャッシュ部分は省いていますが、TextMeshProのSetTextをラップしておくなどで、現在使われている文字列一覧が常にわかるようにしておく必要があります。
そちらの準備をした上で、上記のResetFontTextureを呼び出すことで、フォントテクスチャのクリアが行なえます。
ただし、こちらの処理はそれなりに負荷があるため、ゲームのローディング時など負荷がかかっても問題ない場面で呼び出すのが良いです。
そのため、フォントテクスチャのサイズは、それらのクリアのタイミングを挟まないで描画される文字の最大数を考慮し、決めると良いと思います。

まとめ

本記事では、ダイナミックフォントについて詳しく説明しました。
ただ、ビットマップフォントに関しても比較で上げたようにメリットはあり、場面によってはビットマップフォントを採用したほうが良い場面はあるので、仕組みを理解した上でどちらを採用するか考えることが重要です。
この記事を参考に、より良い選択ができるようになれば幸いです。
以上、
Applibot Advent Calendar 2021」1日目の記事でした!

Discussion