Unity 簡易對話系統


建立時間: 2022年11月6日 20:14
更新時間: 2022年11月7日 15:07

說明

這個對話系統是單機遊戲跟 NPC 對話的那種,不是玩家跟玩家即時聊天,此篇是參考 UNITY 2D NPC DIALOGUE SYSTEM TUTORIAL 再做一些的修改,外觀可以根據自己的喜好調整。

預覽

效果跟原本的教學影片一樣,不過圖片我有更換

原本影片的畫面
original preview

我修改的畫面
my preview

遊戲物件一覽

面板

GameObjects

  • Canvas: 這個是畫布,第一次新增 UI,Unity 就會自動產生一個畫布
  • DialoguePanel: 對話外框,在選單 GameObject > UI > Panel
  • CharacterImage: 大頭照,在選單 GameObject > UI > Image
  • Name: 姓名,在選單 GameObject > UI > Text - TextMeshPro
  • DialogueText: 對話內容,在選單 GameObject > UI > Text - TextMeshPro
  • ContinueButton: 下一句按鈕,在選單 GameObject > UI > Button - TextMeshPro

這些遊戲物件只要新增,依個人喜好調整即可,這裡我就不再贅述。

對話控制器

DialogueController

  • DialogueController: 透過對話控制器控制對話面板,這是一個空物件,裡面只有一個腳本,記得在DialogueController 加上 tag DialogueController 方便讓其他腳本取得此遊戲物件。

腳本

我由淺入深一個一個介紹

文字速度列舉,做為文字速度的選項

WordSpeedTypes.cs

/// <summary>
/// 對話文字數度
/// </summary>
public enum WordSpeedTypes
{
    /// <summary>
    /// 快
    /// </summary>
    Fast,
    /// <summary>
    /// 普通
    /// </summary>
    Normal,
    /// <summary>
    /// 慢
    /// </summary>
    Slow
}

對話物件,可以把它當作每一句話該出現的資料,例如:圖片、名字、對話內容。

Dialogue.cs

/// <summary>
/// 每一句對話者資料
/// </summary>
public class Dialogue
{
    /// <summary>
    /// Resources 位置
    /// </summary>
    private string _photoPath = "Images/CharacterPhotos";
    /// <summary>
    /// 姓名
    /// </summary>
    public readonly string Name;
    /// <summary>
    /// 大頭照
    /// </summary>
    public readonly string PhotoPath;
    /// <summary>
    /// 對話內容
    /// </summary>
    public readonly string Sentence;

    public Dialogue(string name, string sentence = "", string photoPath = null)
    {
        Name = name;
        // 可以填 null,帶入預設圖片
        photoPath = photoPath ?? "Default";
        PhotoPath = $"{_photoPath}/{photoPath}";
        Sentence = sentence;
    }
}

_photoPath 是 Unity Resources 資料夾底下的位置,上面的範例,大頭照放在 Assets/Resources/Images/CharacterPhotos 裡面。

接下來是比較複雜的 DialogueController,文字速度大家可以自行調整,大家可能對協程比較不了解,因為我們要打字速度像人類打字那樣,一個一個打出來的樣子,但工作不能被打字耽擱,所以我叫了另一個人(協程)幫我處理打字,這樣原本的工作就不會被耽擱了。

DialogueController.cs

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

/// <summary>
/// 對話控制器
/// </summary>
public class DialogueController : MonoBehaviour
{
    /// <summary>
    /// 打字協程
    /// </summary>
    private Coroutine _typing;
    /// <summary>
    /// 下一句按鈕
    /// </summary>
    [SerializeField]
    private GameObject _continueButton;
    /// <summary>
    /// 對話面板
    /// </summary>
    [SerializeField]
    private GameObject _dialoguePanel;
    /// <summary>
    /// 角色大頭照
    /// </summary>
    [SerializeField]
    private Image _characterImage;
    /// <summary>
    /// 對話的內容
    /// </summary>
    private Queue<Dialogue> _dialogues;
    /// <summary>
    /// 對話框文字
    /// </summary>
    [SerializeField]
    private TextMeshProUGUI _dialogueText;
    /// <summary>
    /// 說話的姓名
    /// </summary>
    [SerializeField]
    private TextMeshProUGUI _nameText;
    [SerializeField]
    private WordSpeedTypes _wordSpeedTypes;
    /// <summary>
    /// 對話速度
    /// </summary>
    private float _wordSpeed;

    // Start is called before the first frame update
    void Start()
    {
        Dictionary<WordSpeedTypes, float> wordSpeeds =
            new Dictionary<WordSpeedTypes, float>()
        {
            {WordSpeedTypes.Fast, 0.02f},
            {WordSpeedTypes.Normal, 0.05f},
            {WordSpeedTypes.Slow, 0.1f},
        };
        _wordSpeed = wordSpeeds[_wordSpeedTypes];
    }

    // Update is called once per frame
    void Update()
    {
    }

    /// <summary>
    /// 對話有一個字一個字出現的效果
    /// </summary>
    /// <returns></returns>
    private IEnumerator Type()
    {
        Dialogue dialogue = _dialogues.Dequeue();
        _nameText.text = dialogue.Name;
        _characterImage.sprite = Resources.Load<Sprite>(dialogue.PhotoPath);
        _dialogueText.text = string.Empty;

        foreach (char letter in dialogue.Sentence.ToCharArray())
        {
            _dialogueText.text += letter;

            yield return new WaitForSeconds(_wordSpeed);
        }
        _continueButton.SetActive(true);
    }

    /// <summary>
    /// 關閉對話面板
    /// </summary>
    public void CloseDialogue()
    {
        StopCoroutine(_typing);
        _dialoguePanel.SetActive(false);
    }

    /// <summary>
    /// 説下一句
    /// </summary>
    public void SpeakNextSentence()
    {
        _continueButton.SetActive(false);

        if (_dialogues.Count > 0)
        {
            _typing = StartCoroutine(Type());
        }
        else
        {
            CloseDialogue();
        }
    }

    /// <summary>
    /// 對話
    /// </summary>
    /// <param name="dialogues">對話資料</param>
    public void Talk(Queue<Dialogue> dialogues)
    {
        _dialogues = dialogues;
        _dialoguePanel.SetActive(true);
        _typing = StartCoroutine(Type());
    }
}

設定遊戲物件

  1. 把 DialogueController.cs 腳本加在 DialogueController 遊戲物件,然後把相關的物件打拖曳到欄位中,如上面的遊戲物件圖片那樣。
  2. 設定 ContinueButton OnClick(),將 DialogueController 遊戲物件拖曳到如下圖 Runtime Only 的下面,然後選擇 DialogueController.SpeakNextSentence()
    ContinueButton OnClick
  3. 關閉 ContinueButton 遊戲物件
    ContinueButton set active false
  4. 關閉 DialoguePanel 遊戲物件,意思同上。

如何開啟對話

目前我還是以腳本的方式來開啟對話,雖然我有想過非程式人員不會寫腳本,但因為目前只有我自己一個人開發遊戲…我就先不處理這個問題了。

開啟對話只要建立一個腳本,取得對話控制器,執行 Talk() 方法就會開始對話,執行 CloseDialogue() 方法就對關閉對話。

對話範例,這是一個遊戲物件,使用 Box Collider 2D 的 Is Trigger 碰到就會觸發的對話,離開時就會關閉對話。
使用腳本寫對話可以自行定義觸發對話的條件和關閉對話的條件。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 對話範例
/// </summary>
public class EndSceneDialogue1 : MonoBehaviour
{
    private DialogueController _dialogueController;

    private void OnTriggerEnter2D(Collider2D other)
    {
        Queue<Dialogue> dialogues = new Queue<Dialogue>();
        dialogues.Enqueue(new Dialogue("玩家1", "我是玩家1", "Player1"));
        dialogues.Enqueue(new Dialogue("玩家2", "我是玩家2", "Player2"));
        dialogues.Enqueue(new Dialogue("玩家1", "你好玩家2", "Player1"));
        dialogues.Enqueue(new Dialogue("玩家2", "你好玩家1", "Player2"));
        _dialogueController.Talk(dialogues);
    }

    private void OnTriggerExit2D(Collider2D other)
    {
        _dialogueController.CloseDialogue();
    }

    // Start is called before the first frame update
    void Start()
    {
        _dialogueController = GameObject.FindWithTag("DialogueController")
            .GetComponent<DialogueController>();
    }

    // Update is called once per frame
    void Update()
    {

    }
}

參考

觀看次數: 3328
unitydialoguesystem對話系統
按讚追蹤 Enjoy 軟體 Facebook 粉絲專頁
每週分享資訊技術

一杯咖啡的力量,勝過千言萬語的感謝。

支持我一杯咖啡,讓我繼續創作優質內容,與您分享更多知識與樂趣!