🥝

UnityのTextMeshProで日本語と絵文字に正しく対応する

2023/07/28に公開

TextMeshProで日本語に対応する方法

ここではTextMeshProで日本語と絵文字を扱うための現実的な対応方法をまとめる。

よくある間違い(ベストではない方法)

「TextMeshPro 日本語」などで検索するとよく見かけるのが、japanese_full.txtというASCIIとひらがなや漢字全般をひとまとめにしたファイルを使用する方法。この方法はテクスチャサイズが大きくなりすぎるためメモリを圧迫したりアプリのサイズが増えてしまう。
kgsi/japanese_full.txt
また「このようなファイルを使用する方法だけ」ではこのテキストに載っている文字しか表示できない。
チャット画面などでは常用漢字ではない文字をユーザーが入力する場合などもあるから日本語を正しく扱うという視点で考えると採用できない。

そこでDynamicフォントとの併用

この問題を解決するためには3つのSDFを作成して併用する事が必要となる。

  • ASCII
  • 最小限の日本語の単語リスト
  • Dynamicフォント

具体的な手順はこの記事を参考にさせてもらう。
本当に使える!TextMeshProでの「日本語」「多言語」対応方法 - きゅぶろぐ

Dynamicフォントは必要に応じて自動的にテクスチャに文字を登録してくれるので、すべての文字をあらかじめリストアップしてSDFにしなくて良いメリットがある。
その反面、動的にテクスチャを更新する処理が重いため、ある程度の文字はあらかじめSDFにしておいて残りの文字は動的に対応するという事で現実的な解決策になる。

Dynamicフォントは動的にテクスチャに文字が書き込まれるため、アプリ起動時、終了時にはテクスチャをクリアしてやる必要がある。
この記事ではUniTask、Addressablesの使用を前提としてDynamicフォントのテスクチャクリアを行うスクリプトを使用しています。
また、この記事では多言語化は考えず日本語のみを表示する前提とします。

Font Asset(SDF)の作成手順

1つのフォントにつき、3つのSDFを作成してAddressablesに登録する必要がある。

  • Base (Extend ASCII)
  • 基本的な日本語と常用漢字 (CharacterFile)
  • Dynamic (1024x1024, Multi Atlas Texturesオン)

Addressablesの登録を手動でやるのは面倒なので自動化する。
ここの記事のソースを少し修正して使わせてもらっている。
【Unity】Addressable Asset Systemの登録自動化コード公開(ファイル移動によるグループ・ラベル再設定対応版) – YuumeDocs

Dynamicの注意点

作成した時点では設定がStaticになっているので、自分でDynamicに変更する必要がある。
さらに、Multi Atlas Texturesのチェックをオンにする。
また、Dynamicのテクスチャは自動でクリアされないためスクリプトでテクスチャをクリアする必要が出てくる。
そのためにAddressablesに登録して動的に取得したりする必要が出てくる。

ここではフォントはNotoSansJP Regularを使用します。

Font Assetを作成する

Baseの作成

Window > TextMeshPro > Font Asset Creatorを選択。

  • Sampling Point Size: Auto Sizing
  • Padding: 2
  • Packing Method: Optimum
  • Atlas Resolution: 512 x 512
  • Character Set: Extended ASCII
  • Render Mode: SDFAA
  • Atlas Population Mode: Static

Save AsでAssets/AAS/Fonts/NotoSansJP-Regular-SDF-Base.assetに保存。
保存先のフォルダは任意です。自分は例のAddressablesの登録の自動化スクリプトを使ってAssets/AAS/以下のフォルダのファイルを自動登録するようにしているためこのようになっています。

日本語用のFont Assetの作成

Sampling Point Size: Auto Sizing
Padding: 2
Packing Method: Optimum
Atlas Resolution: 2048 x 2048
Character Set: Characters from File
ja: 基本的な日本語 + 常用漢字(2136字)
Render Mode: SDFAA
Atlas Population Mode: Static

リンク先の日本語の文字のリスト(改行なし)をテキストファイルにUTF-8で保存し、Assetsの適当な場所へコピーしてUnityから選択可能にしておく。
自分はとりあえず、japanese_minimum.txtとして保存した。
Auto Sizingで最適なPoint Sizeを探すため時間がかかるので気長に待つ。
Assets/AAS/Fonts/NotoSansJP-Regular-SDF-ja.assetとして保存。
ここで重要なのは「Missing Characters」が0である事。リスト内のすべての文字に対応できている事を確認する。

Dynamicの作成

  • Sampling Point Size: Custom Size (jaと合わせる)
  • Padding: 2
  • Packing Method: Optimum
  • Atlas Resolution: 1024 x 1024
  • Character Set: Unicode Range (Hex)
    何も指定しない
  • Render Mode: SDFAA
  • Atlas Population Mode: Dynamic
  • Multi Atlas Textures: true

さきほどのjaのPoint Sizeが39になったので、Dynamicでも39を指定する。

Assets/AAS/Fonts/NotoSansJP-Regular-SDF-Dynamic.assetとして保存。
保存したらUnity上で選択して、インスペクタで以下の設定を行う。

  • Atlas Population Mode: Dynamic (デフォルトはStatic)
  • Multi Atlas Textures: チェックオン

Fallback Font Assetsの設定

BaseのSDFファイル(ここではNotoSansJP-Regular-SDF-Base.asset)を選択。

インスペクタで「Fallback Font Assets」の項目で+ボタンを押して以下の順番で追加して保存。

  • NotoSansJP-Regular-SDF-ja.asset
  • NotoSansJP-Regular-SDF-Dynamic.asset

これにより、NotoSansJP-Regular-SDF-ja.assetに登録されていない文字でもDynamicに動的にテクスチャが作成されて表示できるようになる。
ただし、エディタの実行終了後もDynamicのテクスチャに文字が書き込まれたままとなってしまうのでスクリプトで動的にテクスチャのクリアをしてやる必要がある。
これをしないと、gitでバージョン管理している場合にDynamicに文字が登録されるたびに差分が発生してしまう。

ここまでの作業で日本語対応の基本は終了。
TextMeshProを作成してNotoSansJP-Regular-SDF-Base.assetを指定すれば日本語が表示できるし、常用漢字以外の文字を指定しても表示される事が確認できるはず。
常用漢字以外の漢字の例としては例えばこんなものがある。

曖盂烏軋鬱斡云按暈庵穢鞍曳闇洩已裔
夷穎頴畏嬰韋翳帷腋萎曰椅奄葦宛彙怨
俺謂閾袁婉焉尹堰咽淵殷淵焔蝦

これらの文字をTextMeshProで表示してみると、DynamicのCharacterTableやGryphTableにこれらの文字が追加されるのが分かる。これをその場でクリアする方法がエディタにはない。
DynamicのインスペクタでClear Dynami Data On Buildにチェックを入れたら再生ボタンを押した時にクリアされるけど少し不便というか結局gitで管理していると差分が出てしまう。
また、静的に文字を設定している場合はどうしてもDynamicに文字が登録されてしまうのでそのへんは諦める。

Dynamicのテクスチャをクリアするスクリプト

※自分はAddressablesには拡張子なしでアドレスを登録するように調整しています。
普通に手動でやるとたぶん拡張子がついた状態になるので注意。

Dynamicのテクスチャをクリアするには、DynamicのTMP_FontAssetを動的にロードしてClearFontAssetData()を実行するだけで良い。
ここではサンプル用のシーンを例にするので起動時と終了時にクリアする事にしている。
実際のアプリケーションではもっと複雑になるかもしれない。

using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.AddressableAssets;
using Cysharp.Threading.Tasks;

public class TestScene : MonoBehaviour
{
    private TMP_FontAsset _regularDynamicFont;

    async UniTaskVoid Start()
    {
        await LoadDynamicFont();
    }

    private async UniTask LoadDynamicFont()
    {
        _regularDynamicFont = await Addressables.LoadAssetAsync<TMP_FontAsset>("Fonts/NotoSansJP-Regular-SDF-Dynamic");
	
        // ロード時にも念のためテクスチャをクリアしておく
        _regularDynamicFont.ClearFontAssetData();
    }

    // シーン終了時にテクスチャのクリアとAddressablesのリリースを行う
    private void OnDestroy()
    {
        _regularDynamicFont?.ClearFontAssetData();
        
        if (_regularDynamicFont != null)
        {
            Addressables.Release(_regularDynamicFont);
        }
    }
}

「The character used for Underline is not available in font asset」の 警告への対応

ここまでの作業で日本語の表示ができるようになったのだが、実行してみるとUnityのログに「The character used for Underline is not available in font asset」という警告が毎回表示される。これのせいなのか分からないがiOSの実機でTextMeshProの文字が表示されない現象が確認できた。Androidの実機やMacのUnityEditorでは正常に表示される。

Assets/TextMesh Pro/Resources/TMP Settings.assetを選択。
インスペクタでDynamic Font Settings にある Disable Warning にチェックを入れる事で、警告を無効にする。これにより対応完了。

絵文字に対応する

Full Emoji Support APIを導入する

Unityで絵文字に対応するにはFull Emoji Support Apiを使うのが一般的というかそれしか情報が出てこないのでこれを使用する。
このライブラリがTextMeshPro専用のため、Unityで絵文字に対応するにはTextMeshProの使用が必須となる。

導入方法は簡単。
パッケージマネージャーからインストールするか、Packages/manifest.jsonを直接編集する。

Packages/manifest.json
{
  "dependencies": {
    "com.kyub.emojisearch": "https://gitlab-ci-token:glpat-c6ymx9eBXkdQhFhCMaLg@gitlab.com/KyubInteractive/kyublibs.git#com.kyub.emojisearch-1.1.5",
    ...
  }
}

Full Emoji Support APIの使い方

TextMeshProを作成する

まずはいつも通り、UI > Text - TextMeshProを選択して作成する。

TextMeshProのコンポーネントを削除する

いきなりだが、TMP_Emoji Text UGUIと入れ替える必要があるので、TextMeshProのコンポーネントを削除する。

TMP_Emoji Text UGUIコンポーネントを追加する

続けて「Add Component」ボタンでTMP_Emoji Text UGUIを選択する。

Extra Settingsで絵文字データを指定する

個別のTMP_Emoji Text UGUIに対して設定する方法と全体で処理する方法がある。
ここではアプリ全体で絵文字に対応させる方法を説明する。

Assets/TextMesh Pro/Resources/TMP Settings.assetをクリック。
Default Sprite AssetにEmojiData_v14_google_appleを指定する。
これによりTMP_Emoji Text UGUIのExtra Settingsが未指定の場合デフォルト値としてEmojiData_v14_google_appleを使うようになる。
いちいち個別に設定するのは手間だし非効率なので、通常はこの方法がいいだろう。

こんな感じで絵文字が表示できるようになる。

日本語および絵文字の対応についてはここまでで完了。
この後はおまけ。

絵文字が汚い(画質が低い)問題に対応する

Full Emoji Support APIに同梱されているEmojiData_v14_google_appleのスプライトシートの画像が荒いので最近の端末で見ると絵文字だけ汚く見えてしまう。
たぶんメモリを節約するためにわざと画質を下げているものと思われるが、不満があるので自分で画像を用意する方法についても記述する。

絵文字の文字コードに対応するスプライトシートの座標を定義したJSONと絵文字のスプライトシートを用意できれば良い。

絵文字の画像を用意する

このリポジトリを使わせてもらう。
https://github.com/iamcal/emoji-data

※node.jsを利用できる環境が必要。
任意のディレクトリを作成して以下を実行。

npm install emoji-datasource
npm install emoji-datasource-apple
npm install emoji-datasource-google

現時点だと、v15の絵文字データがインストールされる。
次に以下の3つのファイルを利用する。

node_modules/emoji-datasource/emoji_pretty.json
node_modules/emoji-datasource-apple/img/apple/sheets-clean/64.png
node_modules/emoji-datasource-google/img/google/sheets-clean/64.png

これらを以下のファイル名に変更する。
ファイル名はなんでもいいのだが、Full Emoji Support APIに合わせたほうが分かりやすいだろう。
EmojiData_v15.txt
EmojiData_v15_apple_64.png
EmojiData_v15_google_64.png

Assets/EmojiDataAssets/Sheets_v15というフォルダにコピーしておく。
jsonの拡張子を.txtに変更しているのはUnityが.jsonを認識してくれないため。

TextMeshPro用のSpriteAssetを作成

Full Emoji Support Apiを導入すると、
Window > TextMeshPro > Sprite Emoji Importerというメニューが増えているのでこれを使用する。

テクスチャのサイズが2048になっているがこのままでは正しく動作しないので、4096に変更する。
以下の2つの画像を選択してテクスチャのMaxSizeを4096にする。

  • EmojiData_v15_apple_64.png
  • EmojiData_v15_google_64.png

実際の画像サイズは4026x4026だが問題ない。

  • SpriteDataSource: EmojiData_v15.txt
  • GridSize: 64x64
  • Padding: 1x1
  • Spacing: 2x2
  • Sprite Texture Atlas: EmojiData_v15_google_64.png

作成したら、EmojiData_v15.assetとして保存。
次にEmojiData_v15.assetを選択。
Override iOS Definitionにチェックを入れて、EmojiData_v15_apple_64.pngを指定する。

デフォルトはGoogle(Android)の絵文字を表示しながら、iOSではApppleの絵文字を表示するように処理してくれる。
実機でSpriteSheetの上書きが動作するので、UnityEditor上ではPlatformをiOSにしていても絵文字はAndroidのままである。

Discussion