概述
当玩家角色移动到NPC身边时,NPC头上会显示“R键”图标。
此时按下R键会弹出对话框,显示角色台词,并在对话框左侧显示当前说话角色的头像,再次按下R键显示下一句台词;当对话结束,按下R键则关闭对话框。
项目细节包括:
-
将TextAsset文件中的文本显示在对话框中。
-
使用协程让台词按顺序逐个字符显示。
-
当台词还在展开过程中,提前按下R键,直接显示完整台词。
感谢麦扣老师:https://m-studio-m.github.io/
UI制作
项目中使用Panel来充当文本框,在其下方再创建Text和Image游戏物体来承担文本和图像显示工作。
特别的,项目的Canvas的RenderMode设置为World Space,这样Panel就不会遮盖住整个屏幕。
因此,Event Camera设置为主摄像机。
为了让对话框显示在最上层,Order in Layer的值为10。
之后调整好UI位置即可。
将TextAsset中的文本显示在对话框中
这一节将用到TextAsset类型变量。
当我们把一个文本文件放到项目文件夹中后,它将被转换为TextAsset。通过读取TextAsset文件,就能访问到我们在文本文件中保存的信息。
这里的“文本文件”支持上面列出的格式,这里为了方便我们就用txt格式。
我们创建脚本DialogSystem,并将它挂载在Panel上。(可以根据需要,将它挂载其他物体上)
成员变量:
-
textLabel:文本框Panel下的Text物体。
- textFile:我们准备的txt文本文件。
-
index:当前显示的文本行数索引值。
-
textList:存放字符串的列表。
成员方法:
-
GetTextFromFile(TextAsset file):在该方法中,我们要将textFile文件中的文本按照换行符分割成句子,并将句子按每一句存放进textList列表。
-
Awake():在该方法中调用GetTextFromFile(TextAsset file)方法进行句子分割。
-
Update():判断玩家是否按下R键,若按下R键,则将textList中存放的元素赋值给textLabel,实现显示台词的功能。特别的,当index指向textLabel最后一项元素时,按下R键则关闭对话框。
-
OnEnable():当对话框被调出后,自动显示第一句话。
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class DialogSystem : MonoBehaviour { [Header("UI组件")] public Text textLabel; [Header("文本文件")] public TextAsset textFile; public int index; private List<string> textList = new List<string>(); private void Awake() { //文本框就绪前将文本文件中的内容放进textList中 GetTextFromFile(textFile); } //当文本框就绪后,就显示第一条文本 private void OnEnable()//会比Start先调用 { textLabel.text = textList[index]; index++; } private void Update() { //当按下R键,且最后一条文本已经展示完,则关闭对话框,重置index if (Input.GetKeyDown(KeyCode.R) && index == textList.Count) { gameObject.SetActive(false); index = 0; return; } //当按下R键,显示下一条文本 if (Input.GetKeyDown(KeyCode.R) && textFinished) { textLabel.text = textList[index]; index++; } } //将TextAsset文件按照每一行切割成字符串,添加进textList里 private void GetTextFromFile(TextAsset file) { textList.Clear(); index = 0; //将TextAsset文件按照换行切割成字符串 //windows换行符是\r\n,再去掉空元素 string[] lineDate = file.text.Split(new char[] { '\r', '\n' }, System.StringSplitOptions.RemoveEmptyEntries); foreach (string line in lineDate) { textList.Add(line); } }
实现让台词按顺序逐个字符显示
这一节将介绍如何用协程来实现按顺序逐个字符显示台词。
我们修改脚本,并将显示说话人物的头像功能也顺便做了。
成员变量:
-
faceImage:文本框Panel下的Image物体。
-
textSpeed:文本框显示速度,每个字符显示中间的间隔时间。
-
textFinished:判断当前这一句台词是否显示完毕,若没有,则玩家按下R键不受理,不然会出现乱码。
成员方法:
-
IEnumerator SetTextUI():Update()方法中,会使用StartCoroutine(SetTextUI())开启协程,进入到SetTextUI()方法里;在SetTextUI()里,遇到yield return new WaitForSeconds(textSpeed);语句后等待textSpeed秒,在继续执行代码;同时切换说话人物头像的功能也在该方法中实现。
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class DialogSystem : MonoBehaviour { [Header("UI组件")] public Text textLabel; public Image faceImage; [Header("文本文件")] public TextAsset textFile; public int index; public float textSpeed; public Sprite faceA, faceB; private List<string> textList = new List<string>(); private bool textFinished;//当前这句话输出完了没 private bool cancelTyping;//是否结束逐个字符显示 private void Awake() { //文本框就绪前将文本文件中的内容放进textList中 GetTextFromFile(textFile); } //当文本框就绪后,就显示第一条文本 private void OnEnable()//会比Start先调用 { StartCoroutine(SetTextUI()); } private void Update() { //当按下R键,且最后一条文本已经展示完,则关闭对话框,重置index if (Input.GetKeyDown(KeyCode.R) && index == textList.Count) { gameObject.SetActive(false); index = 0; return; } //当按下R键,显示下一条文本 //只有该条文本显示完毕后才能显示下一条,不然会乱码(用了协程) if (Input.GetKeyDown(KeyCode.R) && textFinished) { StartCoroutine(SetTextUI());//协程 } } //将TextAsset文件按照每一行切割成字符串,添加进textList里 private void GetTextFromFile(TextAsset file) { textList.Clear(); index = 0; //将TextAsset文件按照换行切割成字符串 //windows换行符是\r\n,再去掉空元素 string[] lineDate = file.text.Split(new char[] { '\r', '\n' }, System.StringSplitOptions.RemoveEmptyEntries); foreach (string line in lineDate) { textList.Add(line); } } //让文本框显示让下一条文本的每个字符按先后顺序显示 //并显示说话角色的头像图片 IEnumerator SetTextUI() { textFinished = false; //因为下面textLabel的文本是累加每个字符,因此每句话之前要先清空 textLabel.text = ""; //切换文本框说话人物的图片 switch (textList[index]) { case "A": faceImage.sprite = faceA; index++; //字符A、B只用来判断该显示什么图片,不显示在文本中 break; case "B": faceImage.sprite = faceB; index++; break; } //让一句话中的每个字符按顺序先后显示 for (int i = 0; i < textList[index].Length; ++i) { textLabel.text += textList[index][i]; yield return new WaitForSeconds(textSpeed);//等待textSpeed秒后继续 } textFinished = true; index++; } }
直接显示完整台词
这一节将介绍当台词还在展开过程中,提前按下R键,直接显示完整台词的方法。
我们直接修改之前的代码,添加一个bool类型变量cancelTyping。
并在Update()方法中添加判断
-
当玩家按下R键,如果textFinished为true,说明当前台词没有在打印中,直接启用协程打印下一句台词。
-
如果textFinished为false,说明当前台词正在打印,则将cancelTyping置为true。
在协程函数SetTextUI()方法里,若检测到cancelTyping为true,则跳出打印循环,将目前textList元素(字符串)直接赋值给textLabel就实现了该功能。
为了方便“跳出打印循环”操作,我们将之前的for循环修改为while循环。
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class DialogSystem : MonoBehaviour { [Header("UI组件")] public Text textLabel; public Image faceImage; [Header("文本文件")] public TextAsset textFile; public int index; public float textSpeed; public Sprite faceA, faceB; private List<string> textList = new List<string>(); private bool textFinished;//当前这句话输出完了没 private bool cancelTyping;//是否结束逐个字符显示 private void Awake() { //文本框就绪前将文本文件中的内容放进textList中 GetTextFromFile(textFile); } //当文本框就绪后,就显示第一条文本 private void OnEnable()//会比Start先调用 { //textLabel.text = textList[index]; //index++; StartCoroutine(SetTextUI()); } private void Update() { //当按下R键,且最后一条文本已经展示完,则关闭对话框,重置index if (Input.GetKeyDown(KeyCode.R) && index == textList.Count) { gameObject.SetActive(false); index = 0; return; } ////当按下R键,显示下一条文本 ////只有该条文本显示完毕后才能显示下一条,不然会乱码(用了协程) //if (Input.GetKeyDown(KeyCode.R) && textFinished) //{ // //textLabel.text = textList[index]; // //index++; // StartCoroutine(SetTextUI());//协程 //} //按下R键后进行判断 //如果显示下一条文本的动作已就绪,则开始逐个字符显示句子 //如果正在逐个字符显示句子,则将cancelTyping设置为true—— //表示取消逐个字符显示,直接显示该条所有字符 if(Input.GetKeyDown(KeyCode.R)) { if(textFinished && !cancelTyping) { StartCoroutine(SetTextUI()); } else if(!textFinished && !cancelTyping) { cancelTyping = true; } } } //将TextAsset文件按照每一行切割成字符串,添加进textList里 private void GetTextFromFile(TextAsset file) { textList.Clear(); index = 0; //将TextAsset文件按照换行切割成字符串 //windows换行符是\r\n,再去掉空元素 string[] lineDate = file.text.Split(new char[] { '\r', '\n' }, System.StringSplitOptions.RemoveEmptyEntries); foreach(string line in lineDate) { textList.Add(line); } } //让文本框显示让下一条文本的每个字符按先后顺序显示 //并显示说话角色的头像图片 IEnumerator SetTextUI() { textFinished = false; //因为下面textLabel的文本是累加每个字符,因此每句话之前要先清空 textLabel.text = ""; //切换文本框说话人物的图片 switch(textList[index]) { case "A": faceImage.sprite = faceA; index++; //字符A、B只用来判断该显示什么图片,不显示在文本中 break; case "B": faceImage.sprite = faceB; index++; break; } //让一句话中的每个字符按顺序先后显示 //for(int i=0;i<textList[index].Length;++i) //{ // textLabel.text += textList[index][i]; // yield return new WaitForSeconds(textSpeed);//等待textSpeed秒后继续 //} //让一句话中的每个字符按顺序先后显示 //若cancelTyping为true,则跳出,显示该条文本所有字符 int letter = 0; while (!cancelTyping && letter < textList[index].Length) { textLabel.text += textList[index][letter]; letter++; yield return new WaitForSeconds(textSpeed);//等待textSpeed秒后继续 } if (cancelTyping) { textLabel.text = textList[index]; cancelTyping = false; } textFinished = true; index++; } }