TextMeshProのフォントアセットでハマった話【Advent Calendar 12/12】
はじめに
こんにちは、クライアントエンジニアの佐川です。 普段は3Dグラフィックス関連の運用業務やライブコンテンツの業務を担当しております。
今日はUnityのTextMeshProにまつわるちょっとした小ネタの話になります。 ぜひ気楽に読んでください。
なお記事を作成するにあたって使用したUnityのバージョンは2021.3.15fとなります。
TextMeshProとは
みなさんはTextMeshProを使用しているでしょうか? TextMeshProは高機能なテキスト描画が可能となる仕組みで現在のUnityでは標準で使用可能です。 特にSigned Distance Field(*)が使われていて高い解像度の画面に対しても 奇麗に文字を描画する事ができるのでおすすめです。
(*)Unity公式ドキュメントによるSigned Distance Fieldの解説 https://docs.unity3d.com/Packages/com.unity.textmeshpro@3.2/manual/FontAssetsSDF.html
TextMeshProとTextMeshの比較
この例ではヒエラルキーのコンテキストメニューから作成できるオブジェクトで 「3D Object/Text - TextMeshPro」と「3D Object/Legacy/TextMesh」を比較しています。 (見た目にはわかりにくいですがTextMeshの方は奇麗に見せるためにフォントサイズ大きめに設定しています)
フォントファイルとフォントアセット
TextMeshProのフォントはよくあるフォントファイル(TTF形式やOTF形式)から 独自の形式のアセットファイル(フォントアセット)を作成する事でフォントが使用できます。
Static vs Dynamic
フォントアセットの設定には内部にビットマップとして展開されるStaticと ランタイムでフォントのテクスチャが形成されるDynamicがありますが ここの設定は割と重要で用途に合わせて適切に設定するとメモリ面やパフォーマンス面で効果があります。 一般的にはそのフォントで描画される文字がゲーム内で変化がない(使用される文字が決定している)場合はStaticで問題ありませんが ユーザーによる自由入力が行える場合はゲーム内に表示される文字が不定となるのでDynamicに設定する必要が出てきます。
依存関係で困った
Static設定でフォントを使用する場合、テクスチャを作成した以降変更が無い場合は フォントアセットだけ使用すればよくて元のフォントは必要なくなります。 特にAssetBundleとしてビルドするときは使用しないのであれば元のフォントを参照しない状態でビルドしたいものです。
ところが、フォントアセットを作成して元のフォントの参照を外しても 元のフォントファイルの参照が残ってしまう現象があり困りました。
SourceFontFileをNoneに設定しても…
パッケージの書き出し時に元のフォントファイルが含まれてしまう…
解決方法は何だったか
これを解決するために原因を探った結果として 「シーン上にあるTextMeshProのオブジェクトに”フォントファイルの参照を外した状態”のフォントアセットを一度セットする」 という操作を行って参照が外せることがわかりました。まるで何かの隠しコマンドみたいです。
恐らくUnityの挙動としてTextMeshProのオブジェクトにフォントアセットをセットしたタイミングで フォントアセットの参照関係を整理する処理が走っているものだと思います。
この結論にたどり着くまでに試したこと
内部で元のフォントの参照を持ったままになっていると推測し、この部分を調べるのに インスペクターのデバッグ表示を使用してそれらしい部分が無いか探しました。
通常で設定できるフォントファイルのほかに同様の参照を行っている箇所やフォントファイルのGUIDを指定している箇所があり ここの設定を外すことを試したところ元のフォントファイルの参照が外れました。
これが原因だと思ったところでデバッグ表示で設定するのも手間なので スクリプトで設定する機能を実装しました。
using System.Reflection;
using UnityEditor;
public class FontReferenceRemove
{
[MenuItem("Assets/MyTools/Font Reference Remove")]
private static void Remove()
{
foreach (var e in Selection.objects)
{
if (!(e is TMPro.TMP_FontAsset))
{
continue;
}
var fontAsset = e as TMPro.TMP_FontAsset;
var type = fontAsset.GetType();
// 下記3つのメンバー変数はリフレクションでアクセスする必要があります.
var bindingAttr = BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic;
var sourceFontFileGUID = type.GetField("m_SourceFontFileGUID", bindingAttr);
sourceFontFileGUID.SetValue(fontAsset, string.Empty);
var sourceFontFile_EditorRef = type.GetField("m_SourceFontFile_EditorRef", bindingAttr);
sourceFontFile_EditorRef.SetValue(fontAsset, null);
var sourceFontFile = type.GetField("m_SourceFontFile", bindingAttr);
sourceFontFile.SetValue(fontAsset, null);
var creationSettings = fontAsset.creationSettings;
creationSettings.referencedFontAssetGUID = string.Empty;
creationSettings.sourceFontFileGUID = string.Empty;
fontAsset.creationSettings = creationSettings;
}
}
}
これを試したところフォントファイルの参照が外れておらず さらに原因を追っていたらTextMeshProにフォントアセットを設定しているかどうか?で 参照関係に変化が出る事を突き止めました。結局これまでに試した部分は何一つ関係なかったというオチで終わりました。 (もう少し深く追うとTextMeshProのオブジェクトにフォントをセットしないでも参照を外す方法がある気はしています)
フォントファイルの参照を外す意味
ここまでしてフォントファイルの参照を外したいか?と思う方もいるかもしれませんが 前述の通り、不要なデータを含まないのでデータサイズの削減になるのですが それ以外にも使用するフォントのライセンスによっては元のフォントファイルを含むことができなかったり 別条件が発生してしまう場合があるので、これを考慮するとやはり不要であれば含まないのに越したことはないと考えます。
この方法で気をつけなければいけないこと
一度フォントファイルの参照を外してから再度フォントファイルを設定して FontAtlasを更新しようとすると、フォントアセットの再作成が必要になるような挙動をしていました。 FontAtlasの再作成が行われる可能性がある状態ではフォントファイルの参照は外さず パッケージエクスポートやアセットバンドルビルドを行うタイミングでフォントファイルの参照を 外すようにするのが良さそうです。
おわりに
今回はTextMeshProのフォントアセットを例に解説しましたが、これ以外でも参照を外したいのに外れないケースがあるかと思います。 今回は実際にアセットを使用することで解決する珍しいケースでしたが 見た目の設定だけではなく内部で処理されている事も考慮した上で原因を探る必要がある学びがありました。 Unityを使っていく上での何かの参考になれば幸いです。
明日はきた(姉御)さんです! 心温まる内容になっていそうですので、お楽しみに!