【Unity】TextMesh Proでテキストを1文字ずつ表示する

こじゃらこじゃら

ノベルゲームのようにテキストを1文字ずつ表示する演出を作りたいの。

このはこのは

TextMesh Proで表示する文字数を制御してあげれば良いわ。

次のような文字送り演出を実装する方法の解説記事です。

実装方法は様々ですが、TextMesh Proであれば最大表示文字数をスクリプトから操作するだけで簡単に実現できます。

TMP_Text text;

・・・(中略)・・・

// 最大10文字まで表示
text.maxVisibleCharacters = 10;

その他の方法としては、String.Substringメソッドを使ったり、1文字ずつ文字を追加したりする方法などが挙げられます。ただし、演出中に新しいStringオブジェクトが生成され続け、余分にメモリを消費してしまう弱点があります。

本記事で解説する最大表示文字数を制御する方法では、このようなオブジェクト生成もなく、ゼロアロケーションで実現でき [1] 、更に実装も簡単というメリットがあります。

また、リッチテキスト絵文字などのタグを含むテキストにも対応できるというメリットも備えています。

TextMesh Pro公式のサンプルにも同様の例があり、以下のようなリッチテキストに対して最大表示文字数を制御することで実現しています。

本記事では、このようなTextMesh Proで文字送り演出を実装する方法について解説していきます。本記事で紹介する方法は、TextMesh ProとUnityの標準機能だけで実現するものとします。

動作環境
  • Unity 2022.2.7f1
  • TextMeshPro 3.0.6

スポンサーリンク

前提条件

TextMesh Proパッケージ、およびTMP Essential Resourcesがインストールされているものとします。

ここまでの手順が済んでいない場合は、以下手順を実施してください。 [2]

  • トップメニューのWindow > TextMeshPro > Import TMP Essential Resourcesを選択
  • Import Unity Packageウィンドウ右下のImportボタンをクリック

本記事では、次のようにパネル上に配置されたテキストに対して文字送り演出を適用していくものとします。

最大表示文字数を指定する

TMP_Textクラス(Unity UIではTextMeshProUGUIクラス)のmaxVisibleCharactersプロパティから最大表示文字数の読み書きができます。

TMP_Text text;
var delay = new WaitForSeconds(0.1f);

・・・(中略)・・・

// 1文字ずつ表示する演出
for (var i = 0; i < length; i++)
{
    // 徐々に表示文字数を増やしていく
    text.maxVisibleCharacters = i;
    
    // 一定時間待機
    yield return delay;
}

TextMeshProUGUIクラスはTMP_Textクラスを継承しているため、スクリプトから扱いたい場合はTMP_Textクラスとしてアクセスすれば良いです。

参考:Class TMP_Text | TextMeshPro | 3.0.6

文字送り演出の実装

前述のmaxVisibleCharactersプロパティの値を時間経過とともに徐々に大きくしていけば実現できます。

以下、文字送り演出の実装例です。

TypeWriteEffect.cs
using System.Collections;
using TMPro;
using UnityEngine;

public class TypeWriteEffect : MonoBehaviour
{
    // 対象のテキスト
    [SerializeField] private TMP_Text _text;
    
    // 次の文字を表示するまでの時間[s]
    [SerializeField] private float _delayDuration = 0.1f;

    private Coroutine _showCoroutine;

    /// <summary>
    /// 文字送り演出を表示する
    /// </summary>
    public void Show()
    {
        // 前回の演出処理が走っていたら、停止
        if (_showCoroutine != null)
            StopCoroutine(_showCoroutine);

        // 1文字ずつ表示する演出のコルーチンを実行する
        _showCoroutine = StartCoroutine(ShowCoroutine());
    }

    // 1文字ずつ表示する演出のコルーチン
    private IEnumerator ShowCoroutine()
    {
        // 待機用コルーチン
        // GC Allocを最小化するためキャッシュしておく
        var delay = new WaitForSeconds(_delayDuration);

        // テキスト全体の長さ
        var length = _text.text.Length;
        
        // 1文字ずつ表示する演出
        for (var i = 0; i < length; i++)
        {
            // 徐々に表示文字数を増やしていく
            _text.maxVisibleCharacters = i;
            
            // 一定時間待機
            yield return delay;
        }

        // 演出が終わったら全ての文字を表示する
        _text.maxVisibleCharacters = length;

        _showCoroutine = null;
    }
}

上記スクリプトをTypeWriteEffect.csという名前でUnityプロジェクトに保存し、適当なゲームオブジェクトにアタッチし、外部からShowメソッドを呼ぶと機能するようになります。

本記事では、UIのボタンが押されたらShowメソッドを呼ぶこととします。

以下、ここまでの流れの設定例の動画です。

次のように0.1秒間隔で1文字ずつ文字を表示する設定にしました。

ボタンの以下設定でShowメソッドを呼び出すようにしています。

実行結果

ボタンをクリックすたびに1文字ずつ文字が表示されるようになりました。

スクリプトの説明

文字送り演出を行っている処理はの部分です。

// 待機用コルーチン
// GC Allocを最小化するためキャッシュしておく
var delay = new WaitForSeconds(_delayDuration);

// テキスト全体の長さ
var length = _text.text.Length;

// 1文字ずつ表示する演出
for (var i = 0; i < length; i++)
{
    // 徐々に表示文字数を増やしていく
    _text.maxVisibleCharacters = i;
    
    // 一定時間待機
    yield return delay;
}

// 演出が終わったら全ての文字を表示する
_text.maxVisibleCharacters = length;

for文で0から文字数までループさせ、ループ内で順番にTMP_Text.maxVisibleCharactersプロパティにインデックスを指定することで徐々に文字が増えていく演出を実現しています。

指定秒数待機にはWaitForSecondsクラスを使いますが、毎回newするたびにGC Allocが発生してしまうので、それを抑えるために処理の開始時にキャッシュさせています。

コルーチンを使わない実装例

コルーチンによるGC Allocが問題になる場合、Updateイベント内などで処理を実装して解消する方法もあります。

以下、Updateで文字送り演出をするように書き直した例です。

TypeWriteEffectUpdate.cs
using TMPro;
using UnityEngine;

public class TypeWriteEffectUpdate : MonoBehaviour
{
    // 対象のテキスト
    [SerializeField] private TMP_Text _text;

    // 次の文字を表示するまでの時間[s]
    [SerializeField] private float _delayDuration = 0.1f;

    // 演出処理に使用する内部変数
    private bool _isRunning;
    private float _remainTime;
    private int _currentMaxVisibleCharacters;

    public void Show()
    {
        // 演出を開始するように内部状態をセット
        _isRunning = true;
        _remainTime = _delayDuration;
        _currentMaxVisibleCharacters = 0;
    }

    private void Update()
    {
        // 演出実行中でなければ何もしない
        if (!_isRunning) return;

        // 次の文字表示までの残り時間更新
        _remainTime -= Time.deltaTime;
        if (_remainTime > 0) return;

        // 表示する文字数を一つ増やす
        _text.maxVisibleCharacters = ++_currentMaxVisibleCharacters;
        _remainTime = _delayDuration;

        // 文字を全て表示したら待機状態に移行
        if (_currentMaxVisibleCharacters >= _text.text.Length)
            _isRunning = false;
    }
}

使い方および、実行結果は一つ目の例と同様のため割愛いたします。

スクリプトの説明

演出処理は以下部分です。

private void Update()
{
    // 演出実行中でなければ何もしない
    if (!_isRunning) return;

    // 次の文字表示までの残り時間更新
    _remainTime -= Time.deltaTime;
    if (_remainTime > 0) return;

    // 表示する文字数を一つ増やす
    _text.maxVisibleCharacters = ++_currentMaxVisibleCharacters;
    _remainTime = _delayDuration;

    // 文字を全て表示したら待機状態に移行
    if (_currentMaxVisibleCharacters >= _text.text.Length)
        _isRunning = false;
}

実行中フラグ_isRunning、次の文字表示までの残時間_remainTime、現在の表示文字数_currentMaxVisibleCharactersなどを用いて1文字ずつ表示する演出を実現しています。

全ての文字を表示できたら実行中フラグを下ろして待機状態に移行させています。

TextMesh Proの文字送りサンプル

TextMesh Proには、本記事で解説したような文字送り演出のサンプルシーンが用意されています。

サンプルシーンは、トップメニューのWindow > TextMeshPro > Import TMP Examples and Extrasを選択し、その後Importボタンをクリックしてサンプルをインポートすれば閲覧可能になります。

サンプルシーンは以下パスに格納されています。

Assets/TextMesh Pro/Examples & Extras/Scenes/17 - Old Computer Terminal.unity

実行すると、次のようなコンソールのような文字送り演出が確認できます。

文字送り演出の制御は以下パスのスクリプトTextConsoleSimulator.csで行っています。こちらはコルーチンを使って文字送り演出を実装しています。

Assets/TextMesh Pro/Examples & Extras/Scripts/TextConsoleSimulator.cs

さいごに

TextMesh Proの文字送り演出は、最大表示文字数の制御をすることで簡単に、そしてゼロアロケーションで実現できます。

スクリプトから、TMP_Text.maxVisibleCharactersプロパティの値を徐々に増やすようにすれば良いです。

アセットやライブラリまで視野を広げると、DOTweenやUniTaskなどを使って実装する方法もあります。

実装方法によっては完全にGC Allocを防げる訳ではありませんが、状況次第ではこれを許容した上で楽な方法を選択すると良いでしょう。

参考サイト

スポンサーリンク