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

简介: 【Unity实战】实现强大通用易扩展的对话系统(附项目源码)(2023/12/26补充更新)

先看看实现的最终效果

前言

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

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

本文是参考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

参考

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

目录
相关文章
|
2月前
|
设计模式 存储 人工智能
深度解析Unity游戏开发:从零构建可扩展与可维护的游戏架构,让你的游戏项目在模块化设计、脚本对象运用及状态模式处理中焕发新生,实现高效迭代与团队协作的完美平衡之路
【9月更文挑战第1天】游戏开发中的架构设计是项目成功的关键。良好的架构能提升开发效率并确保项目的长期可维护性和可扩展性。在使用Unity引擎时,合理的架构尤为重要。本文探讨了如何在Unity中实现可扩展且易维护的游戏架构,包括模块化设计、使用脚本对象管理数据、应用设计模式(如状态模式)及采用MVC/MVVM架构模式。通过这些方法,可以显著提高开发效率和游戏质量。例如,模块化设计将游戏拆分为独立模块。
180 3
|
3月前
|
开发者 图形学 开发工具
Unity编辑器神级扩展攻略:从批量操作到定制Inspector界面,手把手教你编写高效开发工具,解锁编辑器隐藏潜能
【8月更文挑战第31天】Unity是一款强大的游戏开发引擎,支持多平台发布与高度可定制的编辑器环境。通过自定义编辑器工具,开发者能显著提升工作效率。本文介绍如何使用C#脚本扩展Unity编辑器功能,包括批量调整游戏对象位置、创建自定义Inspector界面及项目统计窗口等实用工具,并提供具体示例代码。理解并应用这些技巧,可大幅优化开发流程,提高生产力。
351 1
|
3月前
|
开发者 图形学 Java
揭秘Unity物理引擎核心技术:从刚体动力学到关节连接,全方位教你如何在虚拟世界中重现真实物理现象——含实战代码示例与详细解析
【8月更文挑战第31天】Unity物理引擎对于游戏开发至关重要,它能够模拟真实的物理效果,如刚体运动、碰撞检测及关节连接等。通过Rigidbody和Collider组件,开发者可以轻松实现物体间的互动与碰撞。本文通过具体代码示例介绍了如何使用Unity物理引擎实现物体运动、施加力、使用关节连接以及模拟弹簧效果等功能,帮助开发者提升游戏的真实感与沉浸感。
78 1
|
2月前
|
图形学 C++ C#
Unity插件开发全攻略:从零起步教你用C++扩展游戏功能,解锁Unity新玩法的详细步骤与实战技巧大公开
【8月更文挑战第31天】Unity 是一款功能强大的游戏开发引擎,支持多平台发布并拥有丰富的插件生态系统。本文介绍 Unity 插件开发基础,帮助读者从零开始编写自定义插件以扩展其功能。插件通常用 C++ 编写,通过 Mono C# 运行时调用,需在不同平台上编译。文中详细讲解了开发环境搭建、简单插件编写及在 Unity 中调用的方法,包括创建 C# 封装脚本和处理跨平台问题,助力开发者提升游戏开发效率。
200 0
|
2月前
|
图形学 开发者 UED
Unity游戏开发必备技巧:深度解析事件系统运用之道,从生命周期回调到自定义事件,打造高效逻辑与流畅交互的全方位指南
【8月更文挑战第31天】在游戏开发中,事件系统是连接游戏逻辑与用户交互的关键。Unity提供了多种机制处理事件,如MonoBehaviour生命周期回调、事件系统组件及自定义事件。本文介绍如何有效利用这些机制,包括创建自定义事件和使用Unity内置事件系统提升游戏体验。通过合理安排代码执行时机,如在Awake、Start等方法中初始化组件,以及使用委托和事件处理复杂逻辑,可以使游戏更加高效且逻辑清晰。掌握这些技巧有助于开发者更好地应对游戏开发挑战。
120 0
|
3月前
|
开发者 图形学 API
从零起步,深度揭秘:运用Unity引擎及网络编程技术,一步步搭建属于你的实时多人在线对战游戏平台——详尽指南与实战代码解析,带你轻松掌握网络化游戏开发的核心要领与最佳实践路径
【8月更文挑战第31天】构建实时多人对战平台是技术与创意的结合。本文使用成熟的Unity游戏开发引擎,从零开始指导读者搭建简单的实时对战平台。内容涵盖网络架构设计、Unity网络API应用及客户端与服务器通信。首先,创建新项目并选择适合多人游戏的模板,使用推荐的网络传输层。接着,定义基本玩法,如2D多人射击游戏,创建角色预制件并添加Rigidbody2D组件。然后,引入网络身份组件以同步对象状态。通过示例代码展示玩家控制逻辑,包括移动和发射子弹功能。最后,设置服务器端逻辑,处理客户端连接和断开。本文帮助读者掌握构建Unity多人对战平台的核心知识,为进一步开发打下基础。
120 0
|
3月前
|
开发者 图形学 C#
揭秘游戏沉浸感的秘密武器:深度解析Unity中的音频设计技巧,从背景音乐到动态音效,全面提升你的游戏氛围艺术——附实战代码示例与应用场景指导
【8月更文挑战第31天】音频设计在游戏开发中至关重要,不仅能增强沉浸感,还能传递信息,构建氛围。Unity作为跨平台游戏引擎,提供了丰富的音频处理功能,助力开发者轻松实现复杂音效。本文将探讨如何利用Unity的音频设计提升游戏氛围,并通过具体示例代码展示实现过程。例如,在恐怖游戏中,阴森的背景音乐和突然的脚步声能增加紧张感;在休闲游戏中,轻快的旋律则让玩家感到愉悦。
84 0
|
3月前
|
图形学 C# 开发者
Unity粒子系统全解析:从基础设置到高级编程技巧,教你轻松玩转绚丽多彩的视觉特效,打造震撼游戏画面的终极指南
【8月更文挑战第31天】粒子系统是Unity引擎的强大功能,可创建动态视觉效果,如火焰、爆炸等。本文介绍如何在Unity中使用粒子系统,并提供示例代码。首先创建粒子系统,然后调整Emission、Shape、Color over Lifetime等模块参数,实现所需效果。此外,还可通过C#脚本实现更复杂的粒子效果,增强游戏视觉冲击力和沉浸感。
177 0
|
3月前
|
开发者 图形学 前端开发
绝招放送:彻底解锁Unity UI系统奥秘,五大步骤教你如何缔造令人惊叹的沉浸式游戏体验,从Canvas到动画,一步一个脚印走向大师级UI设计
【8月更文挑战第31天】随着游戏开发技术的进步,UI成为提升游戏体验的关键。本文探讨如何利用Unity的UI系统创建美观且功能丰富的界面,包括Canvas、UI元素及Event System的使用,并通过具体示例代码展示按钮点击事件及淡入淡出动画的实现过程,助力开发者打造沉浸式的游戏体验。
95 0
|
3月前
|
图形学
Unity动画☀️Unity动画系统Bug集合
Unity动画☀️Unity动画系统Bug集合