[Unity] Textコンポーネントでテキストを切り捨てずに全文表示させる方法

- TextComponentにてテキスト(の1部)が切り捨てられ、表示されなくなる場合がある

![truncate.png](https://qiita-image-store.s3.amazonaws.com/0/102664/4cd1d500-81a7-611a-6ae5-1a0620afc87f.png)

# 原因
- https://docs.unity3d.com/jp/current/ScriptReference/HorizontalWrapMode.html
- https://docs.unity3d.com/jp/current/ScriptReference/VerticalWrapMode.html
- TextComponentはInspector上で **(Horizonal|Vertical) Overflow** を設定できる
- **HorizonalOverflow** はデフォルトで **wrap**
  - テキストが水平の端に到達に到達したときに折り返すようになっている
- **VerticalOverflow** はデフォルトで  **truncate**
  - テキストが垂直の端に到達したときに **切り捨てる** ようになっている
- なので **「デフォルトの設定ではRectTransformで指定した領域を垂直方向に越える場合、テキストが描画されない」**という現象が起きる

![overflow.png](https://qiita-image-store.s3.amazonaws.com/0/102664/be156fa0-9280-7730-76d8-590f25253031.png)

### 意図しない発生
また、以下のような要因で意図せず発生して、テキストが描画されなくなる場合があるので対策しておくに越したことは無い

- フォントを差し替えた場合
- Script等で動的に長いテキストを表示した場合
  - 特にユーザが任意に入力できるケース等
- 編集中の状態とは異なる解像度の場合
  - AndroidやiOS等の実機での実行
  - UnityEditor上でのMaximize
  - CanvasScalarの設定変更 ... etc

# 解決方法
## (1) TextComponentにてOverflowの設定を変更する
- **VerticalOverflow** を **overflow** にすることによって垂直方向の描画領域を超えてもテキストが切り捨てられない
- このアプローチで解消した場合、意図しない要素の垂直方向へのはみ出しに注意

![verticaloverflow.gif](https://qiita-image-store.s3.amazonaws.com/0/102664/e1896b5c-19d2-6bd4-e7be-d96f69c0f268.gif)

## (2) TextComponentにてBestFitを指定する
- https://docs.unity3d.com/ja/current/Manual/script-Text.html
- `Unity がサイズプロパティーを無視して、単にコントロールの矩形にテキストを合わせるようにするか`
- BestFitにチェックを入れると **MinSize** / **MaxSize** の項目が出現する
  - テキストが描画領域を超えた場合、フォントサイズを段階的に小さくするプロパティ
  - **MaxSize** にはデフォルトでFontSizeで指定した値が格納される
  - **MinSize** に設定した値よりフォントサイズが小さくなることはない
- このアプローチで解消した場合、最大文字数に注意
  - スクリプト等で意図しない長さの文字を格納した場合、極端に小さくなってしまう

![bestfit.gif](https://qiita-image-store.s3.amazonaws.com/0/102664/1f441994-1d31-9a75-ff93-f99548dd9678.gif)

### 追記
- https://unity3d.com/jp/learn/tutorials/topics/best-practices/optimizing-ui-controls?playlist=44987
- 上記のuGUIのベストプラクティスにおいて以下のような記述がある
  - パフォーマンスを重視する場面や複数のテキストで適応したい場面では回避したほうが良さそう

```
# Best Fit とパフォーマンス
基本的には、UI TextコンポーネントのBest Fit設定は一切使用しないでください。
Best Fitの計算には膨大な量のテストが必要なので、これによって他のTextコンポーネントが使用しているグリフが削除されてしまい
適正なフォントサイズが算出された後に少なくとももう１回、フォントアトラスのリビルドが余儀なくされます。
フォントアトラスの頻繁なリビルドは、ランタイムのパフォーマンスを急激に劣化させるだけでなく、
メモリの断片化も引き起こします。この問題は、Best Fitに設定されたテキストコンポーネントの量が多ければ多いほど悪化します。
```

## (3) ContentSizeFitterをアタッチする
- [[Unity] ContentSizeFitterを使ってコンテンツサイズによって可変なUI要素を作る](http://qiita.com/lycoris102/items/2ca34aafa1144439324e)
- [コンテンツのサイズに合わせた UI 要素を作る](https://docs.unity3d.com/ja/current/Manual/HOWTO-UIFitContentSize.html)
- TextComponentがアタッチされたGameObjectにContentSizeFitterもアタッチするアプローチ
  - テキストに合わせてRectTransformが可変する
- (1)と似たようなアプローチだが、明確な描画領域を取得したいケースで便利
  - (1) 同様に意図しない要素のはみ出しに注意
- Listの場合、ContentSizeFitterをアタッチしなくてもLayoutPropertiesはTextによって制御されるし、このアプローチを取る理由はそんなに無さそう

![csf.gif](https://qiita-image-store.s3.amazonaws.com/0/102664/894af483-219d-9ed2-b89e-0a17f0974f00.gif)

## (4) 余談: 長い文章に対してellipsis(...)を適応するScript
- はみ出し/フォントの縮小を考慮したくない場合、もうテキスト自体を短くするしかない...!!
  - その場合、単に切り捨てるのではなく、ellipsisを付けて省略されたことを認知したい
- 以下、テキストを当てはめつつ、描画領域を超えていたらellipsis(...)を付けるメソッド例
  - 文字数 > 描画される文字数を満たすときに ellipsis(...)を適応する
  - 上記の条件により、確実に全ての文字が描画される(1)~(3)のアプローチとの併用は出来ない

```csharp
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;

public static class TextExtension
{
    // XXX operating in no overflow (Horizonal|Vertical)
    public static void SetTextWithEllipsis(this Text textComponent, string value)
    {
        // create generator with value and current Rect
        var generator     = new TextGenerator();
        var rectTransform = textComponent.GetComponent<RectTransform>();
        var settings      = textComponent.GetGenerationSettings(rectTransform.rect.size);
        generator.Populate(value, settings);

        // trncate visible value and add ellipsis
        var characterCountVisible = generator.characterCountVisible;
        var updatedText = value;
        if (value.Length > characterCountVisible)
        {
            updatedText = value.Substring(0, characterCountVisible - 3);
            updatedText += "...";
        }

        // update text
        textComponent.text = updatedText;
    }
}
```

### 以下呼び出し例

```csharp
using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class TextTest : MonoBehaviour
{
    public string value;

    void Update()
    {
        this.GetComponent<Text>().SetTextWithEllipsis(value);
    }
}
```

![ellipsis.gif](https://qiita-image-store.s3.amazonaws.com/0/102664/85382bfc-9d71-a796-5142-eacd934b572e.gif)