Unity 簡易對話系統
分類
說明
這個對話系統是單機遊戲跟 NPC 對話的那種,不是玩家跟玩家即時聊天,此篇是參考 UNITY 2D NPC DIALOGUE SYSTEM TUTORIAL 再做一些的修改,外觀可以根據自己的喜好調整。
預覽
效果跟原本的教學影片一樣,不過圖片我有更換
原本影片的畫面
我修改的畫面
遊戲物件一覽
面板
- 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 加上 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());
}
}
設定遊戲物件
- 把 DialogueController.cs 腳本加在 DialogueController 遊戲物件,然後把相關的物件打拖曳到欄位中,如上面的遊戲物件圖片那樣。
- 設定 ContinueButton OnClick(),將 DialogueController 遊戲物件拖曳到如下圖 Runtime Only 的下面,然後選擇
DialogueController.SpeakNextSentence()
- 關閉 ContinueButton 遊戲物件
- 關閉 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()
{
}
}
參考
一杯咖啡的力量,勝過千言萬語的感謝。
支持我一杯咖啡,讓我繼續創作優質內容,與您分享更多知識與樂趣!