Excel与Unity工作流(四):对话框架拓展:结合MVE实现Excel调用函数与批量支线导入管理思路

前言

本系列前面两篇更多的都是通过脚本来使用Excel,功能也仅仅局限于与变量有关的赋值、判断等,更复杂的功能无法实现。

Excel与Unity工作流(二):基础对话框架
Excel与Unity工作流(三):对话框架拓展:Excel表内变量导入 赋值 判断

本篇将实现利用Excel调用Unity项目中的脚本函数,通过Excel直接调用Unity函数,实现更大程度上的无代码编程。如果框架加以优化,可以进一步为UGC与AIGC提供便利。

Excel导入示例

文件一:路径 Resouces/Chat/00.csv
在这里插入图片描述

文件二:路径 Resouces/Chat/01.csv
在这里插入图片描述

使用说明

"@"行

本文在原本对话框架的基础上引入了"@"行,用于调用函数
"@"行只会执行该行的函数名,不会进行文本替换、图片替换等
"跳转函数名"列,该列根据提供的函数名表来填写
“跳转函数类别"列,有"无参数函数”,“整数参数函数”,“浮点参数函数”,"字符参数函数"4种,根据提供的函数名表来填写
"参数"列,函数需要传入的参数放在这里;参数与参数之间用^分隔
函数名表

在这里插入图片描述

实现

MVE

MVE全称Model View EventManager,是模型(model) - 视图(view) - 事件控制器(EventManager)的缩写。
MVE方法可看参考的这篇

https://zhuanlan.zhihu.com/p/384528685
本文主要通过利用EventManager实现函数添加与外部调用
EventManager主要通过Delegate实现,具体原因可看这篇
MVE方法中EventManager的多种实现办法(Unity c#)

这里就不多解释了,直接贴代码

private static EventManager _instance = null;
    public static EventManager GetInstance
    {
        get
        {
            if (_instance == null)
                _instance = new EventManager();
            return _instance;
        }
    }
#region Delegate 
    //强烈建议所有delegate只有一个function,因为一次Remove就是Remove整个delegate
    delegate void DeleFunc();
    delegate void DeleFuncFloat(float[] f);
    delegate void DeleFuncInt(int[] i);
    delegate void DeleFuncString(string[] s);
    private Dictionary<string, DeleFunc> eventData = new Dictionary<string, DeleFunc>();
    private Dictionary<string, DeleFuncFloat> eventSetData = new Dictionary<string, DeleFuncFloat>();
    private Dictionary<string, DeleFuncInt> eventSetData_int = new Dictionary<string, DeleFuncInt>();
    private Dictionary<string, DeleFuncString> eventSetData_string = new Dictionary<string, DeleFuncString>();

    /// <summary>
    /// 提供给外部使用的添加监听改变数值的事件
    /// </summary>
    /// <param name="eventName">监听的事件名字</param>
    /// <param name="function">监听的函数</param>
    public void AddSetListener(string eventName, UnityAction<float[]> function)
    {
        DeleFuncFloat deleFunc = new DeleFuncFloat(function);
        if (!eventSetData.ContainsKey(eventName))
        {
            eventSetData.Add(eventName, deleFunc);
            //Debug.Log("AddSetListener" + eventName);
        }
        if (eventSetData.ContainsKey(eventName) && !eventSetData[eventName].Equals(deleFunc))
        {
            eventSetData[eventName] += deleFunc;
            //Debug.Log("AddSetListener" + eventName);
        }
    }
    /// <summary>
    /// 提供给外部使用的移除升级事件监听
    /// </summary>
    /// <param name="eventName">监听的事件名字</param>
    /// <param name="function">监听的函数</param>
    public void RemoveSetListener(string eventName, UnityAction<float[]> function)
    {
        if (!eventData.ContainsKey(eventName)) return;
        //Debug.Log("Remove" + eventName);
        {
            eventSetData.Remove(eventName);
            Debug.Log("RemoveAll" + eventName);
        }
    }

    /// <summary>
    /// 提供给外部使用的添加监听升级事件
    /// </summary>
    /// <param name="eventName">监听的事件名字</param>
    /// <param name="function">监听的函数</param>
    public void AddListener(string eventName, UnityAction function)
    {
        DeleFunc deleFunc = new DeleFunc(function);
        if (!eventData.ContainsKey(eventName))
        {
            eventData.Add(eventName, deleFunc);
            //Debug.Log("AddListener" + eventName);
        }
        if (eventData.ContainsKey(eventName) && !eventData[eventName].Equals(deleFunc))
        {
            eventData[eventName] += deleFunc;
            //Debug.Log("AddListener" + eventName);
        }
    }

    /// <summary>
    /// 提供给外部使用的移除升级事件监听
    /// </summary>
    /// <param name="eventName">监听的事件名字</param>
    /// <param name="function">监听的函数</param>
    public void RemoveListener(string eventName, UnityAction function)
    {
        if (!eventData.ContainsKey(eventName)) return;
        //Debug.Log("Remove" + eventName);
        {
            eventData.Remove(eventName);
            Debug.Log("RemoveAll" + eventName);
        }
    }
    /// <summary>
    /// 提供给外部使用的添加监听改变数值的事件
    /// </summary>
    /// <param name="eventName">监听的事件名字</param>
    /// <param name="function">监听的函数</param>
    public void AddSetListener(string eventName, UnityAction<int[]> function)
    {
        DeleFuncInt deleFunc = new DeleFuncInt(function);
        if (!eventSetData_int.ContainsKey(eventName))
        {
            eventSetData_int.Add(eventName, deleFunc);
            //Debug.Log("AddSetListener" + eventName);
        }
        if (eventSetData_int.ContainsKey(eventName) && !eventSetData_int[eventName].Equals(deleFunc))
        {
            eventSetData_int[eventName] += deleFunc;
            //Debug.Log("AddSetListener" + eventName);
        }
    }
    /// <summary>
    /// 提供给外部使用的移除升级事件监听
    /// </summary>
    /// <param name="eventName">监听的事件名字</param>
    /// <param name="function">监听的函数</param>
    public void RemoveSetListener(string eventName, UnityAction<int[]> function)
    {
        if (!eventSetData_int.ContainsKey(eventName)) return;

        eventSetData_int.Remove(eventName);
        Debug.Log("RemoveAll" + eventName);

    }

    /// <summary>
    /// 提供给外部使用的添加监听改变数值的事件
    /// </summary>
    /// <param name="eventName">监听的事件名字</param>
    /// <param name="function">监听的函数</param>
    public void AddSetListener(string eventName, UnityAction<string[]> function)
    {
        DeleFuncString deleFunc = new DeleFuncString(function);
        if (!eventSetData_string.ContainsKey(eventName))
        {
            eventSetData_string.Add(eventName, deleFunc);
            //Debug.Log("AddSetListener" + eventName);
        }
        if (eventSetData_string.ContainsKey(eventName) && !eventSetData_string[eventName].Equals(deleFunc))
        {
            eventSetData_string[eventName] += deleFunc;
            //Debug.Log("AddSetListener" + eventName);
        }

    }
    /// <summary>
    /// 提供给外部使用的移除升级事件监听
    /// </summary>
    /// <param name="eventName">监听的事件名字</param>
    /// <param name="function">监听的函数</param>
    public void RemoveSetListener(string eventName, UnityAction<string[]> function)
    {
        if (!eventSetData_string.ContainsKey(eventName)) return;
        eventSetData_string.Remove(eventName);
        Debug.Log("RemoveAll" + eventName);
    }
    /// <summary>
    /// 触发事件
    /// </summary>
    /// <param name="eventName">监听的事件名字</param>
    public void TriggerEvent(string eventName)
    {
        if (!SystemModel.GetModel.TriggeredEvent.Contains(eventName))
        {
            if (SystemModel.GetModel.isEnterChat || SystemModel.GetModel.isMeChatTalking)
            {
                SystemModel.GetModel.TriggeredEvent.Add(eventName);
            }
            if (eventData[eventName] != null)
            {
                Debug.Log("BeginTriggerEvent" + eventName);
                eventData[eventName].Invoke();
                Debug.Log("TriggerEvent" + eventName);
            }
        }
    }

    /// <summary>
    /// 触发事件
    /// </summary>
    /// <param name="eventName">监听的事件名字</param>
    public void TriggerSetEvent(string eventName, float[] model)
    {
        if (!SystemModel.GetModel.TriggeredEvent.Contains(eventName))
        {
            if (SystemModel.GetModel.isEnterChat || SystemModel.GetModel.isMeChatTalking)
            {
                SystemModel.GetModel.TriggeredEvent.Add(eventName);
            }
            if (eventSetData[eventName] != null)
            {
                Debug.Log("BeginTriggerEvent" + eventName);
                eventSetData[eventName].Invoke(model);
                Debug.Log("TriggerEvent" + eventName);
            }
        }

    }
    /// <summary>
    /// 触发事件
    /// </summary>
    /// <param name="eventName">监听的事件名字</param>
    public void TriggerSetEvent(string eventName, int[] model)
    {
        if (!SystemModel.GetModel.TriggeredEvent.Contains(eventName))
        {
            if (SystemModel.GetModel.isEnterChat || SystemModel.GetModel.isMeChatTalking)
            {
                SystemModel.GetModel.TriggeredEvent.Add(eventName);
            }
            if (eventSetData_int[eventName] != null)
            {
                Debug.Log("BeginTriggerEvent" + eventName);
                eventSetData_int[eventName].Invoke(model);
                Debug.Log("TriggerEvent" + eventName);
            }
        }

    }

    public void TriggerSetEvent(string eventName, string[] model)
    {
        if (!SystemModel.GetModel.TriggeredEvent.Contains(eventName))
        {
            if (SystemModel.GetModel.isEnterChat || SystemModel.GetModel.isMeChatTalking)
            {
                SystemModel.GetModel.TriggeredEvent.Add(eventName);
            }
            if (eventSetData_string[eventName] != null)
            {
                Debug.Log("BeginTriggerEvent" + eventName);
                eventSetData_string[eventName].Invoke(model);
                Debug.Log("TriggerEvent" + eventName);
            }
        }

    }

    public void ClearEvent(string name)
    {
        if (eventData.ContainsKey(name))
        {
            eventData.Remove(name);
        }
        if (eventSetData.ContainsKey(name))
        {
            eventSetData.Remove(name);
        }
        if (eventSetData_int.ContainsKey(name))
        {
            eventSetData_int.Remove(name);
        }
        if (eventSetData_string.ContainsKey(name))
        {
            eventSetData_string.Remove(name);
        }
    }

    public void ClearAllEvents()
    {
        if (eventData.Count != 0)
        {
            eventData.Clear();
            //eventData = null;
            //这里不使用null的原因是因为所有的AddListener都没有检查是否为Null的判断,如果设定为Null会出错
        }
        if (eventSetData.Count != 0)
        {
            eventSetData.Clear();
            //eventSetData = null;
        }
        if (eventSetData_int.Count != 0)
        {
            eventSetData_int.Clear();
            //eventSetData_int = null;
        }
        if (eventSetData_string.Count != 0)
        {
            eventSetData_string.Clear();
            //eventSetData_string = null;
        }
    }
    #endregion 

对话框架调整

在拥有了EventManager之后,如何让它与对话框架结合呢?
只需要在原本对话框架的基础上对"ShowDialogRow"方法增添对"@"行的支持调用即可

if (cells[i][0] == "@")
                //中断对话,去调用一个函数
                {
                    canNext = false;
                    dialogIndex = int.Parse(cells[i][4]);
                    SystemModel.GetModel.TriggeredEvent.Clear();

                    if (cells[i][11] == "无参数函数")
                    {
                        EventManager.GetInstance.TriggerEvent(cells[i][10]);
                    }
                    else if (cells[i][11] == "整数参数函数")
                    {
                        string[] s = cells[i][12].Split('^');
                        int[] a = new int[s.Length];
                        for (int j = 0; j < s.Length; j++)
                        {
                            a[j] = int.Parse(s[j]);
                        }
                        EventManager.GetInstance.TriggerSetEvent(cells[i][10], a);
                    }
                    else if (cells[i][11] == "浮点参数函数")
                    {
                        string[] s = cells[i][12].Split('^');
                        float[] a = new float[s.Length];
                        for (int j = 0; j < s.Length; j++)
                        {
                            a[j] = float.Parse(s[j]);
                        }
                        EventManager.GetInstance.TriggerSetEvent(cells[i][10], a);
                    }
                    else if (cells[i][11] == "字符参数函数")
                    {
                        string[] s = cells[i][12].Split('^');
                        string[] a = new string[s.Length];
                        for (int j = 0; j < s.Length; j++)
                        {
                            a[j] = s[j];
                        }
                        EventManager.GetInstance.TriggerSetEvent(cells[i][10], a);
                    }
                    break;
                }

更改完的ShowDialogRow

public void ShowDialogRow()
    {
        for (int i = 1; i < dialogRows.Length; i++)//遍历来寻找正确的一行 首行是列名当然不含信息 所以i从1开始
        {
            if (int.Parse(cells[i][1]) == dialogIndex)
            {
                //播放音乐
                if (cells[i].Length > 10)
                {
                    if (cells[i][10] != "")
                    {
                        PlayBackGroundMusic(cells[i][10]);
                    }
                }

                //播放音效
                if (cells[i].Length > 11)
                {
                    if (cells[i][11] != "")
                    {
                        print("PlayEffectMusic" + cells[i][11]);
                        PlayEffectMusic(cells[i][11]);
                    }
                }

                if (cells[i][0] == "*")
                {

                    UpdateBackGround(cells[i][9]);
                    UpdateImage(cells[i][7], cells[i][8]);
                    if (cells[i][5] != "")
                    {
                        OptionEffect(cells[i][5]);
                    }


                    if (cells[i][4].Contains('&'))
                    {
                        string[] judge = cells[i][6].Split('>');
                        string[] jump = cells[i][4].Split('&');
                        OptionJudge(judge[0], judge[1], int.Parse(jump[0]), int.Parse(jump[1]));
                    }
                    else
                    {
                        dialogIndex = int.Parse(cells[i][4]);
                        ShowDialogRow();
                    }
                    break;
                }
                if (cells[i][0] == "@")
                //中断对话,去调用一个函数
                {
                    canNext = false;
                    dialogIndex = int.Parse(cells[i][4]);
                    SystemModel.GetModel.TriggeredEvent.Clear();

                    if (cells[i][11] == "无参数函数")
                    {
                        EventManager.GetInstance.TriggerEvent(cells[i][10]);
                    }
                    else if (cells[i][11] == "整数参数函数")
                    {
                        string[] s = cells[i][12].Split('^');
                        int[] a = new int[s.Length];
                        for (int j = 0; j < s.Length; j++)
                        {
                            a[j] = int.Parse(s[j]);
                        }
                        EventManager.GetInstance.TriggerSetEvent(cells[i][10], a);
                    }
                    else if (cells[i][11] == "浮点参数函数")
                    {
                        string[] s = cells[i][12].Split('^');
                        float[] a = new float[s.Length];
                        for (int j = 0; j < s.Length; j++)
                        {
                            a[j] = float.Parse(s[j]);
                        }
                        EventManager.GetInstance.TriggerSetEvent(cells[i][10], a);
                    }
                    else if (cells[i][11] == "字符参数函数")
                    {
                        string[] s = cells[i][12].Split('^');
                        string[] a = new string[s.Length];
                        for (int j = 0; j < s.Length; j++)
                        {
                            a[j] = s[j];
                        }
                        EventManager.GetInstance.TriggerSetEvent(cells[i][10], a);
                    }
                    break;
                }
                if (cells[i][0] == "#")
                {
                    UpdateBackGround(cells[i][9]);

                    UpdateImage(cells[i][7], cells[i][8]);
                    UpdateText(cells[i][2], cells[i][3]);


                    if (cells[i][5] != "")
                    {
                        OptionEffect(cells[i][5]);
                    }
                    if (cells[i][4].Contains('&'))
                    {
                        string[] judge = cells[i][6].Split('>');
                        string[] jump = cells[i][4].Split('&');
                        OptionJudge(judge[0], judge[1], int.Parse(jump[0]), int.Parse(jump[1]));
                    }
                    else
                    {
                        dialogIndex = int.Parse(cells[i][4]);
                    }
                    break;
                }
                if (cells[i][0] == "&")
                {
                    canNext = false;
                    GenerateOption(i);

                    break;
                }
                if (cells[i][0] == "end")
                {

                    EndChat();
                    break;
                }
            }
        }
    }

基础的对话框架实现可看这篇
https://blog.youkuaiyun.com/ThroughTheArbor/article/details/136910726?spm=1001.2014.3001.5502

函数添加

投掷随机数

在Model.cs的文件里写

static int ProbabilityRandomNum;//概率随机数
    public static int GetProbabilityRandomNum
    {
        get
        {
            return ProbabilityRandomNum;
        }
    }
    public void SetProbabilityRandomNum()
    {
        ProbabilityRandomNum = Random.Range(1, 101);//随机数的取值范围包括前一个数,但不包括后一个数
    }

将函数添加进EventManager

EventManager.GetInstance.AddListener("投概率随机数", SetProbabilityRandomNum);

更改对话框架里的判断函数

if (_judge == "随机数")
        {
            if (SystemModel.GetProbabilityRandomNum >= int.Parse(_param2))
            {
                if (isJumpNow)
                {
                    OnOptionClick(_jump1);
                }
                else
                {
                    dialogIndex = _jump1;
                    canNext = true;
                }
            }
            else
            {
                if (isJumpNow)
                {
                    OnOptionClick(_jump2);
                }
                else
                {
                    dialogIndex = _jump2;
                    canNext = true;
                }
            }
        }

更改后的判断函数

/// <summary>
    /// 根据文件中的条件来判断应该跳转到哪;一般是>=这个条件的跳转到位置1,否则跳转到位置2
    /// </summary>
    /// <param name="_judge">判断的条件</param>
    /// <param name="_param2">条件的大小</param>
    /// <param name="_jump1">跳转的位置1</param>
    /// <param name="_jump2">跳转的位置2</param>
    void OptionJudge(string _judge, string _param2, int _jump1, int _jump2)
    {
        if (_judge.Contains("@"))
        {
            string[] judgs = _judge.Split('@');
            if (judgs.Length < 2) return;
            switch (judgs[0])
            {
                case "int":
                    int values = GetIntValues(judgs[1]);
                    if (values >= int.Parse(_param2))
                    {
                        OnOptionClick(_jump1);
                    }
                    else
                    {
                        OnOptionClick(_jump2);
                    }
                    break;
                case "float":
                    float values1 = GetFloatValues(judgs[1]);
                    if (values1 >= int.Parse(_param2))
                    {
                        OnOptionClick(_jump1);
                    }
                    else
                    {
                        OnOptionClick(_jump2);
                    }
                    break;
                case "string":
                    string values2 = GetStringValues(judgs[1]);
                    if (values2 == _param2)
                    {
                        OnOptionClick(_jump1);
                    }
                    else
                    {
                        OnOptionClick(_jump2);
                    }
                    break;

            }
            return;
        }
        if (_judge == "好感度")
        {
            if (likeValue >= int.Parse(_param2))
            {
                OnOptionClick(_jump1);
            }
            else
            {
                OnOptionClick(_jump2);
            }

        }
        else if (_judge == "时间")
        {
            string[] timeJudge = _param2.Split(':');

            if (time[0] >= int.Parse(timeJudge[0]) && time[1] >= int.Parse(timeJudge[1]))
            {
                OnOptionClick(_jump1);
            }
            else
            {
                OnOptionClick(_jump2);
            }

        }
        else if (_judge == "活力")
        {
            if (energyValue >= int.Parse(_param2))
            {
                OnOptionClick(_jump1);
            }
            else
            {
                OnOptionClick(_jump2);
            }
        }
        else if (_judge == "随机数")
        {
            if (SystemModel.GetProbabilityRandomNum >= int.Parse(_param2))
            {
                OnOptionClick(_jump1);
            }
            else
            {
                OnOptionClick(_jump2);
            }
        }
    }

然而,仅仅配置完这些就可以实现想要实现的函数调用功能了吗?no no no,大漏特漏
因为在ShowDialogRow中对"@"行只执行对应函数,并且中断了对话,我们需要在调用的函数结束时手动继续对话

继续对话函数

在ChatController里添加一个方法

public void NextShowDialogRow()
    {
        print("NextShowDialogRow()");
        ShowDialogRow();
        canNext = true;
    }

添加进EventManager

EventManager.GetInstance.AddListener("继续对话普通", NextShowDialogRow);

原本的函数末尾添加

public void SetProbabilityRandomNum()
    {
        ProbabilityRandomNum = Random.Range(1, 101);//随机数的取值范围包括前一个数,但不包括后一个数
        EventManager.GetInstance.TriggerEvent("继续对话普通");
    }

但是这样就可以了吗?
如果有多个ChatController的对话框架,那么调用"继续对话普通"函数时就会调用很多个ChatController的"继续对话普通"函数,从而导致一些错误

所以我们需要在开始一个对话之前先把EventManager里的"继续对话普通"给清空,并添加当前函数的"继续对话普通",这样就保证其他地方调用的是此时这个对话的了

以下代码放在BeginChat的方法里

//先Clear掉之前的"继续对话微信",保证之前没清掉的不会调用
            EventManager.GetInstance.ClearEvent("继续对话普通");
            EventManager.GetInstance.AddListener("继续对话普通", NextShowDialogRow);

以下代码放在EndChat的方法里

EventManager.GetInstance.RemoveListener("继续对话普通", ShowDialogRow); 

经过这样一番配置,才能较好实现对函数的调用管理

全屏对话跳转播放

这个函数可以手动设置开始的ID数,所以原本的对话框架也应该做一些调整

public void SetBeginId(int begin)
    {
        beginID = begin;
    }

全屏对话跳转函数

/// <summary>
    /// 全屏对话转全屏对话并立刻播放
    /// </summary>
    /// <param name="textAssetName"></param>
    public void ChatToChatShow(string[] textAssetName)
    {
        chatController.EndChat();
        if (textAssetName.Length >= 2)
        {
            ShowChatPanel(textAssetName[0], int.Parse(textAssetName[1]));
        }
        else
        {
            ShowChatPanel(textAssetName[0]);
        }
    }

    //控制对话系统文本
    public void ShowChatPanel(string AssetNameResources)
    {
        print("ShowChatPanel" + AssetNameResources);
        chatController.SetTextAsset(AssetNameResources);
        chatController.BeginChat();
    }
    public void ShowChatPanel(string AssetNameResources, int beginID)
    {
        print("ShowChatPanel" + AssetNameResources);
        chatController.SetTextAsset(AssetNameResources);
        chatController.SetBeginId(beginID);
        chatController.BeginChat();
    }

在EventManager中添加函数

EventManager.GetInstance.AddSetListener("全屏对话跳转播放", ChatToChatShow);

批量支线导入与管理思路

支线的简易框架 做了个简单的图表示一下

在这里插入图片描述

通过总管理文档管理所有支线文档
在单个支线文档里又会根据是否是第一次进入进行区分。此时只是会在界面上出现可交互物,玩家只有点击可交互物之后才会出现对应的支线(相当于只是进行了初始化)
在玩家交互之后,就正是进入支线了

支线配表示例

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

因为篇幅限制,这里就不展示实现办法了。了解思路之后,结合函数调用、前几篇提到的判定等就可以自己实现啦。

参考

https://zhuanlan.zhihu.com/p/384528685

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值