【Unity】無料版のDOTweenでTextMeshProを文字送りする

Unity のアセットの DOTween ですが、有償版のでは TextMeshPro に DOText というメソッドがあって簡単に文字送りできる機能があります。が、無料版にはこの機能はありません。ただし単純な文字送り無料版でも簡単に実装できるので実装方法を紹介したいと思います。

確認環境

  • Unity 2022.3.24f1
  • DOTween 1.2.745

仕様とデモ

以下の仕様で実装します。

  • 対象は TextMeshPro
  • N秒に 1文字ずつ表示していく
  • 1文字表示するごとにイベントを受け取れる
  • 表示が完了した後もイベントを受け取れる

実装結果はこんな感じです。

インスペクターから Play ボタンを押すと文字送りが始まって、Console に1文字表示するごとにコールバックが呼び出されています。

実装コード

DOTweenExtensions.cs

TMP_Text に対して拡張メソッドを以下の通り定義します。

// DOTweenExtensions.cs

using System;
using DG.Tweening;
using DG.Tweening.Core;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// <see cref="TweenParamAsset{T}"/> を拡張します。
/// </summary>
public static class DOTweenExtensions
{
    /// <summary>
    /// 文字送りをDOTWeenで実行します。
    /// </summary>
    /// <param name="target">文字送りをする対象</param>
    /// <param name="text">表示するテキスト</param>
    /// <param name="typingSpeed">1文字ごとの表示速度(秒)</param>
    /// <param name="onUpdate">表示が更新されたときに呼び出されるコールバック</param>
    public static Tween DOFeedText(this TMP_Text target, string text, float typingSpeed, Action<FeedTextArgs> onUpdate = null)
    {
        target.text = text;
        target.maxVisibleCharacters = 0;
        int tempPosition = 0;

        var seq = DOTween.Sequence().SetLink(target.gameObject);
        seq.Append(DOTween.To(() => tempPosition, value =>
        {
            tempPosition = value;
            if (target.maxVisibleCharacters != value)
            {
                target.maxVisibleCharacters = value;

                onUpdate?.Invoke(new FeedTextArgs(target, text, value, false));
            }
        }
        , text.Length
        , text.Length * typingSpeed)
            .SetEase(Ease.Linear));
        seq.AppendInterval(typingSpeed); // これより大きい値は使わない
        seq.AppendCallback(() => onUpdate?.Invoke(new FeedTextArgs(target, text, text.Length, true)));
        return seq;
    }
}

Feed.cs

動作確認用のスクリプトです(申し訳ないですが、UI実装が面倒だったのでインスペクターからボタン操作できるように Odin という外部アセットを使用してます)

using DG.Tweening;
using Sirenix.OdinInspector;
using Takap.Utility;
using TMPro;
using UnityEngine;

public class Feed : MonoBehaviour
{
    Tween _tween;

    [Button]
    public void Play()
    {
        _tween.Kill();

        var text = GetComponent<TMP_Text>();
        if (!text) return;
        
        _tween = text.DOFeedText(text.text, 0.07f, OnTextFeed);
    }

    // 1文字表示された毎に発生する
    void OnTextFeed(FeedTextArgs e)
    {
        Debug.Log($"Position={e.Position}, IsCompleted={e.IsCompleted}", this);
    }

    [Button]
    public void Complete()
    {
        // 最後まで表示する
        if (_tween != null && _tween.IsActive())
        {
            _tween.Complete();
        }
    }
}

これで冒頭のデモのような文字送り、1文字表示ごとにイベント発生が確認できます。