目录
自己做的对话系统,使用QFrameworks框架
Model 数据结构:
对话数据:
[
{
"TreeId": "0",
"NodeId": "0",
"TextData": "",
"DefaultNextTreeId": "0",
"DefaultNextNodeId": "0"
},
{
"TreeId": "0",
"NodeId": "1",
"TextData": "#商人|你好,我是一名商人",
"DefaultNextTreeId": "0",
"DefaultNextNodeId": "0"
},
{
"TreeId": "1",
"NodeId": "1",
"TextData": "第一句话xxxxxxxxxxxxxxxxxx|#人名1|变更人名xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|测试无选项转入下一个节点",
"DefaultNextTreeId": "1",
"DefaultNextNodeId": "2"
},
{
"TreeId": "1",
"NodeId": "2",
"TextData": "已转入节点xxxxxxxxxxxxxxxxxx|显示3个选项xxxxxxxxxxxxxxxxxx",
"DefaultNextTreeId": "0",
"DefaultNextNodeId": "0"
},
{
"TreeId": "1",
"NodeId": "3",
"TextData": "选项1xxxxxxxxxxxxxxxxxx|#|清空人名xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|测试测试测试测试",
"DefaultNextTreeId": "0",
"DefaultNextNodeId": "0"
},
{
"TreeId": "1",
"NodeId": "4",
"TextData": "选项2xxxxxxxxxxxxxxxxxx|#人名2|变化人名xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|以按钮结束对话",
"DefaultNextTreeId": "0",
"DefaultNextNodeId": "0"
},
{
"TreeId": "1",
"NodeId": "5",
"TextData": "选项3xxxxxxxxxxxxxxxxxx|未变化人名|文字结束后自动结束对话",
"DefaultNextTreeId": "0",
"DefaultNextNodeId": "0"
}
]
[
{
"TreeId": "0",
"NodeId": "1",
"AimTreeId": "1",
"AimNodeId": "1",
"Title": "开始测试",
"Index": "0"
},
{
"TreeId": "1",
"NodeId": "2",
"AimTreeId": "1",
"AimNodeId": "3",
"Title": "选项1",
"Index": "0"
},
{
"TreeId": "1",
"NodeId": "2",
"AimTreeId": "1",
"AimNodeId": "4",
"Title": "选项2",
"Index": "1"
},
{
"TreeId": "1",
"NodeId": "2",
"AimTreeId": "1",
"AimNodeId": "5",
"Title": "选项3",
"Index": "2"
},
{
"TreeId": "1",
"NodeId": "3",
"AimTreeId": "1",
"AimNodeId": "2",
"Title": "返回选择",
"Index": "0"
},
{
"TreeId": "1",
"NodeId": "4",
"AimTreeId": "0",
"AimNodeId": "0",
"Title": "结束对话",
"Index": "0"
},
{
"TreeId": "1",
"NodeId": "4",
"AimTreeId": "1",
"AimNodeId": "2",
"Title": "返回选择",
"Index": "1"
}
]
刚开始使用的json结构,node和agent分成了两个列表,最近改了xml的
<root>
<ConversationTree TreeId="0" >
<Node NodeId="0" TextData="" DefaultNextTreeId="0" DefaultNextNodeId="0">
</Node>
<Node NodeId="1" TextData="#商人|你好,我是一名商人" DefaultNextTreeId="0" DefaultNextNodeId="0">
<ChildAgent TargetTreeId="0" TargetNodeId="0" Title="离开" />
<ChildAgent TargetTreeId="1" TargetNodeId="1" Title="开始测试" />
</Node>
</ConversationTree>
<ConversationTree TreeId="1" >
<Node NodeId="1" TextData="第一句话|#人名1|变更人名|测试无选项转入下一个节点" DefaultNextTreeId="1" DefaultNextNodeId="2">
</Node>
<Node NodeId="2" TextData="已转入节点|显示3个选项" DefaultNextTreeId="0" DefaultNextNodeId="0">
<ChildAgent TargetTreeId="1" TargetNodeId="5" Title="选项3" />
<ChildAgent TargetTreeId="1" TargetNodeId="4" Title="选项2" />
<ChildAgent TargetTreeId="1" TargetNodeId="3" Title="选项1" />
</Node>
<Node NodeId="3" TextData="选项1|#|清空人名|测试测试测试测试" DefaultNextTreeId="0" DefaultNextNodeId="0">
<ChildAgent TargetTreeId="1" TargetNodeId="2" Title="返回选择" />
</Node>
<Node NodeId="4" TextData="选项2|#人名2|变化人名|以按钮结束对话" DefaultNextTreeId="0" DefaultNextNodeId="0">
<ChildAgent TargetTreeId="1" TargetNodeId="2" Title="返回选择" />
<ChildAgent TargetTreeId="0" TargetNodeId="0" Title="离开" />
</Node>
<Node NodeId="5" TextData="选项3|未变化人名|文字结束后自动结束对话" DefaultNextTreeId="0" DefaultNextNodeId="0">
</Node>
</ConversationTree>
<ConversationTree TreeId="2" >
<Node NodeId="1" TextData="#商人|我的店铺刚被怪物摧毁了|如果你可以帮我收集修复的木材,我可以支付你报酬并且店铺商品也会有优惠" DefaultNextTreeId="1" DefaultNextNodeId="2">
<ChildAgent TargetTreeId="2" TargetNodeId="2" Title="那些怪物来自哪里?" />
</Node>
<Node NodeId="2" TextData="#商人|那是一群哥布林,它们的营地就在东边的山洞深处" DefaultNextTreeId="0" DefaultNextNodeId="0">
<ChildAgent TargetTreeId="2" TargetNodeId="3" Title="我可以帮你收集木材" />
<ChildAgent TargetTreeId="0" TargetNodeId="0" Title="我还有其他事" />
</Node>
<Node NodeId="3" TextData="#商人|多谢,此处向西有一片树林,那里会有足够的木材" DefaultNextTreeId="0" DefaultNextNodeId="0">
</Node>
<Node NodeId="4" TextData="#商人|多谢您的帮助xxxxxxx" DefaultNextTreeId="0" DefaultNextNodeId="0">
</Node>
<Node NodeId="5" TextData="#商人|多谢您的帮助" DefaultNextTreeId="0" DefaultNextNodeId="0">
</Node>
</ConversationTree>
</root>
代码结构:
//存储一个系列的对话节点
public class ConversationNodeTree
{
public int TreeId{get;set;}
public List<ConversationNode> nodeList = new List<ConversationNode>();
public ConversationNode GetNode(int nodeId)
{
return nodeList.Single(n=>n.NodeId == nodeId);
}
}
//对话节点
public class ConversationNode
{
//Entity
public int TreeId{get;set;}
public int NodeId{get;set;}
public string TextData{get;set;}
public int DefaultNextTreeId{get;set;}
public int DefaultNextNodeId{get;set;}
[Tooltip("以“#”加名字表示下面的对话是此人说的话,每行以‘|’隔开")]
public List<string> lines;
public List<ConversationNodeAgent> childNodeAgents = new List<ConversationNodeAgent>();
}
//用于表示对话中的选项
public class ConversationNodeAgent
{
public int TreeId{get;set;}
public int NodeId{get;set;}
public int TargetTreeId{get;set;}
public int TargetNodeId{get;set;}
//可用于发送事件时判断点击选项的参数
public string Title{get;set;}
public ConversationNodeAgent() { }
public ConversationNodeAgent(int treeID, int talkNodeID, int aimTreeID, int aimNodeID, string title)
{
this.TreeId = treeID;
this.NodeId = talkNodeID;
this.TargetTreeId = aimTreeID;
this.TargetNodeId = aimNodeID;
this.Title = title;
}
}
解析数据:
public class ConversationModel : AbstractModel
{
//存储不进行的更改初始配置数据,当玩家数据为空时使用初始数据
//private Dictionary<int, ConversationNodeTree> configConversationTreeDic = new Dictionary<int, ConversationNodeTree>();
private Dictionary<int, ConversationNodeTree> playerConversationTreeDic = new Dictionary<int, ConversationNodeTree>();
XDocument xmlDoc;
protected override void OnInit()
{
//Dictionary<int, ConversationNodeTree> configConversationTreeDic = LoadDicFromJson(PathConfig.configConversationNodeDataPath, PathConfig.configConversationChildNodeDataPath);
//if(!File.Exists(PathConfig.playerConversationNodeDataPath) || !File.Exists(PathConfig.playerConversationChildNodeDataPath))
//{
// playerConversationTreeDic = configConversationTreeDic;
// xmlDoc = XDocument.Load(PathConfig.configConversationNodeDataPath);
//}
//else
//{
// playerConversationTreeDic = LoadDicFromJson(PathConfig.playerConversationNodeDataPath, PathConfig.playerConversationChildNodeDataPath);
// xmlDoc = XDocument.Load(PathConfig.playerConversationNodeDataPath);
//}
if (!File.Exists(PathConfig.playerConversationXmlDataPath))
{
playerConversationTreeDic = LoadDicFromXml(PathConfig.configConversationXmlDataPath);
xmlDoc = XDocument.Load(PathConfig.configConversationXmlDataPath);
}
else
{
playerConversationTreeDic = LoadDicFromXml(PathConfig.playerConversationXmlDataPath);
xmlDoc = XDocument.Load(PathConfig.playerConversationXmlDataPath);
}
}
[Obsolete("存储json数据,已弃用,改为xml存储")]
Dictionary<int, ConversationNodeTree> LoadDicFromJson(string nodePath, string agentPath)
{
Dictionary<int, ConversationNodeTree> dic = new Dictionary<int, ConversationNodeTree>();
if(!File.Exists(nodePath)||!File.Exists(agentPath))
{
//todo:
Debug.Log("数据不存在");
return null;
}
string s = File.ReadAllText(nodePath,System.Text.Encoding.UTF8);
//Debug.Log(s);
JsonData jsonArray = JsonMapper.ToObject(s);
for (int i = 0; i < jsonArray.Count; i++)
{
JsonData j = jsonArray[i];
ConversationNodeTree t;
if (!dic.ContainsKey(j["TreeId"].ToInt32()))
{
t = new ConversationNodeTree();
t.TreeId = j["TreeId"].ToInt32();
dic.Add(j["TreeId"].ToInt32(), t);
}
t = dic[j["TreeId"].ToInt32()];
ConversationNode n = new ConversationNode();
n.TreeId = j["TreeId"].ToInt32();
n.NodeId = j["NodeId"].ToInt32();
//n.TextData =j["TextData"].ToString();
n.DefaultNextTreeId = j["DefaultNextTreeId"].ToInt32();
n.DefaultNextNodeId = j["DefaultNextNodeId"].ToInt32();
//ConversationNode n = JsonMapper.ToObject<ConversationNode>(j.ToString());
string[] strs = j["TextData"].ToString().Split("|");
n.lines = new List<string>(strs);
t.nodeList.Add(n);
}
s = File.ReadAllText(agentPath, System.Text.Encoding.UTF8);
jsonArray = JsonMapper.ToObject(s);
for (int i = 0; i < jsonArray.Count; i++)
{
JsonData j = jsonArray[i];
//Debug.Log(JsonMapper.ToJson(j));
ConversationNodeAgent agent = new ConversationNodeAgent(
j["TreeId"].ToInt32(),
j["NodeId"].ToInt32(),
j["AimTreeId"].ToInt32(),
j["AimNodeId"].ToInt32(),
j["Title"].ToString()
);
//ConversationNodeAgent agent = JsonMapper.ToObject<ConversationNodeAgent>(j.ToString());
ConversationNode n = dic[j["TreeId"].ToInt32()].GetNode(j["NodeId"].ToInt32());
n.childNodeAgents.Add(agent);
//agent.Index = n.childNodeAgents.Count-1;
}
return dic;
}
Dictionary<int, ConversationNodeTree> LoadDicFromXml(string path)
{
Dictionary<int, ConversationNodeTree> dic = new Dictionary<int, ConversationNodeTree>();
if(!File.Exists(path))
{
//todo:
Debug.Log("数据不存在");
return null;
}
XDocument doc = XDocument.Load(path);
XElement root = doc.Root;
IEnumerable<XElement> treeData = root.Elements("ConversationTree");
foreach(var td in treeData)
{
ConversationNodeTree tree = new ConversationNodeTree();
tree.TreeId = td.Attribute("TreeId").Value.ToInt32();
tree.nodeList = new List<ConversationNode>();
IEnumerable<XElement> nodeData = td.Elements("Node");
foreach(var nd in nodeData)
{
ConversationNode node = new ConversationNode();
node.TreeId = tree.TreeId;
node.NodeId = nd.Attribute("NodeId").Value.ToInt32();
node.lines = nd.Attribute("TextData").Value.Split('|').ToList();
node.DefaultNextTreeId = nd.Attribute("DefaultNextTreeId").Value.ToInt32();
node.DefaultNextNodeId = nd.Attribute("DefaultNextNodeId").Value.ToInt32();
node.childNodeAgents = new List<ConversationNodeAgent>();
IEnumerable<XElement> agentData = nd.Elements("ChildAgent");
foreach(var ad in agentData)
{
ConversationNodeAgent agent = new ConversationNodeAgent();
agent.TreeId = tree.TreeId;
agent.NodeId = node.NodeId;
agent.TargetTreeId = ad.Attribute("TargetTreeId").Value.ToInt32();
agent.TargetNodeId = ad.Attribute("TargetNodeId").Value.ToInt32();
agent.Title = ad.Attribute("Title").Value;
node.childNodeAgents.Add(agent);
}
tree.nodeList.Add(node);
}
dic.Add(tree.TreeId , tree);
}
return dic;
}
void SaveAllXml()
{
foreach(var pair in playerConversationTreeDic)
{
for(int i = 0;i<pair.Value.nodeList.Count;i++)
{
SaveNode(pair.Value.nodeList[i]);
}
}
SaveXmlDoc();
}
void SaveNode(ConversationNode node)
{
XElement root = xmlDoc.Root;
IEnumerable<XElement> treeData = root.Elements("ConversationTree");
XElement td = treeData.Single(t=>t.Attribute("TreeId").Value.ToInt32() == node.TreeId);
IEnumerable<XElement> nodeData = td.Elements("Node");
XElement nd = nodeData.Single(n=>n.Attribute("NodeId").Value.ToInt32() == node.NodeId);
nd.RemoveNodes();
for(int i=0;i<node.childNodeAgents.Count;i++)
{
XElement agentData = new XElement("ChildAgent",
new XAttribute("TargetTreeId", node.childNodeAgents[i].TargetTreeId.ToString()),
new XAttribute("TargetNodeId", node.childNodeAgents[i].TargetNodeId.ToString()),
new XAttribute("Title", node.childNodeAgents[i].Title)
);
nd.Add(agentData);
}
}
void SaveXmlDoc()
{
xmlDoc.Save(PathConfig.playerConversationXmlDataPath);
}
public ConversationNode GetNode(int treeId, int nodeId)
{
ConversationNode node = null;
if (playerConversationTreeDic.ContainsKey(treeId))
{
node = playerConversationTreeDic[treeId].GetNode(nodeId);
}
if (node == null)
{
Debug.Log("没有此节点");
}
return node;
}
public void AddChildAgent(ConversationNodeAgent t)
{
AddChildAgent(t.TreeId, t.NodeId, t);
}
public void AddChildAgent(int treeId, int nodeId, ConversationNodeAgent t)
{
if(treeId==0&&nodeId==0) return;
ConversationNode n = GetNode(treeId, nodeId);
if(n.childNodeAgents.Any(a => a.Title == t.Title))
{
Debug.Log("存在相同标题的节点");
return;
}
t.TreeId = treeId;
t.NodeId = nodeId;
n.childNodeAgents.Add(t);
//t.Index = n.childNodeAgents.Count-1;
}
public void RemoveChildAgent(int treeId, int nodeId,string agentTitle)
{
ConversationNode n = GetNode(treeId, nodeId);
if(n.childNodeAgents.Any(a => a.Title == agentTitle))
{
Debug.Log(n.childNodeAgents.Count);
n.childNodeAgents = n.childNodeAgents.Where(a=>a.Title != agentTitle).ToList();
Debug.Log(n.childNodeAgents.Count);
return;
}
else
{
Debug.LogFormat("{0}-{1} 对话节点不存在标题为 {2} 子节点代理", treeId, nodeId, agentTitle);
}
}
[Obsolete("存储json数据,已弃用,改为xml存储")]
public void SaveConversationData()
{
JsonData nodejs = new JsonData();
JsonData agentjs = new JsonData();
foreach (var kv in playerConversationTreeDic)
{
for (int i = 0; i < kv.Value.nodeList.Count; i++)
{
JsonData nj = new JsonData();
ConversationNode n = kv.Value.nodeList[i];
nj["TreeId"] = n.TreeId;
nj["NodeId"] = n.NodeId;
nj["TextData"] = string.Join('|', n.lines.ToArray());
for (int j = 0; j < n.childNodeAgents.Count; j++)
{
JsonData aj = new JsonData();
ConversationNodeAgent t = n.childNodeAgents[i];
aj["TreeId"] = t.TreeId;
aj["NodeId"] = t.NodeId;
aj["AimTreeId"] = t.TargetTreeId;
aj["AimNodeId"] = t.TargetNodeId;
aj["Title"] = t.Title;
//aj["Index"] = t.Index;
agentjs.Add(aj);
}
nodejs.Add(nj);
}
}
//File.WriteAllText(PathConfig.playerConversationNodeDataPath,nodejs.ToString());
//File.WriteAllText(PathConfig.playerConversationChildNodeDataPath,agentjs.ToString());
}
}
System
控制对话的基本逻辑
public class ConversationSystem : AbstractSystem, IWindowUICtrl
{
public bool isShowingConversation = false;
ConversationWindowUIView conversationWindow;
private ConversationNode currNode;
private ConversationNode CurrNode
{
get=>currNode;
set
{
currNode = value;
}
}
private string CurrLine
{
get
{
if(lineIndex<0||lineIndex>CurrNode.lines.Count-1) return null;
return CurrNode.lines[lineIndex];
}
}
//private string currLineContent;
private int lineIndex;
private bool isShowingLine;
Tween showTween;
public void OpenWindow()
{
conversationWindow = this.GetSystem<UIWindowSystem>().OpenWindow(UIWindowType.Conversation) as ConversationWindowUIView;
}
public void CloseWindow()
{
this.GetSystem<UIWindowSystem>().CloseWindow(UIWindowType.Conversation);
}
public void StartConversation(int treeId, int nodeId)
{
OpenWindow();
conversationWindow.Init(OnClickEvent, OnShowLineOver, SelectionBtnClickEvent);
CurrNode = this.GetModel<ConversationModel>().GetNode(treeId, nodeId);
lineIndex = -1;
MoveNextLine();
InvokeNodeStartEvent(CurrNode);
isShowingLine = true;
isShowingConversation = true;
showTween = conversationWindow.ShowTextTween(CurrLine);
}
//非点击按钮的事件
void OnClickEvent()
{
if(isShowingLine)
{
if(showTween!=null)
{
showTween.Pause();
showTween.Kill();
}
isShowingLine = false;
conversationWindow.ShowAllText(CurrLine);
}
else
{
if(lineIndex<=CurrNode.lines.Count-2)
{
MoveNextLine();
isShowingLine = true ;
showTween = conversationWindow.ShowTextTween(CurrLine);
}
else
{
//最后一行且展示完时,没有按钮时的点击
List<ConversationNodeAgent> agents = CurrNode.childNodeAgents;
if (agents != null && agents.Count <= 0)
{
isShowingLine = true;
ChangeConversationNode(CurrNode.DefaultNextTreeId, CurrNode.DefaultNextNodeId, "");
}
}
}
}
//一行展示完时自动进行的,只有在最后一行结束且存在子节点时,才以此显示子节点选项
void OnShowLineOver()
{
isShowingLine = false;
if (lineIndex == CurrNode.lines.Count - 1)
{
List<ConversationNodeAgent> agents = CurrNode.childNodeAgents;
if (agents != null && agents.Count > 0)
{
conversationWindow.ShowSelection(agents);
}
}
}
void SelectionBtnClickEvent(ConversationNodeAgent agent)
{
ChangeConversationNode(agent);
}
//检查名称
void MoveNextLine()
{
lineIndex++;
if(CurrLine == null)
{
return;
}
if(CurrLine.StartsWith('#'))
{
string name = CurrLine.Remove(0, 1);
//CurrLine.Copyto(1, name, 0, CurrLine.Length);
conversationWindow.SetName(name);
MoveNextLine();
}
}
//没有选项显示直接结束的,会在检查选项时转到此处;有选项来结束的,选项指引转换节点,也会转到此处;两者都传入0-0节点,表示结束对话,同时关闭窗口
public void ChangeConversationNode(int aimTreeId,int aimNodeId, string selectionTitle)
{
if(aimTreeId == 0 && aimNodeId == 0)
{
EndConversation();
return;
}
InvokeNodeEndEvent(CurrNode, selectionTitle);
CurrNode = this.GetModel<ConversationModel>().GetNode(aimTreeId, aimNodeId);
lineIndex = -1;
MoveNextLine();
InvokeNodeStartEvent(CurrNode);
isShowingLine = true;
if (showTween != null)
{
showTween.Pause();
showTween.Kill();
}
showTween = conversationWindow.ShowTextTween(CurrLine);
}
public void ChangeConversationNode(ConversationNodeAgent agent)
{
ChangeConversationNode(agent.TargetTreeId, agent.TargetNodeId, agent.Title);
}
public void EndConversation()
{
InvokeNodeEndEvent(CurrNode,"");
isShowingConversation = false;
isShowingLine = false;
CurrNode = null;
lineIndex = -1;
CloseWindow();
}
void InvokeNodeStartEvent(ConversationNode node)
{
this.GetSystem<ConversationEventSystem>().InvokeNodeStartEvent(node.TreeId, node.NodeId);
}
void InvokeNodeEndEvent(ConversationNode node, string selectionTitle)
{
//Debug.Log(this.GetSystem<ConversationEventSystem>().IsNull());
this.GetSystem<ConversationEventSystem>().InvokeNodeEndEvent(node.TreeId, node.NodeId, selectionTitle);
}
public void RemoveNodeAgent(ConversationNodeAgent agent)
{
this.GetModel<ConversationModel>().RemoveChildAgent(agent.TreeId, agent.NodeId, agent.Title);
}
public void RemoveNodeAgent(int treeId, int nodeId,string agentTitle)
{
this.GetModel<ConversationModel>().RemoveChildAgent(treeId, nodeId, agentTitle);
}
protected override void OnInit()
{
}
public void DestroyWindow()
{
//todo
this.GetSystem<UIWindowSystem>().DestroyWindow(conversationWindow.gameObject);
}
}
对话事件管理:
public class ConversationEventSystem : AbstractSystem, IDisposable
{
Dictionary<Vector2, Action> conversationNodeStartEventDic;
Dictionary<Vector2, Action<string>> conversationNodeEndEventDic;
protected override void OnInit()
{
conversationNodeStartEventDic = new Dictionary<Vector2, Action>();
conversationNodeEndEventDic = new Dictionary<Vector2, Action<string>>();
RegisterInitialEvent();
}
void RegisterInitialEvent()
{
//初始事件
RegisterNodeStartEvent(0, 1, ()=>{Debug.Log("开始与商人对话");});
RegisterNodeStartEvent(1, 1, ()=>{Debug.Log("进入测试对话");});
RegisterNodeEndEvent(1, 2, (i)=>
{
switch(i)
{
case "选项1":
Debug.Log("执行选项1");
break;
case "选项2":
Debug.Log("执行选项2");
break;
case "选项3":
Debug.Log("执行选项3");
break;
}
});
RegisterNodeEndEvent(1, 3, (i)=>
{
switch(i)
{
case "返回选择":
Debug.Log("返回选择");
break;
}
});
RegisterNodeEndEvent(1, 4, (i)=>
{
switch(i)
{
case "结束对话":
Debug.Log("结束对话");
break;
case "返回选择":
Debug.Log("返回选择");
break;
}
});
RegisterNodeEndEvent(1, 5, (i)=>{Debug.Log("结束对话");});
}
public IUnRegister RegisterNodeStartEvent(int treeId, int nodeId, Action action)
{
Vector2 key = new Vector2(treeId, nodeId);
if(!conversationNodeStartEventDic.ContainsKey(key))
{
conversationNodeStartEventDic.Add(key, null);
}
conversationNodeStartEventDic[key]+=action;
return new CustomUnRegister(()=>
{
conversationNodeStartEventDic[key]-=action;
});
}
public IUnRegister RegisterNodeEndEvent(int treeId, int nodeId, Action<string> action)
{
Vector2 key = new Vector2(treeId, nodeId);
if(!conversationNodeEndEventDic.ContainsKey(key))
{
conversationNodeEndEventDic.Add(key, null);
}
conversationNodeEndEventDic[key]+=action;
return new CustomUnRegister(()=>
{
conversationNodeEndEventDic[key]-=action;
});
}
public void InvokeNodeStartEvent(int treeId, int nodeId)
{
Vector2 key = new Vector2(treeId, nodeId);
if(conversationNodeStartEventDic.ContainsKey(key))
{
conversationNodeStartEventDic[key]?.Invoke();
}
}
public void InvokeNodeEndEvent(int treeId, int nodeId, string selectionTitle)
{
Vector2 key = new Vector2(treeId, nodeId);
if(conversationNodeEndEventDic.ContainsKey(key))
{
conversationNodeEndEventDic[key]?.Invoke(selectionTitle);
}
}
public void Dispose()
{
conversationNodeStartEventDic.Clear();
conversationNodeStartEventDic = null;
conversationNodeEndEventDic.Clear();
conversationNodeEndEventDic = null;
}
}
Viewer
public class ConversationWindowUIView : UIWindowBase
{
public GameObject btnPrefab;
List<Button> buttons;
public Text talkerName;
public Text text;
public Transform btnGroup;
Action ClickEvent;
Action ShowLineOverEvent;
Action<ConversationNodeAgent> SelectionBtnClickEvent;
public void Init(Action clickEvent, Action showLineOverEvent, Action<ConversationNodeAgent> SelectionBtnClickEvent)
{
btnGroup.gameObject.SetActive(false);
talkerName.text = "";
text.text = "";
this.ClickEvent = clickEvent;
this.ShowLineOverEvent = showLineOverEvent;
this.SelectionBtnClickEvent = SelectionBtnClickEvent;
}
void Update()
{
if(Input.GetMouseButtonDown(0))
{
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit))
{
if(hit.collider.GetComponent<Button>()==null)
{
ClickEvent?.Invoke();
}
}
else
{
ClickEvent?.Invoke();
}
}
else
{
if(Input.GetKeyDown(KeyCode.Space))
{
ClickEvent?.Invoke();
}
}
}
public void SetName(string str)
{
talkerName.text = str;
}
public Tween ShowTextTween(string str)
{
text.text = "";
float duration = str.Length/10f;
Tween showTween = text.DOText(str, duration).OnStart(()=>
{
text.text = "";
})
.OnComplete(()=>
{
ShowLineOverEvent?.Invoke();
});
return showTween;
}
public void ShowAllText(string str)
{
text.text = str;
ShowLineOverEvent?.Invoke();
}
public void ShowSelection(List<ConversationNodeAgent> agent)
{
btnGroup.gameObject.SetActive(true);
Button[] btn = UIUtility.Instance.ShowBtnGroupList(btnGroup, btnPrefab, agent.Count);
for (int i = 0; i < btn.Length; i++)
{
int j = i;
btn[i].GetComponentInChildren<Text>().text = agent[i].Title;
btn[j].onClick.AddListener(() =>
{
SelectionBtnClickEvent?.Invoke(agent[j]);
btnGroup.gameObject.SetActive(false);
});
}
}
public override void BeforeClose()
{
ClickEvent = null;
ShowLineOverEvent= null;
SelectionBtnClickEvent= null;
}
}
其他:
public class Talker : FoxGameController
{
public int treeId;
public int nodeId;
bool isEnter;
void Start()
{
isEnter = false;
}
void Update()
{
if(isEnter)
{
if(Input.GetKeyDown(KeyCode.E))
{
if(!this.GetSystem<ConversationSystem>().isShowingConversation)
{
this.GetSystem<ConversationSystem>().StartConversation(treeId, nodeId);
}
}
}
}
void OnTriggerEnter2D(Collider2D coll)
{
if(coll.tag.Equals("Player"))
{
//todo:新建ui,button group,在此显示并添加按钮;
isEnter = true;
}
}
void OnTriggerExit2D(Collider2D coll)
{
if(coll.tag.Equals("Player"))
{
//todo:新建ui,button group,在此显示并添加按钮;
isEnter = false;
}
}
}
2023.12.3更新:修改个bug,转换节点时设置index从0改为-1,再用movenextline检查人名;删除agent时,从skipwhile改为使用where筛选,因为skipwhile不是遍历并跳过所有满足条件的项,而是while逻辑,跳过序列中的元素,直到某个元素不满足时就终止,对之后的元素没有进行判断。