【Unity】TextMeshProのAutoSizeは重いと聞いたので、任意のタイミングでフォントサイズを調整できるコンポーネントを作った

# (2022/01/09) 改良版を作りました
https://qiita.com/Yamara/items/cc3b1ec4e3c239278bb3

# 概要
[TMP の AutoSize は処理負荷が重い](https://forum.unity.com/threads/tmp-autosize-performance-vs-uguis-bestfit.525979/)と聞いたので、任意のタイミングでフォントサイズを調整できるコンポーネント「FontSizeFitter」を作りました。
![gif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307985/734b537d-c08a-a0c4-2447-a907bcfee396.gif)

# コード

<details><summary>Text</summary><div>

``` TextFontSizeGoodFitter.cs
using UnityEngine;
using UnityEngine.UI;

public class FontSizeFitter : MonoBehaviour
{
    const float CONTENT_RATE = 0.95f;

    [SerializeField] bool updateOnAwake = true, everyUpdate = true;
    [SerializeField] Text text;
    int startFontSize;
    Rect rect;

    private void Start()
    {
        text.horizontalOverflow = HorizontalWrapMode.Overflow;
        startFontSize = text.fontSize;
        rect = text.rectTransform.rect;

        if (updateOnAwake) UpdateFontSize(rect, text, startFontSize);
    }

    private void Update()
    {
        if (everyUpdate) UpdateFontSize(rect, text, startFontSize);
    }

    private void OnValidate()
    {
        if (text == null) TryGetComponent<Text>(out text);
    }

    /// <summary>
    /// フォントサイズを更新
    /// </summary>
    /// <param name="text">Textを表示するRect。nullでも可</param>
    /// <param name="text">Text</param>
    /// <param name="baseSize">文字が表示される最大のフォントサイズより少し小さい値を指定してください。精度に影響します</param>
    public static void UpdateFontSize(Rect rect, Text text, int baseSize = 300)
    {
        text.fontSize = baseSize;
        float rateX = text.preferredWidth / rect.width;
        float rateY = text.preferredHeight / rect.height;

        if (rateX > rateY)
        {
            text.fontSize = (int)(baseSize / rateX * CONTENT_RATE);
        }
        else
        {
            text.fontSize = (int)(baseSize / rateY * CONTENT_RATE);
        }
    }
    public static void UpdateFontSize(Text text, int baseSize = 300) => UpdateFontSize(text.rectTransform.rect, text, baseSize);
}
```

</div></details>

<details><summary>TextMeshPro</summary><div>

``` TMP_FontSizeFitter.cs
using UnityEngine;
using TMPro;

public class TMP_FontSizeFitter : MonoBehaviour
{
    const float CONTENT_RATE = 0.95f;

    [SerializeField] bool updateOnAwake = true, everyUpdate = true;
    [SerializeField] TextMeshPro text;
    float startFontSize;
    Rect rect;

    private void Start()
    {
        text.overflowMode = TextOverflowModes.Overflow;
        startFontSize = text.fontSize;
        rect = text.rectTransform.rect;

        if (updateOnAwake) UpdateFontSize(rect, text, startFontSize);
    }

    private void Update()
    {
        if (everyUpdate) UpdateFontSize(rect, text, startFontSize);
    }

    private void OnValidate()
    {
        if (text == null) TryGetComponent<TextMeshPro>(out text);
    }

    /// <summary>
    /// フォントサイズを更新
    /// </summary>
    /// <param name="rect">Textを表示するRect。nullでも可</param>
    /// <param name="text">Text</param>
    /// <param name="baseSize">文字が表示される最大のフォントサイズより少し小さい値を指定してください。精度に影響します</param>
    public static void UpdateFontSize(Rect rect, TextMeshPro text, float baseSize = 300f)
    {
        text.fontSize = baseSize;
        float rateX = text.preferredWidth / rect.width;
        float rateY = text.preferredHeight / rect.height;

        if (rateX > rateY)
        {
            text.fontSize = baseSize / rateX * CONTENT_RATE;
        }
        else
        {
            text.fontSize = baseSize / rateY * CONTENT_RATE;
        }
    }
    public static void UpdateFontSize(TextMeshPro text, int baseSize = 300) => UpdateFontSize(text.rectTransform.rect, text, baseSize);
}
```

</div></details>

<details><summary>TextMeshPro UGUI</summary><div>

``` TMP_FontSizeFitter.cs
using UnityEngine;
using TMPro;

public class TMP_UGUI_FontSizeFitter : MonoBehaviour
{
    const float CONTENT_RATE = 0.95f;

    [SerializeField] bool updateOnAwake = true, everyUpdate = true;
    [SerializeField] TextMeshProUGUI text;
    float startFontSize;
    Rect rect;

    private void Start()
    {
        text.overflowMode = TextOverflowModes.Overflow;
        startFontSize = text.fontSize;
        rect = text.rectTransform.rect;

        if (updateOnAwake) UpdateFontSize(rect, text, startFontSize);
    }

    private void Update()
    {
        if (everyUpdate) UpdateFontSize(rect, text, startFontSize);
    }

    private void OnValidate()
    {
        if (text == null) TryGetComponent<TextMeshProUGUI>(out text);
    }

    /// <summary>
    /// フォントサイズを更新
    /// </summary>
    /// <param name="rect">Textを表示するRect。nullでも可</param>
    /// <param name="text">Text</param>
    /// <param name="baseSize">文字が表示される最大のフォントサイズより少し小さい値を指定してください。精度に影響します</param>
    public static void UpdateFontSize(Rect rect, TextMeshProUGUI text, float baseSize = 300f)
    {
        text.fontSize = baseSize;
        float rateX = text.preferredWidth / rect.width;
        float rateY = text.preferredHeight / rect.height;

        if (rateX > rateY)
        {
            text.fontSize = baseSize / rateX * CONTENT_RATE;
        }
        else
        {
            text.fontSize = baseSize / rateY * CONTENT_RATE;
        }
    }
    public static void UpdateFontSize(TextMeshProUGUI text, int baseSize = 300) => UpdateFontSize(text.rectTransform.rect, text, baseSize);
}
```

</div></details>

# 使い方
適当な GameObject に追加して、Text コンポーネントを参照してください（追加した時点で同じ GameObject 内に Text コンポーネントがあれば自動で参照します）。
フォントサイズは枠の中に「M」の1文字が丁度収まるくらいにするといい感じになります。面倒なら300(最大)にしてください。

# 備考
頻繁にフォントサイズを変更する文字には、きゃぶんずさんの[ AgileBestFit ](https://blog.kyubuns.dev/entry/2021/05/14/165942)がおすすめです。
Text の HorizontalWrapMode はスクリプトによって Overflow に変更されます。不都合があれば Start() から消してください。
フォントサイズが少し大きくて文字が表示されない際は、CONTENT_RATE を小さくすると解決します。
TextMeshPro版は動作未確認なので、不具合があればコメントを下さい。