役演亭 -Yakuentei- Roleplay with your own characters.
  1. ホーム
  2. 技術記事
  3. ゲーム開発

Unity の文字列描画 (TextMesh Pro) を C# Script のみで行う方法

公開日 (Published) : 更新日 (Modified) :

Unity で極力デザイナに頼らず TextMesh Pro で文字列を描画する方法を紹介します。

本記事で例として描画する日本語テキスト

本スクリーンショットの例をもとに、C# Script のみ (=マウスによる手作業を極力削減) でのテキスト描画方法を紹介していきます。

目次

まえがき (対象読者など)

以前、「Unity でプログラミングのみ (C# Script のみ) でゲームを作る方法」という記事を書きましたが、日本語フォントファイルをもちいた文字列描画方法は紹介しきれませんでした。

Unity の文字列描画機能である TextMesh Pro を利用するためには、一般的に普及している TrueType フォント形式 (拡張子 ttf) を、SDF (Signed Distance Field) というフォント形式に変換する必要があり、そのための手作業 (※) が多いからです。

簡単に言えば、必要な文字 (例えば日本語で漢字も使用する場合は数千文字) を全部 1 枚の巨大な画像ファイルにまとめたもの (=フォントアトラス) を描画に使用する形式です。

一度 SDF フォントファイルが出来てしまえば、あとはプログラミングのみ (C# Script のみ) で文字列を描画できますので、C# Script のみでゲーム開発をしたいと考えている Unity 開発者にも役に立つと考え、記事化することにしました。

SDF フォント作成の手作業 (※) の自動化について

本記事の最後の方で紹介するとおり、Unity の「エディター拡張」と呼ばれる仕組みをもちいることで、SDF フォント生成の手作業 (ワークフロー) も C# Script で自動化できます。

ただし、どのような設定を手作業で行うのかを理解していないと難しい内容ですので、SDF フォントの作成を行ったことの無い方は、本記事を順番どおりに読み進めながら作業することをおすすめします。

事前準備 - 使用する日本語フォント

本記事は、下記のサイトでフリー (オープンソース) として公開されているポップ系書体の日本語フォントファイルを使用しています。

本記事では、上記リンク先からダウンロードできる zip ファイルに含まれている GenEiPOPle-Bk.ttf を、変換元の TrueType フォントファイルとして使用しています。

必ずしも上記のフォントを使用する必要はなく、読者がこれからゲーム開発で使用したいフォントで試しても構いませんが、フォントに含まれている文字種の量 (特に JIS 第二水準の漢字が含まれているか否か) や、字体の太さ、複雑度などによっては、この後で順次説明していく変換時のパラメーターを試行錯誤で調整する必要が出てきますので、ご注意下さい。

事前準備 - Unity プロジェクト環境

別記事「Unity でプログラミングのみ (C# Script のみ) でゲームを作る方法」にある以下の手順を参考に、プロジェクトを新規に作成し、C# Script の作成と Main Camera への割当まで済ませます。

本記事では、プロジェクト名は ScriptOnlyTMPro、C# Script は NewBehaviourScript.cs としました。

TextMesh Pro 機能の有効化 (TMP Importer)

TextMesh Pro の機能は、新規プロジェクトで、初めてその機能を使用したときに有効化のための TMP Importer ダイアログが表示されます。

C# Script で直接 TextMesh Pro の API を呼び出した場合も例外ではありませんので、まずは NewBehaviourScript.cs に以下のスクリプトを追記してみましょう。

(コードの中身の処理については、日本語が描画できるようになった後で解説します)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

public class NewBehaviourScript : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        Camera.main.orthographicSize = 135; // 画面サイズ 480x270 ピクセル想定

        TextMeshPro testText = new GameObject("testText").AddComponent<TextMeshPro>();
        testText.fontSize = 480; // フォントサイズ 48px
        testText.alignment = TextAlignmentOptions.Center; // 中央揃え
        testText.enableWordWrapping = false; // 自動改行無し
        testText.sortingOrder = 0; // 画像 (SpriteRenderer) と同様、描画順序指定が必要
        testText.text = "Korewa test desu.";
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

Unity 6 で動かす場合の注意

Unity 6 では、enableWordWrapping が非推奨 (=将来廃止予定) となりました。

testText.enableWordWrapping = false; // 自動改行無し

代わりに textWrappingMode に置き換えることが推奨されているため、Unity 6 をお使いの方は以下のように修正することをおすすめします。

testText.textWrappingMode = TextWrappingModes.NoWrap; // 自動改行無し

TMP Importer ダイアログの操作

上記のソースコードを追加したうえでゲームの実行を開始 (再生ボタンをクリック) すると、以下のダイアログが表示されます。

TextMesh Pro の動作に最低限必要なファイルをインポートする必要があるため、ここでゲームの実行を一旦停止し、「Import TMP Essentials」をクリックします。

(Import TMP Examples & Extras は、使用例などの追加ファイルです)

TMP Importer ダイアログ

Import TMP Essentials をクリック後、ボタンはグレー表示に変わりますが、ダイアログは開いたままですので、右上の「×」ボタンで閉じます。

TMP Importer ダイアログ (Import 後)

上記までの操作により、Assets フォルダの直下に TextMesh Pro というフォルダが生成され、必要なファイルがインポートされます。

インポートされたファイルの確認

インポート後の動作確認

ここまでの手順で、あらかじめ用意されている Liberation Sans という「欧文用の」SDF フォントが使えるようになるため、半角のアルファベット、数字、記号類のみ描画できます。

もう一度再生ボタンをクリックして実行を開始すると、以下のような表示になるはずです。

アルファベットの文字列描画の確認

使用する日本語フォントを SDF 形式に変換

事前準備 - 使用する日本語フォント」で用意した TrueType (拡張子 ttf) 形式のフォントを、SDF 形式に変換します。

まずは、用意した ttf 形式のフォントを Assets フォルダ以下の任意の場所 (例えば直下) にインポートします。

使用する TrueType フォントのインポート

Font Asset Creator ダイアログを使用して SDF 形式に変換

SDF 形式に変換するため、Unity に内蔵されている Font Asset Creator ダイアログを開きます。Window メニュー → TextMesh Pro → Font Asset Creator と辿ります。

Font Asset Creator ダイアログを開く

ここの設定が TextMesh Pro で一番大変なところで、使用するフォントや文字数により試行錯誤する必要があるため、注意が必要です。

なので、本稿では「源暎ぽっぷる」(GenEiPOPle-Bk.ttf) の場合の例をもとに説明します。

Font Asset Creator ダイアログの設定例 (源暎ぽっぷるの場合)

Source Font File

使用したいフォントをここに指定します。
(Unity の Project タブからドラッグ & ドロップ可能です)

Sampling Point Size

1 文字あたりの大きさ (ピクセル数) です。例えば日本語の固定幅フォントの場合、全角文字の縦方向および横方向のピクセル数に相当します。

基本は Auto Sizing を設定して、後述の「フォントアトラス」の中に納まる最大のサイズを Unity 側に探してもらうようにします。

文字の解像度に影響するため、大きい方が綺麗な文字になりますが、その分、後述の Atlas Resolution を大きくする必要があり、使用メモリ量も増大します。

Padding

「源暎ぽっぷる」(GenEiPOPle-Bk.ttf) の場合は 4 が最適でした。

Unity 公式フォーラムの情報 (英語) などで、フォントの生成後に表示される SP/PD Ratio が 10% 前後になるように Padding を調整することが推奨されています。

もし 10% から大きく下回った場合、描画内容によっては文字と文字の間に変な線が目立ったり、文字の背景部分が不自然に塗りつぶされたりすることがあります。(筆者も経験有り)

Packing Method

必要なすべての文字を、後述の「フォントアトラス」の中にパズルのように詰め込んでいく際、どれだけ頑張って探索するかを設定する項目です。

(注:一般的には、文字の中は、「・」(ドット) のように狭い面積で済む文字もあるため、1 枚の画像の中に文字を詰め込む順番や位置を工夫することで、同じ総面積でも 1 文字あたりの大きさを増やせる、という特徴があります)

  • Optimum
    • 可能な限り最小の面積で全文字を詰め込むよう、時間をかけてでも頑張る設定です。
  • Fast
    • 時間をあまりかけない程度に、ほどほどに頑張る設定です。Optimum と比べ、前述の Sampling Point Size が犠牲に (=小さく) なりやすいです。

今回のように、実験的に試してみたい場合は Fast を推奨します。

特に日本語文字のように数千文字必要な場合、Optimum は他の設定との組み合わせ次第では丸一日以上の時間がかかる場合がありますので、基本的には本番用 (リリース用) だと割り切った方が良いです。

Fast だと数分~数十分程度で済みますが、Optimum と比べて大差のない生成結果になることが多く、割に合わないので、筆者はゲームのリリース用にも Fast を使用しています。

Atlas Resolution

「源暎ぽっぷる」(GenEiPOPle-Bk.ttf) の場合は 4096x4096 が最適でした。

使用したいすべての文字を含めるための「フォントアトラス」と呼ばれる画像のサイズをピクセル単位で指定します。

日本語で漢字を含める場合は、一般的には数千文字以上を 1 枚の画像の中に納めなければなりませんので、2048x2048 ~ 4096x4096 程度のサイズが必要になることが多いです。

逆にもし、ひらがなやカタカナ等だけで構わない場合は、数十~数百文字程度になるため、より小さなサイズに設定してメモリ使用量を削減することができます。

Character Set

使用したい文字の種類の指定方法 (使用したい文字を直接テキストボックスの中に入力して列挙するか、文字コードの範囲で列挙するか、等) を指定します。

本記事では、JIS 人名漢字や第二水準の漢字をカバーできる Unicode の文字コード範囲を紹介していますので、Unicode Range (Hex) を設定しています。

Select Font Set

すでに他の SDF フォントを生成したことがあり、そのフォントファイルと同じ文字種をカバーできるようにしたい場合に使う設定です。

今回の場合は初めて SDF フォントを生成すると仮定していますので、None のままにします。

Character Sequence (Hex)

Character Set が Unicode Range (Hex) の場合は、収録したい文字コードの範囲を Unicode の範囲で設定します。

筆者の方で、JIS 人名漢字や第二水準の漢字をカバーできる Unicode 範囲を求めたものがありますので、日本語フォントの場合は下記リンクのテキストファイルの内容をコピペすれば、概ね必要十分な文字種をカバーできると思います。

  • Unicode Range (Hex) の指定内容を収録したテキストファイル - 28,997 bytes
    • Windows XP 時代のシフト JIS (CP932) に相当する JIS X 0208:1997 の範囲をカバーできる Unicode 範囲指定です。(約 7000 文字)
    • 第三水準以降の漢字や、中国語の漢字、絵文字などはカバーできていませんので、あらかじめご了承ください。

以下の 2 つは、参考までに、筆者が上記の Unicode 範囲を求めるために作成した C/C++ コードです。文字コード関連の用語説明は割愛します。

  • ShiftJISCharacterGenerator.cpp - 2,002 bytes
    • JIS X 0208:1997 に相当するシフト JIS (CP932) の範囲を総舐めして、シフト JIS 形式の文字群をテキストファイルとして出力する C/C++ コードです。
    • ループ処理の簡易化のため、範囲外の一部も舐めてしまっています。
    • 範囲外の文字は、Windows のメモ帳で開くと全角ドット「・」(0x8145) に変換されるため、メモ帳の置換機能で 1 つだけを残して他は全部消してから上書き保存するのがおすすめです。
  • UnicodeRangeChecker.cpp - 1,345 bytes
    • UTF-16 LE (リトルエンディアン) 形式のテキストファイル中に含まれるすべての文字を総舐めして、Unicode の文字範囲に変換して出力する C/C++ コードです。
    • サロゲートペア (16 bit の範囲から溢れる文字) は非対応です。
    • 上記 ShiftJISCharacterGenerator.cpp で出力した内容 (から余計な範囲外文字を削った内容) を、BOM (Byte Order Mark) 無しの UTF-16 LE 形式で保存してから、本プログラムに入力して使用します。
    • ちなみに筆者は、メモ帳にて UTF-16 LE 形式で保存後、バイナリエディタ (Stirling) を使用して先頭の BOM を削除する方法で入力ファイルを作りました。

Render Mode

フォントに含まれる文字を、前述のフォントアトラス画像上に描画する際のモード指定です。速度重視から品質重視まで、多くのアルゴリズムが用意されています。

筆者は、速度と品質のバランスから SDFAA_HINTED を使用しています。

Unity の公式ドキュメント (英語) でも、SDFAASDFAA_HINTED がほとんどの場合に適している (sufficient for most situations) と書かれているため、この 2 つのどちらかを選ぶのが無難でしょう。(SDFAA_HINTED の方が若干品質重視です)

上記の 2 つよりも品質重視のモードを選ぶと、その分だけ、SDF フォントの生成にかかる時間が延びますので、注意が必要です。

Get Kerning Pairs

カーニング (文字の形状や並び順によって文字間隔を実際よりも狭く詰める処理) の情報をフォントファイルから取得するか否かの設定です。

その性質上、一般的には、プロポーショナルフォント (文字幅が可変のフォント) の場合は ON、固定幅フォントの場合は OFF にするのが良いと思います。

日本語文字の場合、全角文字 (特に漢字) は固定幅であることが多いため、筆者個人としては OFF にしておいた方が変換トラブルを避けやすくなると考えています。

SDF フォントの生成 (Generate ボタン)

ここまで設定できたら、Generate ボタンを押して SDF フォントの生成を開始します。

参考までに、筆者の環境では、以下のスクリーンショットにも出ているとおり約 5 分半 (328652.04 ms = 約 329 秒) かかりましたが、実際には、PC の性能や設定内容次第で大幅に変動します。

SDF フォントの生成結果レポート (源暎ぽっぷるの場合)

上記の生成結果レポートのうち、注意すべき項目は以下の 3 点です。

Point Size

これが大きな値になっているほど、生成されたフォントの解像度が高いことを意味します。

Point Size が小さすぎると文字がぼけて判読できなくなりますし、必要以上に大きすぎるとメモリの無駄遣いになります。

特に JIS 第二水準の漢字など、画数が多くて複雑な漢字を使用したい場合は、Point Size が十分大きくないと判読できない場合がありますので、実際に使用したい文字を画面に描画してみて「ぼやけすぎている」ようであれば、Atlas Resolution を大きくしたり、Character Sequence に含める文字の種類を絞ったり等する必要が出てきます。

SP/PD Ratio

前述のとおり、Unity 公式では 10% 前後が推奨値です。

ちなみに、描画内容次第では 10% でも足りない場合があるため、もし文字列を描画した際に変な線や四角形が目立つようであれば、SP/PD Ratio がさらに大きくなるように Padding を増やすと改善する可能性があります。(ただし、その分、前述の Point Size が犠牲になります)

Characters missing from font file

Character Set の設定で指定した文字の範囲のうち、元の TrueType フォントファイルの中に含まれていないために収録できなかった文字の一覧です。

「源暎ぽっぷる」の場合は、記号が 1 つと、第二水準に含まれる異字体の漢字が 2 つ欠けていましたが、この程度であれば通常は問題のないレベルです。

しかし、特に、素材サイト等で配布されているフリーの日本語フォントの場合、特に見た目重視のものだと、人名漢字や第二水準の漢字の一部 (または全部) が未サポートのものが多いです。

その場合、数百文字、数千文字単位で Missing characters が発生しますので、場合によってはゲームで使用するフォントの変更を検討した方が良いかもしれません。

なぜなら、これらの中には、使用頻度が意外と高い漢字が含まれているからです。

  • 人名漢字や第二水準漢字が含まれているかを簡易的に見分けるテキストの例
    • 茉莉花 (まつりか、「ジャスミン」の漢字名)
      • 「茉」も「莉」も人名で頻出ですが、フリーフォントだと未対応の場合があります。
    • 彷徨う狒狒 (さまようひひ、「世界樹の迷宮」シリーズに登場するモンスター)
      • 一部のゲームでは、このような難しい漢字を使うと思います。
      • このような難しい漢字も、フリーフォントだと未対応の場合があります。

SDF フォントの保存 (Save ボタン)

生成レポートの確認が終わったら、Save (または Save as...) ボタンを押して SDF ファイルとして保存します。

保存する場所は、一般的には Assets フォルダの中であればどこでも OK ですが、本記事では Resources.Load() API を使用して読み込むため、Assets 直下に Resources フォルダを作って、その中に保存します。

SDF フォントの保存先

実際に日本語のテキストを描画する

実際に本記事のサムネイルの例 (下記) を描画するコードを提示し、その中身を解説していきます。

本記事で例として描画する日本語テキスト

まず最初に、Start() メソッドの中に記述するコードを提示しますので、実際に試してみましょう。

  • Unity 6 では、前述のとおり enableWordWrapping が非推奨となりましたので
    testText.textWrappingMode = TextWrappingModes.NoWrap;
    のように置き換えましょう。
Camera.main.orthographicSize = 135; // 画面サイズ 480x270 ピクセル想定
Camera.main.backgroundColor = new Color(0.5f, 1, 1);

TextMeshPro testText = new GameObject("testText").AddComponent<TextMeshPro>();
testText.font = Resources.Load<TMP_FontAsset>("GenEiPOPle-Bk SDF");
testText.fontSize = 480; // フォントサイズ 48px
testText.lineSpacing = 30; // 行間をフォントサイズの 30% 空ける
testText.outlineWidth = 0.2f; // 文字の縁取りを太さ 20% で行う
testText.fontSharedMaterial.EnableKeyword("OUTLINE_ON"); // 文字の縁取りを ON
testText.alignment = TextAlignmentOptions.Center; // 中央揃え
testText.enableWordWrapping = false; // 自動改行無し
testText.sortingOrder = 0; // 画像 (SpriteRenderer) と同様、描画順序指定が必要
testText.text =
    "<color=#00ff00>Unity</color> の<color=#ff80ff>文字列描画</color>\n" +
    "<color=#ffff00>(TextMeshPro)</color>を\n" +
    "<color=#ff8000><i><u>C# Script のみ</u></i></color>\nで行う方法";

ちなみに、Unity で画面サイズを指定するには、「Game」タブの「Display」と「Scale」の間にあるドロップダウンボックスを開き、「+」マークで設定を追加することで行います。

本スクリプトでは、コメントに記載のとおり 480x270 ピクセルを想定して orthographicSize を指定している (注:orthographicSize には画面サイズの半分の値を指定) ため、この画面サイズに合わせることでフォントサイズの辻褄が合う (48 ピクセル) ようになっています。

画面サイズの設定方法

コードの内容の説明

Camera.main.orthographicSize = 135; // 画面サイズ 480x270 ピクセル想定

前述のとおり、本記事では画面サイズの縦幅 270 ピクセルを想定しているため、その半分の値を orthographicSize に設定しています。

詳細は別記事「Unity でプログラミングのみ (C# Script のみ) でゲームを作る方法」を参照して下さい。

Camera.main.backgroundColor = new Color(0.5f, 1, 1);

画面の背景色 (画像やテキスト等が何も表示されていない部分の色) を RGB で指定します。上記では白に近い水色になるように設定しています。

Unity では、R (赤)、G (緑)、B (青) を 0~1 の範囲で設定するのが標準です。(0~255 の範囲で指定できる Color32 という構造体も存在します)

TextMeshPro testText = new GameObject("testText").AddComponent<TextMeshPro>();

別記事「Unity でプログラミングのみ (C# Script のみ) でゲームを作る方法」で説明した、画像 (SpriteRenderer オブジェクト) を描画する方法と同様に、テキストを描画する場合は GameObject の中に TextMeshPro オブジェクトを「格納」する必要があります。

これは、他の Unity の機能 (オブジェクト) を C# Script から直接使用する場合も同様なので、今後も C# Script メインでゲームを作りたいと考えている方は、このやり方が Unity プログラミングでの基本パターンになるのだと覚えておきましょう。

testText.font = Resources.Load<TMP_FontAsset>("GenEiPOPle-Bk SDF");

別記事「Unity でプログラミングのみ (C# Script のみ) でゲームを作る方法」で説明した、画像ファイルや音声ファイルを読み込むのと同じ方法で、SDF フォントを読み込むことができます。

SDF フォントを読み込む場合の型名は TMP_FontAsset となります。

読み込んだオブジェクトを font プロパティに指定することで、フォントの設定を行うことができます。

testText.fontSize = 480; // フォントサイズ 48px
testText.lineSpacing = 30; // 行間をフォントサイズの 30% 空ける
testText.outlineWidth = 0.2f; // 文字の縁取りを太さ 20% で行う
testText.fontSharedMaterial.EnableKeyword("OUTLINE_ON"); // 文字の縁取りを ON
testText.alignment = TextAlignmentOptions.Center; // 中央揃え
testText.enableWordWrapping = false; // 自動改行無し
testText.sortingOrder = 0; // 画像 (SpriteRenderer) と同様、描画順序指定が必要

コメントに書かれているとおり、各種見た目 (大きさ、位置など) を各プロパティに設定することで制御しています。

このあたりの設定は、読者なりに色々と試行錯誤でいじってみたほうが理解しやすいと思いますので、細かな説明は割愛します。

他にも様々なプロパティが存在しますので、公式ドキュメントや、他のサイトの記事を参考に色々と試してみることをおすすめします。

testText.text =
    "<color=#00ff00>Unity</color> の<color=#ff80ff>文字列描画</color>\n" +
    "<color=#ffff00>(TextMeshPro)</color>を\n" +
    "<color=#ff8000><i><u>C# Script のみ</u></i></color>\nで行う方法";

指定した文字列を、HTML のようなタグ付きで画面に描画しています。

richText プロパティに false を指定することで、タグを無効化することもできます。

ここで使っている各タグの簡単な説明は以下のとおりです。
(詳細は公式ドキュメントなどを参照して下さい)

  • <color=#rrggbb></color>
    • タグで指定した範囲の文字列の色を変えます。
    • HTML と同様、R, G, B の値を 2 桁の 16 進数 (10 進数の 0~255 を、2 桁の 00~FF で表記) で指定します。
  • <i></i>
    • タグで指定した範囲の文字列を「斜体」にします。
  • <u></u>
    • タグで指定した範囲の文字列に「下線」を引きます。

描画される文字列のサイズ情報の取得

固定幅フォントを使用する場合は、fontSize に設定した値と文字数からサイズを求めることができますが、現実問題として可変幅のフォントにも対応しなければならないケースは多いです。

実際問題として、本記事で使用している「源暎ぽっぷる」フォントも、半角アルファベットは「可変幅」なので、文字数だけでは描画サイズを調べることができません。

そんなときに役に立つ API が、GetPreferredValues() API です。
(本 API の情報を日本語で紹介する Unity 記事は、おそらく本記事が初です)

GetPreferredValues() API の実例

以下の実行結果は、実際に 2 つの文字列の幅と高さ (ピクセル数) を GetPreferredValues() API をもちいて計測した結果になります。

GetPreferredValues() API による文字列描画サイズ (ピクセル数) の取得

  • 上段の「■□■□■□」は、フォントサイズ (48px) と文字数から計算できるサイズ (48 * 6 = 288) とほぼ一致しています。
  • 下段の「mmmlll」は、「m」の幅と「l」の幅の違いからも分かるように、フォントサイズと文字数だけでは横幅ピクセル数を推定できません。

このような場合に、GetPreferredValues() API が真価を発揮します。

上記の処理を実際に行っているコード

複数 (計 4 個) の TextMeshPro オブジェクトを使用するため、共通処理を MakeNewTextObject() という関数 (メソッド) に切り出しています のでご注意下さい。

  • Unity 6 では、前述のとおり enableWordWrapping が非推奨となりましたので
    testText.textWrappingMode = TextWrappingModes.NoWrap;
    のように置き換えましょう。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

public class NewBehaviourScript : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        Camera.main.orthographicSize = 135; // 画面サイズ 480x270 ピクセル想定

        TextMeshPro testText1 = MakeNewTextObject("testText1", 0, +96);
        testText1.text = "■□■□■□";

        TextMeshPro testText2 = MakeNewTextObject("testText2", 0, +48);
        testText2.text =
            testText1.GetPreferredValues().x + "x" +
            testText1.GetPreferredValues().y;

        TextMeshPro testText3 = MakeNewTextObject("testText3", 0, -48);
        testText3.text = "mmmlll";

        TextMeshPro testText4 = MakeNewTextObject("testText4", 0, -96);
        testText4.text =
            testText3.GetPreferredValues().x + "x" +
            testText3.GetPreferredValues().y;
    }

    TextMeshPro MakeNewTextObject(string name, int x, int y)
    {
        TextMeshPro testText = new GameObject(name).AddComponent<TextMeshPro>();
        testText.font = Resources.Load<TMP_FontAsset>("GenEiPOPle-Bk SDF");
        testText.fontSize = 480; // フォントサイズ 48px
        testText.alignment = TextAlignmentOptions.Center; // 中央揃え
        testText.enableWordWrapping = false; // 自動改行無し
        testText.rectTransform.position = new Vector3(x, y); // 表示座標指定
        testText.sortingOrder = 0; // 画像 (SpriteRenderer) と同様、描画順序指定が必要
        return testText;
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

コード内容の説明

新規 API を使用している箇所と、特別に注意が必要な個所のみ説明します。

testText2.text =
    testText1.GetPreferredValues().x + "x" +
    testText1.GetPreferredValues().y;

testText1 の内容 (■□■□■□) のサイズを、横幅 (x)、縦幅 (y) に分けて取得し、画面に表示しています。

testText4.text =
    testText3.GetPreferredValues().x + "x" +
    testText3.GetPreferredValues().y;

testText3 の内容 (mmmlll) のサイズを、横幅 (x)、縦幅 (y) に分けて取得し、画面に表示しています。

testText.rectTransform.position = new Vector3(x, y); // 表示座標指定

別記事「Unity でプログラミングのみ (C# Script のみ) でゲームを作る方法」で説明した画像 (SpriteRenderer) の場合と同様、表示する座標位置を指定することができます。

SpriteRenderer と異なり、transform プロパティではなく、rectTransform プロパティを使用する必要がある点に注意が必要です。

testText.sortingOrder = 0; // 画像 (SpriteRenderer) と同様、描画順序指定が必要

今回のケースでは、それぞれの文字列 (testText1~4) が互いに重なり合わないため、描画順序 (sortingOrder) を全部 0 に設定しています。

しかし、別記事「Unity でプログラミングのみ (C# Script のみ) でゲームを作る方法」で説明したとおり、複数の描画オブジェクトを 1 つの画面の中に「重ね描き」する場合は描画順序を明示的に指定する必要があるため、注意が必要です。

たとえば 2D ゲームで「メッセージウィンドウの画像」と「TextMeshPro オブジェクトで描画する文字列」を描画する際、TextMeshPro オブジェクト側の sortingOrder を大きな値に設定する必要があります。

補足:TMP Importer および SDF フォント生成の自動化

実は、本記事で紹介した以下の手順

は、Unity のエディター拡張 API を呼び出すことで、C# Script で行うこともできます。

ただし、後述のとおり SDF フォント生成の自動化には「制限」があり、やや上級者向けです。

自動化するためのスクリプトの準備

エディター拡張とは、Unity Editor に搭載されているマクロ機能に相当します。

配布するゲームアプリには埋め込まれない、特別な C# Scriptとなりますので、Assets フォルダの下に「Editor」という名前でフォルダを作成し、その中にスクリプトを格納していきます。

  • TMP Importer も含めて自動化を行っているため、本手順を実際に試す際は、プロジェクトを新たに作り直すことを推奨します。
  • 本記事では、新たに ScriptOnlyTMPro2 という名称でプロジェクトファイルを作成しなおし、変換元のオリジナルのフォントファイル (GenEiPOPle-Bk.ttf) をインポートした状態で動作確認を行っています。

Editor フォルダの作成

フォルダ名は必ず「Editor」にする必要がありますが、C# Script のファイル名は任意です。ただし、本記事で紹介する C# Script をそのままコピペする場合は、Editor.cs というファイル名で C# Script を作成して下さい。

Editor.cs の作成

Editor.cs に貼り付ける C# Script は下記です。

using UnityEditor;
using UnityEngine;
using UnityEngine.TextCore.LowLevel;
using TMPro;

class Editor
{
    [MenuItem("!!!! ScriptOnlyTMPro2 !!!!/!!!! 1. Import TMP Essentials !!!!")]
    public static void ImportTMPEssentials()
    {
        TMP_PackageUtilities.ImportProjectResourcesMenu();
    }

    [MenuItem("!!!! ScriptOnlyTMPro2 !!!!/!!!! 2. Generate SDF Font !!!!")]
    public static void GenerateSdfFont()
    {
        var srcFont = AssetDatabase.LoadAssetAtPath<Font>(
            "Assets/GenEiPOPle-Bk.ttf");
        var destFont = TMP_FontAsset.CreateFontAsset(
            srcFont, 37, 4, GlyphRenderMode.SDFAA_HINTED,
            4096, 4096, AtlasPopulationMode.Dynamic, false);

        if (destFont.TryAddCharacters(UnicodesForTMPro, false))
        {
            destFont.atlasPopulationMode = AtlasPopulationMode.Static;
            destFont.atlasTexture.name = "GenEiPOPle-Bk_Atlas";
            destFont.material.name = "GenEiPOPle-Bk_Mat";
            destFont.material.shader = Shader.Find(
                "TextMeshPro/Mobile/Distance Field");
            AssetDatabase.CreateFolder("Assets", "Resources");
            AssetDatabase.CreateAsset(destFont,
                "Assets/Resources/GenEiPOPle-Bk SDF.asset");
            AssetDatabase.AddObjectToAsset(destFont.atlasTexture, destFont);
            AssetDatabase.AddObjectToAsset(destFont.material, destFont);
            EditorUtility.SetDirty(destFont);
            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
        }
        else
        {
            Debug.LogError("Could not generate SDF.");
        }
    }

    private static readonly uint[] UnicodesForTMPro = new[]
    {
        // ■■■ ここに AutoUnicodeRangeHex.txt の内容をコピペ ■■■
    };
}

なお、UnicodesForTMPro の定義 (文字コード一覧) は非常に大きいため、別ファイルにしています。上記ソースコードのコメントにも書かれているとおり、以下のファイルの内容をコピー & ペーストして下さい。

Editor.cs によるメニューの出現の確認

上記の C# Script を保存して Unity の画面に戻ると、Unity Editor 上に 2 つのメニューが追加されるはずです。

Editor.cs によるメニューの出現

TMP Importer の自動化スクリプトの実行 - ImportTMPEssentials()

上記のメニュー !!!! 1. Import TMP Essentials !!!! をクリックして実行することで、以下のダイアログが現れます。

TMP Importer の自動化スクリプトの実行

右下の Import をクリックすることで、Import TMP Essentials と同等の処理が行われます。

SDF フォント生成の自動化スクリプトの実行 - GenerateSdfFont()

上記のメニュー !!!! 2. Generate SDF Font !!!! をクリックして、しばらく待つと以下のような小さいウィンドウが Unity Editor 上に現れます。

SDF フォント生成の自動化スクリプトの実行

そのまましばらく (筆者の PC だと概ね 1 分程度) 待つと、SDF フォントファイルの生成が完了します。

SDF フォント生成の自動化の制限事項

これは、TextMesh Pro 用のエディター拡張 API の機能が不足していることが原因ですが、以下の制約が存在します。

  • Sampling Point Size を Auto Sizing に設定できない。
    • つまり、Sampling Point Size を手動で指定する必要がある。
  • Packing Method にあたる機能が存在しない。
    • Packing Method = Fast よりも効率の悪い Packing 処理が行われる。
    • 結果的に、Sampling Point Size をより小さめの値に設定する必要があり、フォントの解像度が低下する。
  • Missing Characters が存在するとエラーになる。
    • 本記事で紹介している UnicodesForTMPro の定義からは、あらかじめ、エラーとなる Unicode 値を除外しておく必要がある。

したがって、手作業でフォント生成を行ったときのレポート (再掲) に表示される Point Size と、Missing Characters の中身を手掛かりに C# Script で設定するパラメーターを試行錯誤する必要があります。

SDF フォントの生成結果レポート (再掲)

Sampling Point Size に関しては、前述のとおり Packing Method = Fast よりも効率の悪い Packing 処理が行われることが原因で、レポートに表示されている値 (今回の場合は 39) と同じ値だと生成に失敗する可能性があります。

筆者の方で試してみたところ、37 に落とせざるを得ませんでした。

  • SDF フォントに収録される各文字の、1 文字あたりの解像度が 39 ピクセル四方から 37 ピクセル四方に減少することを意味します。
var destFont = TMP_FontAsset.CreateFontAsset(
    srcFont, 37, 4, GlyphRenderMode.SDFAA_HINTED,
    4096, 4096, AtlasPopulationMode.Dynamic, false);

Missing Characters については、レポートに表示されているとおり、Unicode 値で 2225, F929, F9DC の 3 文字が変換元の TTF フォント (=源暎ぽっぷる) に含まれていないことを意味するため、UnicodesForTMPro で指定する内容からは除外しておく必要があります。

  • 本サイトに掲載している AutoUnicodeRangeHex.txt については、上記の 3 箇所をコメントアウト (/* ~ */) で除外済みです。
private static readonly uint[] UnicodesForTMPro = new[]
{
    // ■■■ ここに AutoUnicodeRangeHex.txt の内容をコピペ ■■■
};

ただし、将来的に、TextMesh Pro 用のエディター拡張 API への機能追加により、上記の制限が撤廃される可能性もあります。少なくとも本記事の執筆時点 (Unity 2022 LTS 時点) では、上記のような制約があることを念頭に置く必要があります。

おわりに

別記事「Unity でプログラミングのみ (C# Script のみ) でゲームを作る方法」と同様に、本記事も「C# Script のみで行う」をコンセプトに Unity で文字列の描画を行う方法を紹介しました。

しかし、TextMesh Pro の機能を利用するにあたっては、むしろ事前準備のためのワークフロー (SDF フォントファイルの準備) の方が大変であることはお分かりいただけたかと思います。

このように、Unity には、他にも事前準備が必要な機能が数多く存在します。

  • プロジェクトを作成するために毎回同じような「手作業」をするのも大変なので、使用頻度の高い設定に関しては、本記事で紹介したように「エディター拡張」の API (マクロ) を利用して自動化しておくと便利です。

筆者自身も、本記事の執筆時点では Unity の使用経験が浅く、記事化できるだけの知識をあまり保有していないため、Unity 関連の技術情報を拡充していくには時間がかかりそうですが、なるべく新機能を修得するたびに (自分自身のための備忘録も兼ねて) 記事化を進めていきたいと考えていますので、よろしくお願いいたします。

主な更新履歴

  • 2024-05-05
    • Unity 6 で動作することが確認できたため更新。
      (1 箇所だけ非推奨 API になったため、置換方法も追記)
  • 2024-04-20
    • TMP Importer と SDF フォント生成を C# Script 経由で自動化する方法を追記。
    • 文字の縁取りを行うコードを修正。
      (一部条件のときに縁取りが自動的に ON にならないため)
  • 2023-03-14
    • 初版

「ゲーム開発」一覧

Unity でプログラミングのみ (C# Script のみ) でゲームを作る方法
プログラマーの方向けに、Unity で極力デザイナに頼らずゲームを作る方法を紹介します。
Unity の文字列描画 (TextMesh Pro) を C# Script のみで行う方法
(現在開いているページです)
Unity エディター拡張スクリプトで「手作業」を減らす・無くす
実際のゲーム開発で作成したエディター拡張スクリプトを例に自動化方法を説明します。
Unity の C# Script で「無」から音を産み出す方法
音声ファイル無し、スクリプトのみでゼロから音を作って鳴らす方法を紹介します。
Unity アセット暗号化 実例付き入門
AssetStudio 等のツールで素材をぶっこ抜かれないように保護する方法の基本説明です。
Unity アセットバンドル (AssetBundle) 実例付き入門
外部のアセットファイルを読み込み可能にする「アセットバンドル」機能の説明です。
Unity の Time.deltaTime で測った時間がおかしい? (float 型に注意)
Unity で Time.deltaTime をもちいて時間計測を行う際の注意点を解説します。
【暫定】RPG Maker Unite のアドオンからゲーム動作に介入する方法
従来ツクールのように、既存ゲーム動作に介入するための手法を調べた暫定記事です。
【IL2CPP対応】RPG Maker Unite のアドオンからメソッド差替する方法
iOS など IL2CPP でのビルドが必要な場合でも使えるメソッド差替方法を紹介します。