使用Xnode编写的简易对话系统

使用Xnode试着做了简单的对话系统

可视化了看起来是挺方便的,就是节点多了窗口会卡…

运行效果预览

需要的插件

  • Xnode

    用于绘制节点

  • Odin

    用于定制inspector窗口

自定义节点

首先要新建一个Graph脚本

[CreateAssetMenu(menuName = "Graph/对话图")]
public class DialogueGraph : NodeGraph {
    
    
   
}

然后我们需要开始结束,普通对话,选择分支,触发事件这些最基本的功能

所以分别写以下脚本用来定义节点

对话框

public class DialogueNode : Node {
    
    
    [LabelText("说话人")] public string speaker;
    [PreviewField(Alignment = ObjectFieldAlignment.Left), LabelText("头像")]
    public Sprite head;
    [TextArea, LabelText("说话内容")] public List<string> contents;
    [Input(ShowBackingValue.Never), LabelText("上一段")] public string pre;

    [LabelText("下一个是")] public NextType nextType;

    [ShowIf("nextType", NextType.Dialogue), Output, LabelText("下一段对话")]
    public DialogueNode nextDialogue;
    [ShowIf("nextType", NextType.Branch), Output, LabelText("下一段分支")]
    public BranchNode nextBranch;
    [ShowIf("nextType", NextType.Flag), Output, LabelText("下一段标记")]
    public FlagNode nextFlag;
    [Output, LabelText("触发事件")] public EventNode trigger;
    //下一个节点类型
    public enum NextType {
    
    
        [LabelText("对话框")] Dialogue,
        [LabelText("分支框")] Branch,
        [LabelText("标记框")] Flag
    }
    //类型与名字存起来 对连接进行限制时使用
    private Dictionary<NextType, string> singleDt = new Dictionary<NextType, string>(){
    
    
        {
    
    NextType.Dialogue, nameof(nextDialogue)},
        {
    
    NextType.Branch, nameof(nextBranch)},
        {
    
    NextType.Flag, nameof(nextFlag)}
    };

    protected override void Init() {
    
    
        base.Init();
    }

    public override object GetValue(NodePort port) {
    
    
        return null;
    }

    //当值更新时 (编辑器下)
    private void OnValidate() {
    
    
        //切换下一个类型的选项时 对所连接的节点进行限制
        foreach (var s in singleDt) {
    
    
            if (nextType!=s.Key) {
    
    
                GetPort(s.Value).ClearConnections();
            }
        }
    }

    public override void OnCreateConnection(NodePort from, NodePort to) {
    
    
        //限定连接节点类型
        if (Outputs.Contains(from)) {
    
    
            if (from.ValueType != to.node.GetType()) {
    
    
                Debug.LogError("不能将" + from.ValueType + "端口连接到" + to.node.GetType() + "节点!");
                GetPort(from.fieldName).Disconnect(to);
            }
        }
    }
}

示例

分支框

public class BranchNode : Node {
    
    
    [LabelText("提问人")]
    public string speaker;
    [PreviewField(Alignment = ObjectFieldAlignment.Left),LabelText("头像")]
    public Sprite head;
    [TextArea, LabelText("问题")] public string question;
    [Input(ShowBackingValue.Never), LabelText("上一段")] public string pre;
    [Output(dynamicPortList = true), LabelText("分支选项"), TextArea]
    public List<string> branchs;

    public override object GetValue(NodePort port) {
    
    
        return null; // Replace this
    }
}

示例

标记框和事件框

其实这俩可以写在一起的,但是懒得改了

public class FlagNode : Node {
    
    
    [LabelText("节点类型")]
    public FlagNodeType flagType;
    public enum FlagNodeType {
    
    
        Start,End
    }
    [Input((ShowBackingValue.Never)),ShowIf("flagType",FlagNodeType.End)]
    public string pre;
    [Output(),LabelText("下一段"),ShowIf("flagType",FlagNodeType.Start)] public string next;

    public override object GetValue(NodePort port) {
    
    
        return null;
    }
}
public class EventNode : Node {
    
    
    [Input(ShowBackingValue.Never), LabelText("触发对象")] public string triggerObj;
    [LabelText("事件名称")] public string eventName;

    public override object GetValue(NodePort port) {
    
    
        return null; // Replace this
    }
}

对话管理

首先写一些扩展方法

public static class NodeTool {
    
    
   /// <summary>
   /// 获取端口连接节点
   /// </summary>
   /// <param name="node"></param>
   /// <param name="fieldName"></param>
   /// <returns></returns>
   public static Node GetNodeByField(this Node node, string fieldName) {
    
    
      if (!node.HasPort(fieldName)) {
    
    
         Debug.LogWarning("不存在 "+fieldName+" 端口!");
         return null;
      }
      var port = node.GetPort(fieldName);
      if (!port.IsConnected) {
    
    
         Debug.LogWarning( fieldName+" 端口未连接!");
         return null;
      }
      return port.Connection.node;
   }
   /// <summary>
   /// 获取端口属性的值
   /// </summary>
   /// <param name="node"></param>
   /// <param name="fieldName"></param>
   /// <returns></returns>
   public static object GetValuesByField(this Node node,string fieldName) {
    
    
      return node.GetType().GetField(fieldName).GetValue(node);
   }
}

原理就是获取结点然后依次解析其中的内容,然后更新UI

public class DialogueManager : MonoBehaviour {
    
    
   public GameObject dialogueUi;

   public DialogueGraph dialogueGraph;

   private Text content;
   private Text speaker;
   private Image head;
   [ShowInInspector] private Node currentNode;
   [ShowInInspector] private List<string> contentList = new List<string>();
   [ShowInInspector] private List<Button> branchBtns = new List<Button>();
   public GameObject branchPb;

   private void Awake() {
    
    
      content = dialogueUi.transform.Find("Content").GetComponent<Text>();
      speaker = dialogueUi.transform.Find("Speaker").GetComponent<Text>();
      head = dialogueUi.transform.Find("Head").GetComponent<Image>();
   }

   void Start() {
    
    
      dialogueUi.SetActive(false);
   }

   void Update() {
    
    
      if (Input.GetKeyDown(KeyCode.A)) {
    
    
      }
      if (Input.GetKeyDown(KeyCode.E)) {
    
    
         if (currentNode == null) {
    
    
            dialogueUi.SetActive(true);
            currentNode = dialogueGraph.nodes[0].GetOutputPort("next").Connection.node;
            Debug.Log(currentNode);
            UpdateDialogueUi(currentNode);
         }
         else if (currentNode.GetType() == typeof(DialogueNode)) {
    
    
            ShowContent();
         }
      }
   }

   #region "对话系统逻辑部分"

   private void UpdateDialogueUi(Node current) {
    
    
      if (current.GetType() == typeof(DialogueNode)) {
    
    
         var node = current as DialogueNode;
         if (node != null) {
    
    
            contentList.AddFromList(node.contents);
            content.text = contentList[0];
            speaker.text = node.speaker;
            head.sprite = node.head;
         }
      }
      else if (current.GetType() == typeof(BranchNode)) {
    
    
         var branchNode = currentNode as BranchNode;
         if (branchNode != null) {
    
    
            contentList.Add(branchNode.question);
            speaker.text = branchNode.speaker;
            head.sprite = branchNode.head;
         }
      }
   }

   /// <summary>
   /// 显示对话内容
   /// </summary>
   void ShowContent() {
    
    
      if (contentList.Count > 0) {
    
    
         contentList.RemoveAt(0);
         if (contentList.Count == 0) {
    
    
            foreach (var connection in currentNode.GetOutputPort("trigger").GetConnections()) {
    
    
               TriggerEvent(connection.node as EventNode);
            }
            switch (currentNode.GetValuesByField("nextType")) {
    
    
               case DialogueNode.NextType.Dialogue:
                  Debug.Log("进入对话框节点");
                  currentNode = currentNode.GetNodeByField("nextDialogue");
                  var node = currentNode as DialogueNode;
                  if (node != null) {
    
    
                     contentList.AddFromList(node.contents);
                     speaker.text = node.speaker;
                     head.sprite = node.head;
                  }
                  break;
               case DialogueNode.NextType.Branch:
                  Debug.Log("进入分支框节点");
                  currentNode = currentNode.GetNodeByField("nextBranch");
                  UpdateDialogueUi(currentNode);
                  AddBranchClick(currentNode as BranchNode);
                  break;
               case DialogueNode.NextType.Flag:
                  Debug.Log("进入标记框节点");
                  currentNode = currentNode.GetNodeByField("nextFlag");
                  FlagNode flagNode = currentNode as FlagNode;
                  if (flagNode != null && flagNode.flagType == FlagNode.FlagNodeType.End) {
    
    
                     Debug.Log("对话流程结束!");  
                     dialogueUi.SetActive(false);
                  }
                  break;
            }
         }
         if (contentList.Count > 0) {
    
    
            content.text = contentList[0];
         }
      }
   }

   /// <summary>
   /// 触发对话框连接的事件
   /// </summary>
   /// <param name="node"></param>
   private void TriggerEvent(EventNode node) {
    
    
      Debug.Log("触发事件:" + node.eventName);
   }

   void AddBranchClick(BranchNode node) {
    
    
      for (int i = 0; i < node.branchs.Count; i++) {
    
    
         var branchPort = node.GetOutputPort("branchs " + i);
         var text = node.branchs[i];
         Debug.Log("增加" + text + "分支");
         var btn = Instantiate(branchPb, dialogueUi.transform.Find("Select").transform, false)
            .GetComponent<Button>();
         branchBtns.Add(btn);
         btn.GetComponentInChildren<Text>().text = text;
         if (branchPort.IsConnected) {
    
    
            var i1 = i;
            btn.onClick.AddListener(delegate {
    
    
               foreach (var connection in branchPort.GetConnections()) {
    
    
                  if (connection.node.GetType() == typeof(EventNode)) {
    
    
                     TriggerEvent(connection.node as EventNode);
                  }
                  if (connection.node.GetType() == typeof(DialogueNode)) {
    
    
                     Debug.Log("点击分支" + i1 + "进入对话框节点");
                     currentNode = connection.node;
                     contentList.RemoveAt(0);
                     UpdateDialogueUi(currentNode);
                  }
               }
               //清楚所有按钮
               for (int j = branchBtns.Count - 1; j >= 0; j--) {
    
    
                  Destroy(branchBtns[j].gameObject);
                  branchBtns.Remove(branchBtns[j]);
               }
            });
         }
      }
   }

   #endregion
}

UI层级与文件

最后将脚本挂到物体上即可

需要的可以直接在这下载packagehttps://legroft.lanzous.com/iH0Mqoy1q8d

猜你喜欢

转载自blog.csdn.net/qq_25969985/article/details/116550032
今日推荐