【Unity】テキスト表示にタイピング演出とスキップ機能を追加する

Unity

Unity2Dで開発を行っている者です。

メッセージウィンドウにテキストを表示してキャラクター同士に会話をさせるような仕組みを作成しているのですが、その中で以下の機能を追加しました。

  • テキストが一文字ずつ表示される「タイピング風」の演出
  • Ctrlキーなどで即時表示できるスキップ処理

「タイピング風」の演出とは、以下のように文字が1文字ずつ表示されていく演出です。

よくノベルゲームで実装されているシステムですが、読みやすさがアップするのでおすすめできます。

それぞれ実装した内容について紹介します。

まずUIを作成する

コードの紹介の前に、今回作成したコードをアタッチするUIについてご説明します。

今回は以下のような構造でUIを配置しました。

Canvas
└── DialogWindow (Image, 透明・全画面)
   └── MessagePanel (Image)
      └── MessageText (TMP_Text)

MessagePanel、MessageTextというのはメッセージを表示する部分の事です。そして、その親として透明で巨大な画像を用意してDialogWindowという名称としました。

(カラーのアルファを0にする)

透明な巨大画像を配置する理由は、メッセージウィンドウだけでなく画面上のどこをクリックしても次のメッセージに進めるようにしたかったためです。

Unityのインスペクターで見ると以下のようなイメージです。

プログラムの紹介

次に今回作成したプログラムを紹介します。早速ですが以下になります。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using TMPro;

public class DialogWindow : MonoBehaviour, IPointerClickHandler
{
    [SerializeField] private TMP_Text messageText;          // 表示するメッセージ用のTextMeshPro
    [SerializeField] private float textSpeed = 0.05f;       // タイピングスピード

    private LinkedList<string> messageQueue = new LinkedList<string>(); // 表示待ちメッセージ
    private Coroutine typingCoroutine;
    private bool isTyping = false; // タイピング中かどうか
    private string currentMessage = ""; // 現在表示しているメッセージ

    private void Start()
    {
        // テスト用メッセージ(実際は外部からSetMessagesで設定)
        SetMessages(new List<string> {
            "abcdefg",
            "hijklmn"
        });

        // 表示開始(実際は外部から呼び出す)
        Show();
    }

    void Update()
    {
        // Ctrlキーでスキップ
        if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl))
        {
            HandleCtrlSkip();
        }
    }

    // メッセージの初期設定
    public void SetMessages(List<string> messages)
    {
        messageQueue.Clear();
        foreach (var msg in messages)
        {
            messageQueue.AddLast(msg);
        }
    }

    // 表示開始
    public void Show()
    {
        if (messageQueue.Count > 0)
        {
            currentMessage = messageQueue.First.Value;
            messageQueue.RemoveFirst();

            if (typingCoroutine != null)
            {
                StopCoroutine(typingCoroutine);
            }
            typingCoroutine = StartCoroutine(TypeText(currentMessage));
        }
        else
        {
            // 表示し終わったら閉じる
            CloseWindow();
        }
    }

    // タイピング演出
    IEnumerator TypeText(string message)
    {
        isTyping = true;
        messageText.text = "";

        for (int i = 0; i < message.Length; i++)
        {
            if (message[i] == '\\' && i + 1 < message.Length && message[i + 1] == 'n')
            {
                messageText.text += '\n';
                yield return new WaitForSeconds(textSpeed * 4f);
                i++;
                continue;
            }

            messageText.text += message[i];
            yield return new WaitForSeconds(textSpeed);
        }

        isTyping = false;
    }

    // Ctrlキーでスキップ
    private void HandleCtrlSkip()
    {
        if (isTyping)
        {
            StopCoroutine(typingCoroutine);
            messageText.text = currentMessage;
            isTyping = false;
        }
        else
        {
            Show();
        }
    }

    // クリックで現在のテキストを全部表示、表示が終わってるなら次へ
    public void OnPointerClick(PointerEventData eventData)
    {
        if (isTyping)
        {
            StopCoroutine(typingCoroutine);
            messageText.text = currentMessage;
            isTyping = false;
        }
        else
        {
            Show();
        }
    }

    // ウィンドウを閉じる
    private void CloseWindow()
    {
        gameObject.SetActive(false);
    }
}

このコードをUIのDialogWindowにAddします。そしてmessageTextにUIので作成したテキストをアタッチします。

これでゲームを開始すると、タイピング風の演出で文字が少しずつ表示される様子が確認できると思います。

またCTRLキーを使用するとメッセージがスキップされて最後まで表示されていきます。

自動送り、自動最前面を追加

これでブログ題名の機能紹介は終わりになりますが、個人的に「これもウィンドウダイアログにはつけておきたい」というものが何点かあるので追加しようと思います。

追加したいのは以下になります。

  • ウィンドウダイアログを最前面に表示したい
  • タイピング演出を無効にするモードを作成する
  • クリック待ちをせずに、自動で次のメッセージを表示するモードを作成する

それぞれの理由について説明します。

まず「ウィンドウダイアログを最前面に表示したい」

ウィンドウを表示中に立ち絵表示などのprefabを配置した時、絵の方が前にきてしまうのを防ぐためです。

もちろん、絵を最初から用意されているオブジェクトにアタッチしたりするだけであれば発生しない問題ですが、対処しておいて損はないと考えます。(損があるとするなら、ウィンドウより前に何かを置きたいことが発生した時ですが、個人的には経験がないです。)

「タイピング演出を無効にするモードを作成する」

これは、例えば警告などを表示したいときには、一瞬で表示された方が自然だと思ったためです。

「クリック待ちをせずに、自動で次のメッセージを表示するモードを作成する」

戦闘中のウィンドウなどはクリック待ちにせずに移動した方が自然に感じるので実装しておきたいという意図です。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using TMPro;

public class DialogWindow : MonoBehaviour, IPointerClickHandler
{
    [SerializeField] private TMP_Text messageText;          // 表示するメッセージ用のTextMeshPro
    [SerializeField] private float textSpeed = 0.05f;       // タイピングスピード
    [SerializeField] private bool disableTypingEffect = true; // ←追加 タイピング演出OFF
    [SerializeField] private bool autoAdvance = true;         // ←追加 自動送りON/OFF
    [SerializeField] private float autoAdvanceDelay = 1.0f;    // ←追加 自動送りの待ち時間

    private LinkedList<string> messageQueue = new LinkedList<string>(); // 表示待ちメッセージ
    private Coroutine typingCoroutine;
    private bool isTyping = false; // タイピング中かどうか
    private string currentMessage = ""; // 現在表示しているメッセージ

    private void Start()
    {
        // テスト用メッセージ(実際は外部からSetMessagesで設定)
        SetMessages(new List<string> {
            "abcdefg",
            "hijklmn"
        });

        // 表示開始(実際は外部から呼び出す)
        Show();
    }

    void Update()
    {
        // Ctrlキーでスキップ
        if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl))
        {
            HandleCtrlSkip();
        }
    }

    // メッセージの初期設定
    public void SetMessages(List<string> messages)
    {
        messageQueue.Clear();
        foreach (var msg in messages)
        {
            messageQueue.AddLast(msg);
        }
    }

    // 表示開始
    public void Show()
    {
        transform.SetAsLastSibling(); // ←追加 最前面に移動

        if (messageQueue.Count > 0)
        {
            currentMessage = messageQueue.First.Value;
            messageQueue.RemoveFirst();

            if (typingCoroutine != null)
            {
                StopCoroutine(typingCoroutine);
            }
            typingCoroutine = StartCoroutine(TypeText(currentMessage));
        }
        else
        {
            // 表示し終わったら閉じる
            CloseWindow();
        }
    }

    // タイピング演出
    IEnumerator TypeText(string message)
    {
        isTyping = true;
        messageText.text = "";

        if (disableTypingEffect) // ←追加
        {
            messageText.text = message;
            yield return null;
        }
        else
        {
            for (int i = 0; i < message.Length; i++)
            {
                if (message[i] == '\\' && i + 1 < message.Length && message[i + 1] == 'n')
                {
                    messageText.text += '\n';
                    yield return new WaitForSeconds(textSpeed * 4f);
                    i++;
                    continue;
                }

                messageText.text += message[i];
                yield return new WaitForSeconds(textSpeed);
            }
        }

        isTyping = false;

        if (autoAdvance) // ←追加
        {
            yield return new WaitForSeconds(autoAdvanceDelay);
            Show();
        }
    }

    // Ctrlキーでスキップ
    private void HandleCtrlSkip()
    {
        if (isTyping)
        {
            StopCoroutine(typingCoroutine);
            messageText.text = currentMessage;
            isTyping = false;
        }
        else
        {
            Show();
        }
    }

    // クリックで現在のテキストを全部表示、表示が終わってるなら次へ
    public void OnPointerClick(PointerEventData eventData)
    {
        if (isTyping)
        {
            StopCoroutine(typingCoroutine);
            messageText.text = currentMessage;
            isTyping = false;
        }
        else
        {
            Show();
        }
    }

    // ウィンドウを閉じる
    private void CloseWindow()
    {
        gameObject.SetActive(false);
    }
}

←追加とコメントしたところがコードを追加した部分になります。

メッセージを表示する部分で「transform.SetAsLastSibling()」を使用して最前面に移動させています。

またdisableTypingEffectとautoAdvanceでタイピング演出と自動送りをOFFにできるようにしました!!

他にも「こんな機能がウィンドウにあったら便利」といったご意見があれば、ぜひコメントでお知らせください。

コメント

タイトルとURLをコピーしました