前言
本系列前面两篇更多的都是通过脚本来使用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);
批量支线导入与管理思路
支线的简易框架 做了个简单的图表示一下

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



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

被折叠的 条评论
为什么被折叠?



