【Unity实战】实现强大通用易扩展的对话系统(附项目源码)

先看看实现的最终效果

在这里插入图片描述

前言

之前的对话系统因为存在一些错误和原作者不允许我分享,所以被我下架了,而且之前对话系统确实少了一些功能,比如最基本的逐字打印功能,原本来是打算后面补充的。

对话系统在游戏中实现太常见了,所以我又重新去找了一些对话系统的课程进行学习,把实现过程和笔记分享出来,后面肯定会用到。

本文是参考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); // 将每行对话文本添加到对话内容列表中
        }
    }
}

补充

逐字打印的时候,还可以加入一些打字音效,这里我就不加了,留给大家自己补充

源码

整理好后我会放上了

参考

【视频】https://www.bilibili.com/video/BV1WJ411Y71J/

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,以便我第一时间收到反馈,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,出于兴趣爱好,于是最近才开始自习unity。如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我可能也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/124519.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于 Python 的课程助教智能聊天机器人

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长 Wechat / QQ 名片 :) 1. 项目简介 课程助教是高校中一种常见的教学模式,其在学生理论知识的掌握与实践能力的提高方面起到关键性的作用,已经成为高校日常教育环节中不可或缺的一环。然而,传统的人力助教有若干关键问题亟待…

Leangoo领歌免费Scrum管理工具中如何看到关于自己的所有任务?

个人工作台 个人工作台是个人最新待办工作的展示区域&#xff0c;它展示了个人所有的待办任务&#xff0c;最新访问的项目和工作动态&#xff0c;当一个人在多个项目和看板上工作时&#xff0c;它可以帮助个人快速看到个人在各个项目的工作&#xff0c;快速进入任务看板处理任…

Flink集群的搭建

1、Flink独立集群模式 1、首先Flink的独立集群模式是不依赖于Hadoop集群。 2、上传压缩包&#xff0c;配置环境&#xff1a; 1、解压&#xff1a; tar -zxvf flink-1.15.2-bin-scala_2.12.tgz2、配置环境变量&#xff1a;vim /etc/profileexport FLINK_HOME/usr/local/soft/fl…

3、FFmpeg基础

1、FFmpeg 介绍 FFmpeg是一套可以用来记录、转换数字音频、视频&#xff0c;并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库。 2、FFmpeg 组成 - libavformat&#xff1a;用于…

【理解链表指针赋值】链表中cur->next = cur->next->next->next与cur =cur->next->next的区别

最近在做链表的题目的时候&#xff0c;对于所定义的cur链表指针产生了一些疑惑&#xff0c;查阅资料后整理一下我的理解&#xff1a; /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode(int x) : val(x), next(n…

AI创作系统ChatGPT商业运营系统源码+支持GPT4/支持ai绘画

一、AI创作系统 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如…

工程(十二)Ubuntu20.04LSD_SLAM运行

博主创建了一个科研互助群Q&#xff1a;772356582&#xff0c;欢迎大家加入讨论。这是一个科研互助群&#xff0c;主要围绕机器人&#xff0c;无人驾驶&#xff0c;无人机方面的感知定位&#xff0c;决策规划&#xff0c;以及论文发表经验&#xff0c;以方便大家很好很快的科研…

CSS 显示、定位、布局、浮动

一、CSS 显示&#xff1a; CSS display属性设置元素应如何显示&#xff1b;CSS visibility属性指定元素应可见还是隐藏。隐藏元素可以通过display属性设置为“none”&#xff0c;也可以通过visibility属性设置为“hidden”。两者的区别&#xff1a;visibility:hidden可以隐藏某…

王道p40 1.设计一个递归算法,递归删除单链表L中所有值为x的结点(c语言)

视频讲解在这里&#xff1a;&#x1f447; p40 第1题 王道数据结构课后代码题c语言代码实现_哔哩哔哩_bilibili 本题代码如下 void delete(linklist* L,int x)//递归删除x {if((*L)->next! NULL){if ((*L)->next->data x)//找到x{lnode* p (*L)->next;(*L)-&…

【Springboot】Vue3-Springboot引入JWT实现登录校验以及常见的错误解决方案

文章目录 前言一、JWT简单介绍二、token校验设计思路三、使用步骤Springboot部署JWT引入依赖&#xff1a;创建登录实体类后端&#xff1a;LoginController.java路由守卫函数 四、问题 前言 项目版本&#xff1a; 后端&#xff1a; Springboot 2.7、 Mybatis-plus、Maven 3.8.1…

网络测试工具—— iperf2 安卓APK 下载 及简单使用

网络测试工具—— iperf2 安卓APK 下载 及简单使用 前言一、iperf2是什么&#xff1f;二、使用步骤附上help中命令截图翻译总结 前言 项目上有一款安卓车机加载局域网图片加载非常慢&#xff0c;所以需要测试一个安卓车机设备的带宽&#xff0c;经过调研后使用到了iperf2。 一…

项目管理之如何出道(中)

昨日立冬&#xff0c;各位盆友&#xff0c;有没有吃饺子&#xff1f; 冬天来了&#xff0c;寒冷未约而至&#xff0c;冬雪侵袭北国。未知的变化总能让人产生恐慌和无措&#xff0c;就像行走在荒岛小路&#xff0c;前面遇到的究竟是迷人之景&#xff1f;还是饿狼之瞟&#xff1f…

52基于MATLAB的希尔伯特Hilbert变换求包络谱

基于MATLAB的希尔伯特Hilbert变换求包络谱&#xff0c;对原始信号进行初步滤波&#xff0c;之后进行包络谱分析。可替换自己的数据进行优化。程序已调通&#xff0c;可直接运行。 52的尔伯特Hilbert变换包络谱 (xiaohongshu.com)

计算机三级四级嵌入式备战经验

2023年9月23日于东北大学考完三四级 大四的时候时间比较多&#xff0c;因为本科学了一点嵌入式的知识&#xff0c;研究生又用不到&#xff0c;所以想着考个证金盆洗手。 三级考的是一本书&#xff0c;更多涉及到S3C2410这个芯片&#xff1b;四级考的是两本书&#xff1a;《操作…

故障注入测试目的及方法

在软件开发的复杂环境中&#xff0c;保证应用程序的鲁棒性和稳定性是至关重要的。故障注入测试是一种专门设计用于模拟和评估系统对故障的响应能力的测试方法。通过主动引入故障并观察系统的行为&#xff0c;开发者可以更全面地了解系统在面临异常情况时的表现。 一、故障注入测…

人工智能入门:什么是“具身智能“?

具身智能&#xff08;Embodied Intelligence&#xff09;是一种智能系统的设计理念&#xff0c;其目标是通过将感知、决策和行动融合在一起&#xff0c;使机器能够像人类一样具备身体和运动能力。具身智能的核心理念是利用机器的身体结构和动作能力来增强其智能表现和解决复杂任…

git解决冲突的方法。

1、 cherry-pick git fetch ssh://jingyou.caigerrit.transtekcorp.com:29418/leshan refs/changes/23/34123/3 && git cherry-pick FETCH_HEAD2、 文件解冲突&#xff01; 3、 cherry-pick完整。 git cherry-pick --continue4、查看状态。 5、 push。 git push o…

混合云中 DevOps 的最佳实践

近年来&#xff0c;出现了各种工具、技术和框架&#xff0c;其目标是增强灵活性、性能和可扩展性。传统的整体方法已被微服务和纳米服务等更加模块化的方法所取代。此外&#xff0c;云计算的兴起导致本地软件被云环境所取代&#xff0c;云环境提供了以前无法提供的广泛优势和功…

Ubuntu中安装rabbitMQ

一、安装 RabbitMQ ①&#xff1a;更新源 sudo apt-get update②&#xff1a;安装Rrlang语言 由于RabbitMq需要erlang语言的支持&#xff0c;在安装RabbitMq之前需要安装erlang sudo apt-get install erlang-nox③&#xff1a;安装rabbitMQ sudo apt-get install rabbitmq-s…

彻底改变您的用户体验设计:您需要了解的 5 个工具包和指南

问题 进行设计冲刺、设计思维工作坊期间&#xff0c;如何找到好用的UX工具&#xff1f; 市面上有很多优秀的UX书籍&#xff0c;但也有越来越多的在线 用户体验设计 工具包和方法指南详细介绍了大量的UX工具和方法&#xff0c;包括这些方法是什么、为什么要用、何时用还有怎么…