当前位置: 首页 > news >正文

【Unity】2D 对话模块的实现

对话模块主要参考 【Unity教程】剧情对话系统 实现。

在这次模块的构建将基于 unity ui 组件 和 C#代码实现一个从excel 文件中按照相应规则读取数据并展示的逻辑。这套代码不仅能实现正常的对话,也实现了对话中可以通过选择不同选项达到不同效果的分支对话功能。

整一套代码分为分为两部分,一部分和库存模块一样通过 Collider 2D 和 Unity Event 构建了一个范围内可互动的功能,这一部分可以参考之前的库存模块。

剩下一部分就是对话框模块整体逻辑,先看一下效果:

在这里插入图片描述

在这里插入图片描述

从上图中,可以看出整个对话框可以分为五个部分:头像、说话人名字、普通对话内容、跳转到下一句的按钮、和 选择对话框。可以简单将 普通对话内容和跳转按钮 划分成一个逻辑组件,而选择对话框划分成另一个逻辑组件,这样划分的原因在于两者实现方式不一样。

接下来内容将分为三个段落,我将自下而上一一实现:从Excel中读取对话内容的方式、普通对话实现 和 选择对话 这三个步骤:

从Excel中读取对话内容的方式

整一个Excel的内容分为了五个部分,如下图所述,分别是、

对话的id,用来快速定位到一个对话类,可以方便我们进行查找和使用

说话人的名字,用来展示在 对话框中的UI组件

说话人的头像,也是用来展示在对话框中的 UI 和 名字不同的是他是代表一个文件的路径

对话内容,用来展示在 对话框中的UI组件

下一句对话id,用来做跳转使用

在这里插入图片描述

定义好具体数据的Excel之后,需要将Excel导出成Unity可以识别的编码格式,否则在Unity中会被识别成乱码(这一步可以通过 txt 文本另存为的方式进行变更)。将另存为的文本保存在unity 项目中的 Assets/Resources/Dialogue/路径下,以便项目能够读取到。

在这里插入图片描述

接下来,在C#中定义一个对应的class,用来接住Excel读取出的数据,并在C#中通过Resources.Load的方法来读取。Resoruces.Load的方法会将整个文本以 string的方式进行读取,所以还需要对每一行文本进行拆分,才能处理成我们需要的对话类。最终,将 对话 id 和 对话实体保存到一个 dictionary里,方便后面的步骤进行调用。

// 对话类class Dialogue{public int dialogueId;public string characterName;public string characterIcon;public string dialogue;public List<int> toDialogueIdList;public Dialogue(int dialogueId, string characterName, string characterIcon, string dialogue, List<int> toDialogueIdList){this.dialogueId = dialogueId;this.characterName = characterName;this.characterIcon = characterIcon;this.dialogue = dialogue;this.toDialogueIdList = toDialogueIdList;}}//public void eventDialogueFileProcess(int eventId){   // 文件处理成 对话 id 和 对话对象 的映射dialogueDictionary.Clear();string filePath = dialogueFilePrefix + eventId;TextAsset textFile = Resources.Load<TextAsset>(filePath);processDialogueFile(textFile);    }private void processDialogueFile(TextAsset dialogueFile){string[] dialogueArrays = dialogueFile.text.Split("\r\n");foreach(string strDialogue in dialogueArrays){if(string.IsNullOrEmpty(strDialogue)){continue;}string[] cols = strDialogue.Split(',');string[] strToDialogueIds = cols[4].Split('|');List<int> toDialogueIdList = new List<int>();foreach(string strToDialogueId in strToDialogueIds){   if(string.IsNullOrEmpty(strToDialogueId)){break;}Debug.Log(strToDialogueId);toDialogueIdList.Add(int.Parse(strToDialogueId));}Dialogue dialogue = new Dialogue(int.Parse(cols[0]), cols[1], cols[2], cols[3], toDialogueIdList);dialogueDictionary.Add(dialogue.dialogueId, dialogue);}}

普通对话实现

普通对话实现的主要方式就是将当前对话的 id 保存为一个成员对象,这样在触发按钮的点击事件之后,便能通过事件监听的方式调用这个对话id来获取下一段对话。

// 正常对话int nextDialogueId = dialogueIdList[0];if(dialogueDictionary.TryGetValue(nextDialogueId, out Dialogue dialogue)){// 隐藏多选dialogueMutliBG.enabled = false;// 显示正常对话dialogueText.enabled = true;nextButton.gameObject.SetActive(true);characterIcon.sprite = Resources.Load<Sprite>(characterIconPrefix + dialogue.characterIcon);characterName.text = dialogue.characterName;dialogueText.text = dialogue.dialogue;currentDialogueIndex = dialogue.dialogueId; }

选择对话

选择对话相比普通对话来说,实现有一些复杂,主要在于需要用C# 的 delegate 代理一个函数,来达成下一步对话的操作。这样做的原因在于选择对话每一个对话都会有一个下一个对话的选项,导致没有办法直接使用普通对话定义的当前对话id变量。

// 可选对话选择List<Dialogue> dialogues = new List<Dialogue>();foreach(int index in dialogueIdList){if(dialogueDictionary.TryGetValue(index, out Dialogue dialogue)){dialogues.Add(dialogue);}}// 隐藏正常对话dialogueText.enabled = false;nextButton.gameObject.SetActive(false);// 显示多选dialogueMutliBG.enabled = true;foreach(Dialogue dialogue in dialogues){// 依次生成对话 ButtonoptionButton = Instantiate(optionButton, dialogueMutliBG.transform);TextMeshProUGUI textMeshPro = optionButton.GetComponentInChildren<TextMeshProUGUI>();textMeshPro.text = dialogue.dialogue;// 添加事件监听optionButton.GetComponent<Button>().onClick.AddListener(delegate {// 点完以后 销毁选择对话框Button[] optionalButtons = dialogueMutliBG.GetComponentsInChildren<Button>();foreach(Button button in optionalButtons){Destroy(button.gameObject);}// 显示下一句对话showDialogue(dialogue.toDialogueIdList);});}

将这两部分合入一个函数中,通过下一段对话id 的数量进行判断到底是 普通对话还是选择对话,合并后的代码如下:


using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;public class DialogueControl : MonoBehaviour
{class Dialogue{public int dialogueId;public string characterName;public string characterIcon;public string dialogue;public List<int> toDialogueIdList;public Dialogue(int dialogueId, string characterName, string characterIcon, string dialogue, List<int> toDialogueIdList){this.dialogueId = dialogueId;this.characterName = characterName;this.characterIcon = characterIcon;this.dialogue = dialogue;this.toDialogueIdList = toDialogueIdList;}}public Image characterIcon;public TextMeshProUGUI characterName;public TextMeshProUGUI dialogueText;private static string dialogueFilePrefix = "Dialogue/event_dialogue_";private static string characterIconPrefix = "Character/";private Dictionary<int, Dialogue> dialogueDictionary = new Dictionary<int, Dialogue>();private int currentDialogueIndex = 1;public Image dialogueMutliBG;public Button optionButton;public Button nextButton;public int lastEvent;// Start is called before the first frame updatevoid Start(){transform.gameObject.SetActive(false);}// Update is called once per framevoid Update(){}public void eventDialogueFileProcess(int eventId){   transform.gameObject.SetActive(true);// 文件处理成 对话 id 和 对话对象 的映射if(!lastEvent.Equals(eventId)){dialogueDictionary.Clear();string filePath = dialogueFilePrefix + eventId;TextAsset textFile = Resources.Load<TextAsset>(filePath);processDialogueFile(textFile);}// 显示第一段对话List<int> dialogueIdList = new List<int>(1);dialogueIdList.Add(1);showDialogue(dialogueIdList);lastEvent = eventId;}private void processDialogueFile(TextAsset dialogueFile){string[] dialogueArrays = dialogueFile.text.Split("\r\n");foreach(string strDialogue in dialogueArrays){if(string.IsNullOrEmpty(strDialogue)){continue;}string[] cols = strDialogue.Split(',');string[] strToDialogueIds = cols[4].Split('|');List<int> toDialogueIdList = new List<int>();foreach(string strToDialogueId in strToDialogueIds){   if(string.IsNullOrEmpty(strToDialogueId)){break;}Debug.Log(strToDialogueId);toDialogueIdList.Add(int.Parse(strToDialogueId));}Dialogue dialogue = new Dialogue(int.Parse(cols[0]), cols[1], cols[2], cols[3], toDialogueIdList);dialogueDictionary.Add(dialogue.dialogueId, dialogue);}}private void showDialogue(List<int> dialogueIdList){if(dialogueIdList.Count == 0){return;}if(dialogueIdList.Count > 1){// 可选对话选择List<Dialogue> dialogues = new List<Dialogue>();foreach(int index in dialogueIdList){if(dialogueDictionary.TryGetValue(index, out Dialogue dialogue)){dialogues.Add(dialogue);}}// 隐藏正常对话dialogueText.enabled = false;nextButton.gameObject.SetActive(false);// 显示多选dialogueMutliBG.enabled = true;foreach(Dialogue dialogue in dialogues){// 依次生成对话 ButtonoptionButton = Instantiate(optionButton, dialogueMutliBG.transform);TextMeshProUGUI textMeshPro = optionButton.GetComponentInChildren<TextMeshProUGUI>();textMeshPro.text = dialogue.dialogue;// 添加事件监听optionButton.GetComponent<Button>().onClick.AddListener(delegate {// 点完以后 销毁选择对话框Button[] optionalButtons = dialogueMutliBG.GetComponentsInChildren<Button>();foreach(Button button in optionalButtons){Destroy(button.gameObject);}// 显示下一句对话showDialogue(dialogue.toDialogueIdList);});}}else {// 正常对话int nextDialogueId = dialogueIdList[0];if(dialogueDictionary.TryGetValue(nextDialogueId, out Dialogue dialogue)){// 隐藏多选dialogueMutliBG.enabled = false;// 显示正常对话dialogueText.enabled = true;nextButton.gameObject.SetActive(true);characterIcon.sprite = Resources.Load<Sprite>(characterIconPrefix + dialogue.characterIcon);characterName.text = dialogue.characterName;dialogueText.text = dialogue.dialogue;currentDialogueIndex = dialogue.dialogueId;}}}public void nextDialogue(){if(dialogueDictionary.TryGetValue(currentDialogueIndex, out Dialogue dialogue)){if(dialogue.toDialogueIdList.Count == 0){transform.gameObject.SetActive(false);nextButton.gameObject.SetActive(false);}showDialogue(dialogue.toDialogueIdList);}}}

来看一下最终效果

请添加图片描述

相关文章:

【Unity】2D 对话模块的实现

对话模块主要参考 【Unity教程】剧情对话系统 实现。 在这次模块的构建将基于 unity ui 组件 和 C#代码实现一个从excel 文件中按照相应规则读取数据并展示的逻辑。这套代码不仅能实现正常的对话&#xff0c;也实现了对话中可以通过选择不同选项达到不同效果的分支对话功能。 …...

laravel安装初步使用学习 composer安装

一、什么是laravel框架 Laravel框架可以开发各种不同类型的项目&#xff0c;内容管理系统&#xff08;Content Management System&#xff0c;CMS&#xff09;是一种比较典型的项目&#xff0c;常见的网站类型&#xff08;如门户、新闻、博客、文章等&#xff09;都可以利用CM…...

【VS插件】VS code上的Remote - SSH

【VS插件】VS code上的Remote - SSH 目录 【VS插件】VS code上的Remote - SSH获得Linux服务器或者Linux系统的IP地址下载插件远程登录注意如果Linux虚拟机系统无法连接成功可能是没有开启ssh服务优势 作者&#xff1a;爱写代码的刚子 时间&#xff1a;2023.9.12 前言&#xff1…...

TensorFlow 02(张量)

一、张量 张量Tensor 张量是一个多维数组。与NumPy ndarray对象类似&#xff0c;tf.Tensor对象也具有数据类型和形状。如下图所示: 此外&#xff0c;tf.Tensors可以保留在GPU中。TensorFlow提供了丰富的操作库 (tf.add&#xff0c;tf.matmul,tf.linalg.inv等)&#xff0c;它们…...

513. 找树左下角的值

代码链接&#xff1a; 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 思路&#xff1a; 万金油层次遍历&#xff0c;保存每一层的第一个元素返回就行了 我的代码&#xff1a; /*** Definition for a binary tree node.* struct TreeNode {* …...

量化:基于支持向量机的择时策略

文章目录 参考机器学习简介策略简介SVM简介整体流程收集数据准备数据建立模型训练模型测试模型调节参数 参考 Python机器学习算法与量化交易 利用机器学习模型&#xff0c;构建量化择时策略 机器学习简介 机器学习理论主要是设计和分析一些让计算机可以自动“学习”的算法。…...

成功解决Selenium 中116版本的chromedriver找不到问题

Selenium 中的Google&#xff08;谷歌浏览器&#xff09;最新版本chromedriver 文章目录 Selenium 中的Google&#xff08;谷歌浏览器&#xff09;最新版本chromedriver1.当前作者的谷歌浏览器版本2.当前驱动官网的最新版本3.当不想降低浏览器版本继续使用谷歌浏览器的办法 1.当…...

PYQT常用组件--方法汇总

QTimeEdit timeEdit是Qt框架中的一个时间编辑器控件&#xff0c;它提供了以下常用方法&#xff1a; setTime(QTime time): 设置时间编辑器的时间为指定的QTime对象。time(): 返回时间编辑器的当前时间&#xff0c;返回一个QTime对象。setDateTime(QDateTime dateTime): 设置时…...

Linux系统编程(一):文件 I/O

参考引用 UNIX 环境高级编程 (第3版)黑马程序员-Linux 系统编程 1. UNIX 基础知识 1.1 UNIX 体系结构&#xff08;下图所示&#xff09; 从严格意义上说&#xff0c;可将操作系统定义为一种软件&#xff0c;它控制计算机硬件资源&#xff0c;提供程序运行环境&#xff0c;通常…...

OSM+three.js打造3D城市

对于我在 Howest 的研究项目,我决定构建一个 3D 版本的 Lucas Bebber 的“交互式讲故事的动画地图路径”项目。我将使用 OSM 中的矢量轮廓来挤出建筑物的形状并将它们添加到 3js 场景中,随后我将对其进行动画处理。 一、开发环境 为了使用 Node 和 npm 包,我选择使用 Vite…...

02JVM_垃圾回收GC

二、垃圾回收GC 在堆里面存放着java的所有对象实例&#xff0c;当对象为“死去”&#xff0c;也就是不再使用的对象&#xff0c;就会进行垃圾回收GC 1.如何判断对象可以回收 1.1引用计数器 介绍 在对象中添加一个引用计数器&#xff0c;当一个对象被其他变量引用时这个对象…...

ARM Linux DIY(八)USB 调试

前言 V3s 带有一个 USB 接口&#xff0c;将其设置为 HOST 或 OTG 模式&#xff0c;这样可以用来接入键盘、鼠标等 USB 外设。 USB 简介 USB 有两种设备&#xff1a;HOST 和 USB 功能设备。 在 USB2.0 中又引入了一个新的概念 OTG&#xff0c;即设备角色可以动态切换。 切换方…...

编程小白的自学笔记十四(python办公自动化创建、复制、移动文件和文件夹)

系列文章目录 编程小白的自学笔记十三&#xff08;python办公自动化读写文件&#xff09; 编程小白的自学笔记十二&#xff08;python爬虫入门四Selenium的使用实例二&#xff09; 编程小白的自学笔记十一&#xff08;python爬虫入门三Selenium的使用实例详解&#xff09; …...

MySQL使用Xtrabackup备份到AWS存储桶

1.安装Xtrabackup cd /tmp wget https://downloads.percona.com/downloads/Percona-XtraBackup-8.0/Percona-XtraBackup-8.0.33-28/binary/redhat/7/x86_64/percona-xtrabackup-80-8.0.33-28.1.el7.x86_64.rpm yum -y localinstall percona-xtrabackup-80-8.0.33-28.1.el7.x86…...

(高阶)Redis 7 第11讲 BIGKEY 优化篇

面试题 问题答案如何在海量数据中查询某一固定前缀的Keyscan生产环境如何限制 keys */FLUSHDB/FLUSHALL 等危险命令,防止误删误用# 修改配置文件 rename-command keys "" rename-command flushdb "" rename-command flushall ""如何使用MEMORY U…...

一阶差分和二阶差分概念及其举例

一阶差分和二阶差分概念及其举例 目录 一阶差分和二阶差分概念及其举例1、一阶差分1.1 概念1.2 举例 2、二阶差分2.1 概念2.2 举例 1、一阶差分 1.1 概念 一阶差分是指对一个数列中的每个元素&#xff0c;计算其与其前一个元素之差的操作。 1.2 举例 举例来说&#xff0c;对…...

使用自定义注解和SpringAOP捕获Service层异常,并处理自定义异常

目录 一 自定义异常二 自定义注解三 注解切面处理类四 使用 一 自定义异常 /*** 自定义参数为null异常*/ public class NoParamsException extends Exception {//用详细信息指定一个异常public NoParamsException(String message){super(message);}//用指定的详细信息和原因构…...

Kotlin(六) 类

目录 创建类 调用类 类的继承------open 构造函数 创建类 创建类和创建java文件一样&#xff0c;选择需要创建的目录New→Kotlin File/Class Kotlin中也是使用class关键字来声明一个类的&#xff0c;这一点和Java一致。现在我们可以在这个类中加入字段和函数来丰富它的功…...

蓝桥杯官网练习题(灌溉)

题目描述 小蓝负责花园的灌溉工作。 花园可以看成一个 n 行 m 列的方格图形。中间有一部分位置上安装有出水管。 小蓝可以控制一个按钮同时打开所有的出水管&#xff0c;打开时&#xff0c;有出水管的位置可以被认为已经灌溉好。 每经过一分钟&#xff0c;水就会向四面扩展…...

数据结构:树的概念和结构

文章目录 1. 树的概念2. 树的结构3. 树的相关概念4. 树的表示孩子表示法双亲表示法孩子兄弟表示法 5. 树在实际中的应用5. 树在实际中的应用 1. 树的概念 树是一种非线性的数据结构,它是由 n (n > 0)个有限结点组成一个具有层次关系的. 把它叫做树是因为它看起来像一棵倒挂的…...

大数据学习栈记——Neo4j的安装与使用

本文介绍图数据库Neofj的安装与使用&#xff0c;操作系统&#xff1a;Ubuntu24.04&#xff0c;Neofj版本&#xff1a;2025.04.0。 Apt安装 Neofj可以进行官网安装&#xff1a;Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

ssc377d修改flash分区大小

1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...

linux arm系统烧录

1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 &#xff08;忘了有没有这步了 估计有&#xff09; 刷机程序 和 镜像 就不提供了。要刷的时…...

对WWDC 2025 Keynote 内容的预测

借助我们以往对苹果公司发展路径的深入研究经验&#xff0c;以及大语言模型的分析能力&#xff0c;我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际&#xff0c;我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测&#xff0c;聊作存档。等到明…...

【论文笔记】若干矿井粉尘检测算法概述

总的来说&#xff0c;传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度&#xff0c;通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...

Reasoning over Uncertain Text by Generative Large Language Models

https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化

缓存架构 代码结构 代码详情 功能点&#xff1a; 多级缓存&#xff0c;先查本地缓存&#xff0c;再查Redis&#xff0c;最后才查数据库热点数据重建逻辑使用分布式锁&#xff0c;二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...

Leetcode33( 搜索旋转排序数组)

题目表述 整数数组 nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转&#xff0c;使数组变为 [nums[k], nums[k1], …, nums[n-1], nums[0], nu…...

在RK3588上搭建ROS1环境:创建节点与数据可视化实战指南

在RK3588上搭建ROS1环境:创建节点与数据可视化实战指南 背景介绍完整操作步骤1. 创建Docker容器环境2. 验证GUI显示功能3. 安装ROS Noetic4. 配置环境变量5. 创建ROS节点(小球运动模拟)6. 配置RVIZ默认视图7. 创建启动脚本8. 运行可视化系统效果展示与交互技术解析ROS节点通…...