【TextMeshPro】既存のTextMeshProのフォントアセットを更新する方法

前提:TextMeshProのフォントアセット?

TextMeshProでフォントを使うには、ttfなどのフォントファイルのままではなく、
TextMeshProのFontAssetCreatorでTextMeshProで使える形式(拡張子は.assetになってる)に変換してから使用する。
今回はこのファイルを更新(新規にフォントを追加したいのではなく)したい場合の手順を記載する。

▼詳細はこちら www.snoopopo.com

なんで更新したいの

フォントアセットを作る際に日本語の場合、そのフォントで表示したい文字を列挙する必要がありますがそこに「髙」を入れていませんでした。
フォントの配布元サイトを確認するとフォントはこの文字に対応しているため、
フォントアセットを作るときに「髙」含めていなかった事が原因でUnityで表示できていません。

▲ない文字は□で表示されてしまいます

※たぬき油性マジック というフォントを使わせて頂きました。 tanukifont.com

既存のTextMeshProのフォントファイルを更新する方法

作成した時と同じようにTextMeshProのFontAssetCreatorを開きます。
既存のフォントアセットは削除しないで下さい。

フォントアセットのGenerationSettingsから転記する

↑のキャプチャまでの設定値は、元のフォントアセットのGenerationSettingsにあります。
なので変えたい部分以外はこの設定値を元に設定していけばOKです。

Character Set について

「Custom Characters」にすると任意の文字を入力できます。基本的に日本語を表示したい場合はこちらの設定をしているかと思います。
「Select Font Asset」には、既存のフォントアセットを指定します。
指定するとCustom CharacterListに作成した時に設定した文字の一覧が自動的に入力されます。

今回はここに「髙」を追加して、Generate Font Atlasボタンを押して作り直します。

既存のフォントアセットを上書きする

フォントファイルができたら、元のフォントアセットを上書きしましょう。
ここで元のフォントアセットと別ファイルにしてしまうと、元々参照していた部分に再設定しないといけないので上書きがよいです。
(例えば以下のようにTextMeshProのコンポーネントに既に指定している場合)

これで「髙」の字を表示することができました!

【TextMeshPro】<material>タグで使うフォントマテリアルをResouces以外の場所に置く

前提:<material>タグ?


TextMeshProの<material>タグを使うとテキスト内の一部に指定したマテリアル(shader)に適応することが出来ます。

ここだけ<material="testTmproMaterial">マテリアル</material>を適応してるよ

このとき使うマテリアルは、TMP SettingsのDefault Font Asset に設定された
Resoucesフォルダ配下に予め配置しておく必要があります。(デフォルトだとResouces/Fonts & Materials/配下になります。)


しかしResouces配下におかないといけないというのは不都合が出てくる場合があります。
Addressableの登場から昨今はResoucesを積極的に使う現場は少なくなっているかと思います。
仮にフォントファイルをAddressableで管理した場合、
このフォントマテリアルもAddressable側に配置しないと二重でメモリに読み込まれたりといった問題も発生するようです。

本題:フォントマテリアルをResouces以外から読み込めるようにする

MaterialReferenceManager.AddFontMaterialを使って実行時にマテリアルを読み込む事で、
Resouces以外の場所にあるフォントマテリアルを使う事ができます。

public class TestTmproMaterial : MonoBehaviour
{
    [SerializeField] private Material mat; //今回は単純にしたいので設定したいマテリアルをここにアタッチしてます

    void Start()
    {
        int hashCode = TMP_TextUtilities.GetSimpleHashCode(mat.name); //GetHashCodeではないので注意
        if(!MaterialReferenceManager.TryGetMaterial(hashCode, out Material tmpMat))
    {
            MaterialReferenceManager.AddFontMaterial(hashCode, mat);
    }
    }
}

【uGUI】ContentsSizeFitterのサイズ反映をすぐに行う方法と注意点

前提

ContentsSizeFitterは設定したコンテンツにあわせてRectTransformのサイズを調整してくれるものだが、これは即反映されない。

たとえば以下のように、テキストにContentsSizeFitterを設定し、テキストの親クラスにテキストの周りに表示する枠画像をもった状態があるとする。

そして実行時にテキストの内容を「あいうえお」→「はろーわーるど!」に変えて、枠画像のサイズもそれにあわせて調整したいとする。

using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class test_contentsSizeFitter : MonoBehaviour
{

    public Image frame;
    public TextMeshProUGUI tmp;

    void Start()
    {
        tmp.SetText("はろーわーるど!");
        frame.rectTransform.sizeDelta = tmp.rectTransform.sizeDelta + new Vector2(40, 60);
    }

}

これの実行結果は↓で意図しないものになる。

ContentsSizeFitterのサイズ反映すぐに行う

そこで、ContentSizeFitter の SetLayoutHorizontalSetLayoutVerticalを同じフレーム内で呼び出し、
即時ContentsSizeFitterのサイズ反映を行うようにする

using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class test_contentsSizeFitter : MonoBehaviour
{

    public Image frame;
    public TextMeshProUGUI tmp;

    void Start()
    {
        tmp.SetText("はろーわーるど!");

        ContentSizeFitter csf = tmp.GetComponent<ContentSizeFitter>();
        csf.SetLayoutHorizontal();        //←add!!!
        csf.SetLayoutVertical();        //←add!!!

        frame.rectTransform.sizeDelta = tmp.rectTransform.sizeDelta + new Vector2(40, 60);
    }
}

実行結果は以下になり、これで意図した対応ができた。

注意点:ContentSizeFitter をアタッチしたGameObjectがアクティブでないと働かない

非アクティブになっているGameObjectに対して同等の処理を行いたい場合には注意が必要だ。

注意が必要な状態というのは以下のどちらもあてはまる。ようはヒエラルキーでグレーアウトして非表示になっているという状態。
①ContentSizeFitterをアタッチしたGameObject自体が非アクティブの場合


②ContentSizeFitterをアタッチしたGameObjectの親が非アクティブの場合


こんなケースはないと思う人もいるかもしれないが、なくはない。
非表示のゲームオブジェクトにテキストを設定した後、後で特定のタイミングで表示するようなケースだ。

というわけで、実行時には非アクティブなテキストの内容を「あいうえお」→「はろーわーるど!」に変えて、枠画像のサイズもそれにあわせて調整。その後、ボタンを押すとテキストが表示されるという状況を作ってみる。

using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class test_contentsSizeFitter : MonoBehaviour
{
    public Button activeButton;

    public Image frame;
    public TextMeshProUGUI tmp;

    void Start()
    {
        activeButton.onClick.AddListener(() =>
        {//ボタンを押すと予め、非表示だったテキストが表示される
            this.tmp.gameObject.SetActive(true);
        });

        tmp.SetText("はろーわーるど!");

        ContentSizeFitter csf = tmp.GetComponent<ContentSizeFitter>();
        csf.SetLayoutHorizontal();
        csf.SetLayoutVertical();

        frame.rectTransform.sizeDelta = tmp.rectTransform.sizeDelta + new Vector2(40, 60);
    }

この実行結果は以下で意図した結果にならないのだ。


というわけでこれに対応するには、以下が考えられる。 ①表示したときにContentSizeFitter の SetLayoutHorizontalSetLayoutVerticalを呼ぶ ②テキスト設定時にサイズ反映するタイミングで一旦アクティブにする

②で対応しようとすると以下のようなソースになる。

using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class test_contentsSizeFitter : MonoBehaviour
{
    public Button activeButton;

    public Image frame;
    public TextMeshProUGUI tmp;

    void Start()
    {
        activeButton.onClick.AddListener(() =>
        {
            this.tmp.gameObject.SetActive(true);
        });

        tmp.SetText("はろーわーるど!");

        ContentSizeFitter csf = tmp.GetComponent<ContentSizeFitter>();
        bool active = csf.gameObject.activeInHierarchy;
        csf.gameObject.SetActive(true); //一時的にアクティブに
        csf.SetLayoutHorizontal();
        csf.SetLayoutVertical();
    if (!active)
    {
            csf.gameObject.SetActive(false); //元に戻す
    }

        frame.rectTransform.sizeDelta = tmp.rectTransform.sizeDelta + new Vector2(40, 60);
    }
}

これで意図した表示になった↓

【uGUI】自作shaderのマテリアルをオブジェクト個別に作成して別の値を適応させる

起きた事象

uGUIのImageに自作のshaderを適応させようとした際に、
マテリアルの値を更新すると適応した全てのImageオブジェクトの反映されてしまいました。
期待値は変更した1つのImageオブジェクトにだけ変更した値が適応されたいです。

【環境】
unity2021.3.3f1

対処法

これは複数のImageオブジェクトで使用しているマテリアルが同一インスタンスのために発生しています。
なので、別のインスタンスを生成してセットしてあげればよいだけです。

Material baseMat = this.image.material;
this.image.material = new Material(baseMat.shader); //new して新しいインスタンスを作って設定する

this.image.material.SetFloat("_Alpha", alpha); //適応したい変更を新しく作ったマテリアルに適応する

これで、変更したいImageオブジェクトだけに変更を適応できるようになりました!

補足

マテリアルの値を変えるとそのたびにマテリアルが複製される。という有名な話がありますが、それはuGUIには当てはまりません。
※3Dオブジェクト等のUI以外のマテリアルを操作した際には発生するのでその時はMaterialPropertyBlock等を使って対処する必要があります。

試しに先程のコードに以下のようなログを仕込んでみると
変更前後で同じインスタンスIDがかえってきているので、これで複製されていない事が分かります。

Debug.Log("before:" + this.image.material.GetInstanceID());
this.image.material.SetFloat("_Alpha", alpha); //適応したい変更を新しく作ったマテリアルに適応する
Debug.Log("after:" + this.image.material.GetInstanceID());