【Unity】テキストを１文字ずつ表示する処理

## はじめに
今後エンジニアとして活動するか分からないので，ここを一旦の区切りとし，自分が作ったものの中で皆の役に立ちそうなものを共有します。


## メイン
今回のスクリプトには，１文字ずつ表示する機能の他に下記のものを実装しました。

- 自動進行
- 一時停止
- 速度調整

この他に，UnityのTextMeshProに対応しています。

```C#
using UnityEngine;
using TMPro;

public class SentenceDisplay : MonoBehaviour
{
    [SerializeField, TextArea]
    private string[] _sentences;

    [SerializeField]
    private float _charaDisplayInterval = 0.1f;

    [SerializeField]
    private float _textAutoProgressInterval = 2.0f;

    [SerializeField]
    private bool _auto, _highSpeed, _wait;

    [SerializeField]
    private float _highSpeedValue = 2;

    private float _progressTimer;
    private float _autoProgressTimer;

    // リッチテキストが進行中かどうか
    private bool _inRichTextDuringProgress;

    private string _currentSentence; // 現在の文
    private int _currentLineIndex;   // 現在の行数
    private int _currentCharaCount;  // 現在の文字数

    private TextMeshProUGUI _textMeshPro;

    private void Awake()
    {
        _textMeshPro = GetComponentInChildren<TextMeshProUGUI>();
    }

    private void Start()
    {
        SetSentence();
    }

    private void Update()
    {
        var inputAction = Input.GetMouseButtonDown(0);

        // 文字の表示が完了している
        if (SentenceDisplayIsComplete())
        {
            if (_auto) _autoProgressTimer += Time.deltaTime;
            bool autoTimeElapsed = _textAutoProgressInterval <= _autoProgressTimer;
            // 未表示のテキストが残っている，尚且つ（待機時間が完了，またはボタンが押される）
            if (_currentLineIndex < _sentences.Length && (autoTimeElapsed || inputAction))
            {
                SetSentence();
            }

            return;
        }
        // 文字の表示が完了していない
        else
        {
            _autoProgressTimer = 0;

            if (inputAction)
            {
                // 文字を全て表示
                _textMeshPro.text = _currentSentence;
                _currentCharaCount = _currentSentence.Length;
            }
        }

        var specifiedRangeIn = _currentCharaCount < _currentSentence.Length;
        if (!_wait && specifiedRangeIn)
        {
            // 次に表示する文字によって処理を変更する
            switch (_currentSentence[_currentCharaCount])
            {
                // RichTextを進行中に
                case '<':
                    _inRichTextDuringProgress = true;
                    break;

                // RichTextを終了
                case '>':
                    _inRichTextDuringProgress = false;
                    _currentCharaCount++;
                    return;
            }

            if (_inRichTextDuringProgress)
            {
                _currentCharaCount++;
                return;
            }

            _progressTimer += Time.deltaTime;
            if (_highSpeed) _progressTimer *= _highSpeedValue;

            if (_charaDisplayInterval < _progressTimer)
            {
                _progressTimer = 0f;

                _textMeshPro.text = _currentSentence.Substring(0, _currentCharaCount + 1);
                _currentCharaCount++;
            }
        }
    }

    private void SetSentence()
    {
        // 現在のテキストを更新
        _currentSentence = _sentences[_currentLineIndex];
        _currentLineIndex++;
        _textMeshPro.text = string.Empty;
        _currentCharaCount = 0;
    }

    private bool SentenceDisplayIsComplete() => _currentCharaCount == _currentSentence.Length;
}
```

かなりシンプルな構造だと思うので，機能追加や修正もしやすいと思います。

他，セットするテキストの配列をJsonから取得すると，使用の幅が広がるかなと（本来ならこの取得方法含めて記事を書く予定だった）