先看看实现的最终效果
前言
之前的对话系统因为存在一些错误和原作者不允许我分享,所以被我下架了,而且之前对话系统确实少了一些功能,比如最基本的逐字打印功能,原本来是打算后面补充的。
对话系统在游戏中实现太常见了,所以我又重新去找了一些对话系统的课程进行学习,把实现过程和笔记分享出来,后面肯定会用到。
本文是参考b站麦扣老师比较老的课程了,我已经看完了,后面发现缺失
了挺多功能的:
- 比如扩展性不好,多NPC很难将对话分开
- 快速显示的实现过于麻烦了
- 对话框显示在世界坐标,UI无法适配屏幕的变化
- 文本只支持显示内容,不支持显示角色名称和人物的不同表情变化
- 缺少控制某些文字样式变化,比如修改颜色
所以我改动的地方可能比较多,因为我想实现的是一个通用的对话脚本,可以很方便的对多个NPC绑定不一样的对话内容,当然,麦扣老师的视频链接我会放在文章底部,感兴趣的也可以去看看原版,对照着学习!
素材
素材下载地址:
https://bakudas.itch.io/generic-rpg-pack
前期准备工作
1. 简单绘制地形
关于TileMap的使用,这里就不再过多介绍了,感兴趣的可以查看我之前的文章:
【Unity小技巧】Unity2D TileMap的探究(最简单,最全面的TileMap使用介绍)
2. 绘制对话框
3. 配置人物动画
4. 实现简单的控制人物移动
新建脚本,简单的控制人物的移动和动画切换
public class Player : MonoBehaviour { [Header("移动速度")] public float speed; Animator animator; Vector3 movement; void Start() { animator = GetComponent<Animator>(); } void Update() { //移动 movement = new Vector3(Input.GetAxisRaw("Horizontal") * Time.deltaTime * speed, Input.GetAxisRaw("Vertical") * Time.deltaTime * speed, transform.position.z); transform.Translate(movement); //动画 if (movement != Vector3.zero) { animator.SetBool("run", true); }else{ animator.SetBool("run", false); } //翻面 if(movement.x>0){ transform.localScale = new Vector3(1, 1, 1); } if(movement.x<0){ transform.localScale = new Vector3(-1, 1, 1); } } }
效果
控制对话框的显示隐藏
新增脚本TalkButton,控制NPC对话提示和对话框的显示和隐藏
public class TalkButton : MonoBehaviour { private GameObject tipsButton;//对话提示按钮 [Header("对话框")] public GameObject dialogBox; private void OnTriggerEnter2D(Collider2D other) { tipsButton = other.transform.Find("对话提示").gameObject; tipsButton.SetActive(true); } private void OnTriggerExit2D(Collider2D other) { tipsButton.SetActive(false); dialogBox.SetActive(false); } private void Update() { if (tipsButton != null && tipsButton.activeSelf && Input.GetKeyDown(KeyCode.R)) { dialogBox.SetActive(true); } } }
效果
定义对话内容
新建DialogNode,定义每段对话的各种属性
// 代表了一个对话节点。 [Serializable] public class DialogNode { [Header("角色的名字")] public string name; [Header("角色的头像")] public Sprite sprite; [TextArea, Header("对话的内容")] public string content; }
新建Dialogue脚本,继承ScriptableObject,这样我们就可以在界面方便的新建各种对话了
// 表示一段对话 [CreateAssetMenu(menuName="创建对话" ,fileName = "对话")] public class Dialogue : ScriptableObject { // 对话节点 public DialogNode[] dialogNodes; }
回到界面,创建各种对话,并配置对话内容
实现简单的对话功能
定义NPC脚本
public class NPC : MonoBehaviour { [Header("对话内容")] public Dialogue dialogue; }
给不同NPC挂载不同的对话
修改TalkButton,获取对应的NPC对话内容,并修改为单例,方便其他地方调用dialogue对话内容
[NonSerialized] public Dialogue dialogue;//对话内容 //单例 public static TalkButton instance; private void Awake() { if(instance == null) { instance = this; }else{ if(instance != this){ Destroy(gameObject); } } DontDestroyOnLoad(gameObject); } private void OnTriggerEnter2D(Collider2D other) { dialogue = other.GetComponent<NPC>().dialogue; //。。。 }
新增DialogSystem脚本,挂载在对话框上
public class DialogSystem : MonoBehaviour { private Dialogue dialogue;//对话内容 //索引 private int index; //对话内容框 TextMeshProUGUI dialogueContent; //名称框 TextMeshProUGUI dialogueName; //头像框 Image dialogueImage; private void Awake() { gameObject.SetActive(false); } private void OnEnable() { dialogue = TalkButton.instance.dialogue; dialogueContent = transform.Find("内容").GetComponent<TextMeshProUGUI>(); dialogueName = transform.Find("名字").GetComponent<TextMeshProUGUI>(); dialogueImage = transform.Find("头像").GetComponent<Image>(); //设置人物头像保持宽高比,防止压缩变形 dialogueImage.preserveAspect = true; index = 0; Play(); } private void Update() { if (Input.GetKeyDown(KeyCode.R) && dialogue != null) { //对话播放完,关闭对话 if (index == dialogue.dialogNodes.Length) { gameObject.SetActive(false); index = 0; } else { //开始对话 Play(); } } } // Play 函数用于开始播放对话。 private void Play() { // 获取当前对话节点,并更新索引值。 DialogNode node = dialogue.dialogNodes[index++]; // 设置对话内容、角色名称和头像 dialogueContent.text = node.content; dialogueName.text = node.name; dialogueImage.sprite = node.sprite; } }
效果
逐字打印效果
修改DialogSystem,创建携程实现逐字打印效果,为了防止字体发生错乱我们要加判断,每一行执行完成后才可以继续进入下一段对话
[SerializeField, Header("目前逐字打印速度")] private float textSpeed; bool isDialogue;//是否正在对话 private void OnEnable() { isDialogue = false; // 。。。 } private void Update() { if (Input.GetKeyDown(KeyCode.R) && dialogue != null) { if (!isDialogue) { //对话播放完,关闭对话 if (index == dialogue.dialogNodes.Length) { gameObject.SetActive(false); index = 0; } else { //开始对话 Play(); } } } } // Play 函数用于开始播放对话。 private void Play() { // 获取当前对话节点,并更新索引值。 DialogNode node = dialogue.dialogNodes[index++]; // 设置对话内容、角色名称和头像 // dialogueContent.text = node.content; StartCoroutine(SetTextUI(node)); dialogueName.text = node.name; dialogueImage.sprite = node.sprite; } //逐字打印 IEnumerator SetTextUI(DialogNode node) { isDialogue = true; dialogueContent.text = ""; for (int i = 0; i < node.content.Length; i++) { dialogueContent.text += node.content[i]; yield return new WaitForSeconds(textSpeed); } isDialogue = false; }
效果,记得在面板配置textSpeed值,我这里定为0.1
按下按键快速显示文本
修改DialogSystem,我们通过控制文本播放速度实现
private float startTextSpeed;//开始逐字打印速度 private void OnEnable() { //... startTextSpeed = textSpeed; } private void Update() { if (Input.GetKeyDown(KeyCode.R) && dialogue != null) { //如果正在对话,再次按下R,快速显示所有对话 if (isDialogue) { textSpeed = 0; } else { //回复文本速度 textSpeed = startTextSpeed; //对话播放完,关闭对话 if (index == dialogue.dialogNodes.Length) { gameObject.SetActive(false); index = 0; } else { //开始对话 Play(); } } } }
效果
实现多个NPC配置不同对话
配置多个NPC,给每个NPC配置不同的对话
最终效果
扩展
麦扣的课程用的是TextAsset读取txt文本,这种方式因为不方便配置显示角色名称和头像表情变化,所有我放弃了,但是还是补充一下TextAsset的用法,因为他可能在其他地方可以应用
TextAsset 读取文档文件
TextAsset 是把一种某种格式的文件输入到我们的游戏项目当中,然后它可以帮助我们转换这里边的这个文本
它可以支持的类型有:
它里边也有一个自带的一个参数的方法,就是.text,它会把整个文件转换成一个单独的字符型的数据
实际应用
比如这样的文本
代码读取文本
public class DialogSystem : MonoBehaviour { [Header("文本文件")] public TextAsset textFile; // 用于存储对话文本的文本文件 public int index; // 对话索引,用于跟踪当前对话位置 List<string> textList = new List<string>(); // 存储从文本文件中读取的对话内容的列表 void Start() { GetTextFromFile(textFile); } void GetTextFromFile(TextAsset file) { var lineData = file.text.Split('\n'); // 将文本文件按行分割 foreach (var line in lineData) { textList.Add(line); // 将每行对话文本添加到对话内容列表中 } } }
修改字体样式
最近有小伙伴来找我,说实现修改某些字体颜色或者突出某些文本如何做?
其实也很简单,如下,这里就再补充一下。
这种语法叫做 Rich Text 标记,在 Unity 中可以用于富文本显示。除了 标记外,还有其他一些标记可以用于修改文本的样式、大小、字体等,比如:
<color=red>红色</color> <color=#FF0000>红色</color> <color=rgb(255,0,0)>红色</color> <b>加粗</b> <i>斜体</i> <size=30>大号</size>
代码控制
// 使用 Rich Text 标记修改文本内容 myText.text = "这是一段 <color=red>红色</color> 的文本,<b>加粗</b>并且变为 <size=30>大号</size> 字体。"; // 改变文本字体 Font myFont = Resources.GetBuiltinResource<Font>("Arial.ttf"); myText.font = myFont;
这里我们要修改某段对话的文字样式,就很简单了
补充
逐字打印的时候,还可以加入一些打字音效,这里我就不加了,留给大家自己补充
源码
https://gitcode.net/unity1/dialoguesystem