Unity 实现一个简易可拓展性的对话系统

本人能力有限,一切实现仅供参考,如有不足还请斧正

起因是我看到学校社团内有人做了对话系统的分享,我想了想之前没写过这种东西,而Fungus插件教程太老了,NodeCanvas插件学习成本又比较高,我就干脆寻找资料 加上自己迭代一下,花了一天时间完成了这个对话系统

目录

1.介绍

2.核心脚本

对话管理器

对话事件

对话配置脚本

对话节点脚本

3.使用指北

路径配置

关于特性

关于接口

关于UI

其余内容请自行查看源码


Github: Haki-sheep/Haki-sheep-UnityTools: 这里是咩咩所有的工具

演示视频:

Unity一个简易可拓展的对话系统

1.介绍

这个对话系统并不是可视化编辑节点(像是NodeCanvas插件那种),但也支持 一键将Excel表转为So文件,通过配表的方式轻量化这一过程

首先,算上DEMO一共632行,去掉以后可能 不到四百行 所以十分轻巧

但是由于代码量摆在那,所以目前本对话系统只支持小玩具, 今后我说不定会将其拓展为课编辑节点的系统,当然,目前我个人使用起来还是比较方便的,毕竟是自己编写的系统

其次 Base只涉及到了Odin插件EPPLUS 以及一个单例基类 无需其他支持

2.核心脚本

对话管理器

对话流程如下:

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.UI;


public class DialogManager : SingltonMono<DialogManager>
{
    #region 基础配置
    //配置相关
    private DialogConfig curDialogConfig;
    private int nodeIndex;
    public bool nodeNotOver => nodeIndex < curDialogConfig.nodeList.Count - 1;

    //角色相关
    private string characterName;
    private Sprite characterAvatar;

    //外部 可做替换
    public Player player;
    public DialogMainUI dialogMainUI;
    public SelectUI selectUI;
    #endregion
    #region 对话流程 
    /// <summary>
    /// 开始对话
    /// </summary>
    /// <param name="dialogConfig">想要对话角色的配置</param>
    /// <param name="nodeIndex">从第几个节点开始对话</param>
    public void StartDialog(DialogConfig dialogConfig, int nodeIndex = 0)
    {

        if (curDialogConfig == dialogConfig) return;//不要重复对话

        curDialogConfig = dialogConfig;
        this.nodeIndex = nodeIndex;
        characterName = curDialogConfig.characterName;
        characterAvatar = curDialogConfig.characterAvatar;
        StartCoroutine(PlayNode(curDialogConfig.nodeList[nodeIndex]));

    }

    public void CeckCharacterInfo(DialogNode node, Image ui_characterAvator, Text ui_characterName)
    {
        //角色的信息
        if (node.player)
        {
            ui_characterName.text = player.name;
            ui_characterAvator.sprite = player.Avator;
        }
        else
        {
            ui_characterName.text = characterName;
            ui_characterAvator.sprite = characterAvatar;
        }

    }
    private IEnumerator PlayNode(DialogNode node)
    {
        dialogMainUI.Show();
        CeckCharacterInfo(node, dialogMainUI.ui_characterAvator, dialogMainUI.ui_characterName);

        //开始事件
        OnEvent(node.onStartEventList);
        yield return OnBlockEvent(node.onStartEventList);

        //打字机
        yield return Typing(node.content, dialogMainUI.ui_contentText);

        //等待交互
        while (!Input.GetMouseButtonDown(0)) { yield return null; }

        //结束事件
        OnEvent(node.onEndEventList);
        yield return OnBlockEvent(node.onEndEventList);

        if (nodeNotOver)
        {
            nodeIndex++;
            StartCoroutine(PlayNode(curDialogConfig.nodeList[nodeIndex]));
        }
        else
        {
            CloseDialog();
        }
    }
    private void OnEvent(List<IDialogEvent> dialogEvents)
    {
        foreach (IDialogEvent sEvent in dialogEvents)
        {
            sEvent.Execute();
        }
    }
    private IEnumerator OnBlockEvent(List<IDialogEvent> dialogEvents)
    {
        foreach (IDialogEvent sBEvnt in dialogEvents)
        {
            IEnumerator enumerator = sBEvnt.ExecuteBlock();
            if (enumerator == null) continue;
            yield return enumerator;
        }
    }
    public void CloseDialog()
    {
        StopAllCoroutines();
        curDialogConfig = null;
        nodeIndex = 0;
        dialogMainUI.Hide();
    }
    #endregion
    #region 打字机相关 
    public float delayBetweenContent = 0.1f;
    private Dictionary<string, string> keywordDic = new Dictionary<string, string>();
    public void SetKeyword(string key, string value)
    {
        keywordDic[key] = value;
    }
    public void RemoveKeyword(string key)
    {
        keywordDic.Remove(key);
    }
    private IEnumerator Typing(string content, Text ui_contentText)
    {
        StringBuilder builder = new StringBuilder();

        foreach (var item in keywordDic)
        {
            content = content.Replace(item.Key, item.Value);
        }

        foreach (var s in content)
        {
            builder.Append(s);
            ui_contentText.text = builder.ToString();
            yield return new WaitForSeconds(delayBetweenContent);
        }
    }


    #endregion

    #region 资源管理
    public T GetDialogConfig<T>(string path) where T : ScriptableObject, new()
    {
        return Resources.Load<T>(path);
    }
    #endregion

}

对话事件

这里我就展示其中一种事件,检查某一样子东西玩家是否已经应有 从而跳过对话

cs 复制代码
using System.Collections;
[DialogEvent("CheckKeyWordEvent")]
public class CheckKeyWordEvent : IDialogEvent
{
    public void ConverString(string excelString)
    {

    }

    public void Execute()
    {
        //检查是否有选中物品 TODO:条件可以替换
        if (Player.Instance.selectItem != null)
        {
            DialogManager.Instance.CloseDialog();
        }
    }

    public IEnumerator ExecuteBlock()
    {
        return null;
    }
}

对话配置脚本

cs 复制代码
using Sirenix.OdinInspector;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(menuName = "Tools", fileName = "创建新角色")]
public class DialogConfig : SerializedScriptableObject
{
    //角色名称
    public string characterName;
    //角色头像
    public Sprite characterAvatar;
    //显示索引,开启翻页
    [ListDrawerSettings(ShowIndexLabels = true, ShowPaging = true)]
    public List<DialogNode> nodeList = new List<DialogNode>();
}

对话节点脚本

cs 复制代码
using System.Collections.Generic;
/// <summary>
/// 对话节点配置
/// </summary>
public class DialogNode
{
    //是否是玩家
    public bool player;
    //说的内容
    public string content;
    //对话事件
    public List<IDialogEvent> onStartEventList = new List<IDialogEvent>();
    public List<IDialogEvent> onEndEventList = new List<IDialogEvent>();

}

3.使用指北

路径配置

这个文件填写你的Excel表和so文件想在的位置

但是我推荐将so文件放在Res下面 方便管理器读取

如果有报错就把你的DialogImprotSetting的路径放在这里面

关于特性

这个特性内填写你的事件名称即可 可以不和脚本一样 只需要和Excel表之中一样便可以读取

关于接口

事件需要继承这个接口

阻塞执行 里面直接return nul即可 因为外部会判断

当然你直接yield rerun null也可以,但是会造成延迟一帧后才执行其他语句

UI接口的话可以选择性继承,因为里面也没什么方法,可以自己写

关于UI

在DialogManager里有两个UI的对象,其实所有在外部这个注释下的字段都可以自行做替换

只要让Manager得到了你UI身上下面这些信息即可(方式自行选择比如事件中心或者订阅回调的方式)

剩下的UI样式之类的自行配置即可

其余内容请自行查看源码

相关推荐
两水先木示3 小时前
【Unity3D】微信小游戏适配安全区域或胶囊控件(圆圈按钮)水平高度一致方案
unity·微信小游戏·安全区域·ui适配·胶囊控件·safearea
枯萎穿心攻击3 小时前
ECS由浅入深第三节:进阶?System 的行为与复杂交互模式
开发语言·unity·c#·游戏引擎
小码编匠4 小时前
WPF 自定义TextBox带水印控件,可设置圆角
后端·c#·.net
水果里面有苹果4 小时前
17-C#的socket通信TCP-1
开发语言·tcp/ip·c#
不绝1914 小时前
怪物机制分析(有限状态机、编辑器可视化、巡逻机制)
网络·游戏·unity·游戏引擎
unicrom_深圳市由你创科技4 小时前
Unity开发如何解决iOS闪退问题
unity·ios·蓝桥杯
Yasin Chen9 小时前
C# Dictionary源码分析
算法·unity·哈希算法
阿蒙Amon11 小时前
C# Linq to SQL:数据库编程的解决方案
数据库·c#·linq
iCxhust14 小时前
c# U盘映像生成工具
开发语言·单片机·c#
emplace_back15 小时前
C# 集合表达式和展开运算符 (..) 详解
开发语言·windows·c#