こんにちはcoconeでクライアントエンジニアをしているものです。
ここ最近ローカライズについて考える機会が増えておりましてそこで得た知見を記事にしてみようかと思います。
本記事 Cocone Advent Calendar 2023 15日目の記事となります。
記事の内容
UIデザイナーさんに「言語毎に使用するフォントを変えたいです」と言われたときにTextMeshProでどうにか頑張ってみた知見のまとめです。
問題点
TextMeshProを使用する場合ダイナミックフォントを使用することがあると思います。
(ダイナミックフォントの必要性や使用方法は調べればたくさん記事があると思うので省略します)
ダイナミックフォントはRuntimeでMeshを生成するために大本のフォントデータ(.ttf等)への参照が必要となります。
ローカライズ機能を搭載したアプリで言語毎にフォントを変えたいとなるとダイナミックフォントで使用するために使用するフォントがすべて必要となってしまい使用するフォントによってはかなり大きなAssetデータを扱うことになってしまいます。
フォントをすべてビルドに含めるとアプリサイズがかなり大きくなってしまうし、AssetBundle等で配信したとしても大きなデータをダウンロードすることになってしまいます。
そこで、TextMeshProのダイナミックフォントの参照先をOSにインストールされているフォントにできれば無駄にフォントデータを持つ必要がなくなるのでは?という発想のもといろいろ試してみました。
環境
以下のコードではTextMeshPro 3.2.0-pre6
のversionを使用しています。
この実装をおこなった環境(Unity2022.3.10f1)ではTextMeshProの推奨versionが 3.0.6
となっており以下の実装とは少しインターフェイスが変わっているのとどうやらメモリリークが発生するという情報もあり(自分では未検証) preview versionを使用しています。
https://forum.unity.com/threads/issue-with-removing-fonts-from-fallbackfontassettable-memory-leak.1045936/
実装したコード
前置きが長くなってしまいがしたがとりあえず完成したコートです
void AddDynamicFont(TMP_FontAsset baseFont, string fontName)
{
// OSにインストールされているFontPathを取得する
var paths = Font.GetPathsToOSFonts();
// 取得したFontPathから使用したいフォントパスを取得する
var fontPath = paths.FirstOrDefault(x => Path.GetFileNameWithoutExtension(x).Contains(fontName));
if (string.IsNullOrEmpty(fontPath))
{
return;
}
// あらかじめ生成してあるMeshと同じ設定となるようにダイナミックフォントを生成
var fontAsset = TMP_FontAsset.CreateFontAsset(fontPath, 0, baseFont.creationSettings.pointSize, baseFont.atlasPadding, GlyphRenderMode.SDFAA, 512, 512);
// 生成したダイナミックフォントにfallbackされるように設定
baseFont.fallbackFontAssetTable.Add(fontAsset);
}
コード解説
コメントの通りOSにインストールされているフォント一覧のパスを取得し、使用したいフォントのパスを検索しています。
当たり前ですがOS内に存在しないフォントを使用することはできないので対応するプラットフォーム毎に必ず存在するフォントを使用するか指定したフォントが存在しなかったときに使用する第2候補などがあるとより安全だと思います。
// OSにインストールされているFontPathを取得する
var paths = Font.GetPathsToOSFonts();
// 取得したFontPathから使用したいフォントパスを取得する
var fontPath = paths.FirstOrDefault(x => Path.GetFileNameWithoutExtension(x).Contains(fontName));
TextMeshProのDefaultFontAssetなどデフォルトのMeshをあらかじめ生成していると思うのでそのFontAssetと同じ設定になるようにダイナミックフォントを生成します。
512は生成するAtlasサイズです。
ダイナミックフォントを生成したら実際使用されるようにBaseとなるフォントのfallbackに設定します。
// あらかじめ生成してあるMeshと同じ設定となるようにダイナミックフォントを生成
var fontAsset = TMP_FontAsset.CreateFontAsset(fontPath, 0, baseFont.creationSettings.pointSize, baseFont.atlasPadding, GlyphRenderMode.SDFAA, 512, 512);
// 生成したダイナミックフォントにfallbackされるように設定
baseFont.fallbackFontAssetTable.Add(fontAsset);
まとめと注意
TextMeshProでOSにインストールされているフォントを使用する例の紹介でした。
フォントデータを増やすことなくOSにインストールされているフォントであれば自由に使用することができるようになると思います。
最後に注意点ですが、この実装ではTextMeshProのpreview versionを使用しており実際のメモリ消費量などはちゃんと検証していない状態となりますので参考にされる方は自己責任でお願いいたします…。