E Story 故事编辑器开发笔记 #4 创建中间派生节点

设置输出端口所需数据结构

创建选项数据类

  1. 打开Data文件夹,创建C#文件,命名为ChoiceData
  2. 打开ChoiceData.cs,将内容修改如下:
C#
using System;
using UnityEngine;

namespace E.Story
{
    // 选项数据
    [Serializable]
    public class ChoiceData
    {
        [SerializeField] private string text;
        [SerializeField] private string nextNodeID;

        // 选项文本
        public string Text { get => text; set => text = value; }

        // 节点GUID
        public string NextNodeID { get => nextNodeID; set => nextNodeID = value; }

        // 构造器
        public ChoiceData(string text)
        {
            this.text = text;
        }
    }
}

修改基类节点

  1. 打开BaseNode.cs,新增以下属性:
C#
// 选项数据列表
public List ChoiceDatas { get; set; }
  1. 修改以下方法:
C#
// 初始化节点
public virtual void Init(StoryGraphView graphView, string title, Vector2 position)
{
    /* ... 此处代码已省略 ... */

    ChoiceDatas = new() { new("下个节点") };
}

// 绘制输出容器
public virtual void DrawOutputContainer()
{
    // 遍历选项视图列表,创建对应端口
    for (int i = 0; i < ChoiceDatas.Count; i++)
    {
        ChoiceData choiceData = ChoiceDatas[i];
        output = this.CreatePort(choiceData.Text);
        output.userData = choiceData;
        outputContainer.Add(output);
    }
}

创建中间派生类

创建单进单出节点类

  1. 打卡UI Node文件夹,创建C#文件,命名为SingleInSingleOutNode
  2. 打开SingleInSingleOutNode.cs,将内容修改如下:
C#
using UnityEditor.Experimental.GraphView;
using UnityEngine;

namespace E.Story
{
    // 单进单出节点
    public class SingleInSingleOutNode : BaseNode
    {
        public override void Init(StoryGraphView graphView, string title, Vector2 position)
        {
            base.Init(graphView, title,  position);

            // 重设属性默认值
            Type = NodeType.SingleInSingleOut;
        }
    }
}

创建单进多出节点类

  1. 继续创建C#文件,命名为SingleInMultiOutNode
  2. 打开SingleInMultiOutNode.cs,将内容修改如下:
C#
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.UIElements;

namespace E.Story
{
    // 单进多出节点
    public class SingleInMultiOutNode : BaseNode
    {
        public override void Init(StoryGraphView graphView, string title, Vector2 position)
        {
            base.Init(graphView, title, position);

            // 重设属性默认值
            Type = NodeType.SingleInMultiOut;

            // 清除并添加默认选项
            ChoiceDatas.Clear();
            ChoiceDatas.Add(new("选项文本"));
        }

        protected override void DrawOutputContainer()
        {
            // 遍历选项视图列表,创建对应端口
            for (int i = 0; i < ChoiceDatas.Count; i++)
            {
                ChoiceData choiceData = ChoiceDatas[i];
                output = CreateOutputPort(choiceData);
                outputContainer.Add(output);
            }
        }

        protected override void DrawExtensionContainer()
        {
            // 创建添加选项按钮
            Button btnAdd = ElementUtility.CreateButton("添加选项", () =>
            {
                ChoiceData choiceData = new("选项文本");
                ChoiceDatas.Add(choiceData);

                output = CreateOutputPort(choiceData);
                outputContainer.Add(output);
            });

            // 放置UI元素
            extensionContainer.Add(btnAdd);

            RefreshExpandedState();
        }

        // 创建选项端口
        private Port CreateOutputPort(object userData)
        {
            // 获取选项数据
            ChoiceData choiceData = (ChoiceData)userData;

            // 创建输出接口
            Port outputPort = this.CreatePort();
            // 创建删除按钮
            Button btnDelete = ElementUtility.CreateButton("X", () =>
            {
                if(ChoiceDatas.Count == 1)
                {
                    Debug.LogWarning("需至少保留一条选项");
                    return;
                }
                
                ChoiceDatas.Remove(choiceData);
                graphView.RemoveElement(outputPort);
            });
            // 创建选项文本框
            TextField tfdChoice = ElementUtility.CreateTextField(choiceData.Text, null, callback =>
            {
                choiceData.Text = callback.newValue;
            });

            // 放置UI元素
            outputPort.Add(btnDelete);
            outputPort.Add(tfdChoice);

            return outputPort;
        }
    }
}

创建单进零出节点类

  1. 继续创建C#文件,命名为SingleInZeroOutNode
  2. 打开SingleInZeroOutNode.cs,将内容修改如下:
C#
using UnityEngine;

namespace E.Story
{
    // 单进零出节点
    public class SingleInZeroOutNode : BaseNode
    {
        public override void Init(StoryGraphView graphView, string title, Vector2 position)
        {
            base.Init(graphView, title, position);

            // 重设属性默认值
            Type = NodeType.SingleInZeroOut;
            
            // 清除默认选项
            ChoiceDatas.Clear();
        }

        public override void Draw()
        {
            DrawMainContainer();
            DrawTitleContainer();
            DrawTitleButtonContainer();
            DrawTopContainer();
            DrawInputContainer();
            DrawExtensionContainer();
        }
    }
}

创建零进单出节点类

  1. 继续创建C#文件,命名为ZeroInSingleOutNode
  2. 打开ZeroInSingleOutNode.cs,将内容修改如下:
C#
using UnityEngine;

namespace E.Story
{
    // 零进单出节点
    public class ZeroInSingleOutNode : BaseNode
    {
        public override void Init(StoryGraphView graphView, string title, Vector2 position)
        {
            base.Init(graphView, title, position);

            // 重设属性默认值
            Type = NodeType.ZeroInSingleOut;
        }

        public override void Draw()
        {
            DrawMainContainer();
            DrawTitleContainer();
            DrawTitleButtonContainer();
            DrawTopContainer();
            DrawOutputContainer();
            DrawExtensionContainer();
        }
    }
}

扩展上下文菜单

打开StoryGraphView.cs,修改以下方法:

C#
// 创建节点
public BaseNode CreateNode(string title, NodeType type, Vector2 position)
{
    // 获取类型
    Type nodeType = Type.GetType($"E.Story.{type}Node");

    // 创建对应节点
    BaseNode node = Activator.CreateInstance(nodeType) as BaseNode;
    node.Init(this, title, position);
    node.Draw();
    AddElement(node);

    return node;
}

// 添加默认节点
private void AddDefaultNodes()
{
    CreateNode("节点", NodeType.Base, new Vector2(200, 200));
}

// 构建上下文菜单
public override void BuildContextualMenu(ContextualMenuPopulateEvent evt)
{
    base.BuildContextualMenu(evt);

    // 点击按钮实现添加新节点
    evt.menu.AppendAction("添加节点", action =>
    {
        CreateNode("节点", NodeType.Base, Vector2.zero);
    });
    evt.menu.AppendAction("添加单进单出节点", action =>
    {
        CreateNode("单进单出节点", NodeType.SingleInSingleOut, Vector2.zero);
    });
    evt.menu.AppendAction("添加单进多出节点", action =>
    {
        CreateNode("单进多出节点", NodeType.SingleInMultiOut, Vector2.zero);
    });
    evt.menu.AppendAction("添加单进零出节点", action =>
    {
        CreateNode("单进零出节点", NodeType.SingleInZeroOut, Vector2.zero);
    });
    evt.menu.AppendAction("添加零进单出节点", action =>
    {
        CreateNode("零进单出节点", NodeType.ZeroInSingleOut, Vector2.zero);
    });
}

测试效果

打开插件窗口,最终效果如下:

相关链接

留下评论