Unity Graph View打造图形化对话编辑系统(四)

Unity Graph View打造图形化对话编辑系统(四)——视图类

目录

  • 一、效果展示及实现思路
  • 二、数据节点抽象
  • 三、UIBuilder构造EditorWindow
  • 四、实现EditorWindow各类视图类
  • 五、实现运行组件,让对话系统跑起来

视频效果演示

Graph View打造图形化对话编辑系统效果展示

最终源码先附在此

点击此处下载源码


DialogueTreeView

这个应该算是核心了吧,处理节点的编辑。关键有这么几处:

// 当视图内容改变时,比如用户建立了连线、删除了节点等等操作
private GraphViewChange OnGraphViewChanged(GraphViewChange graphViewChange)
{
	if (treeData != null)
	{
		if (graphViewChange.elementsToRemove != null)
		{
			graphViewChange.elementsToRemove.ForEach(x =>
			{
				if (x is NodeView node)
				{
					// 删除节点
					node.OnNodeSelected -= WhenNodeSelected;
					treeData.RemoveNode(node.nodeData);
				}
				else if (x is Edge edge)
				{
					// 删除连线
					NodeView parent = edge.output.node as NodeView;
					string key = parent.GetOutputPortIndex(edge.output);
					if( ! string.IsNullOrEmpty(key))
					{
						parent.nodeData.BreakNode(key);
					}
				}
			});
		}

		if( graphViewChange.edgesToCreate != null )
		{
			// 创建连线
			graphViewChange.edgesToCreate.ForEach(e =>
			{
				NodeView parent = e.output.node as NodeView;
				string key = parent.GetOutputPortIndex(e.output);
				if( ! string.IsNullOrWhiteSpace( key ))
				{
					NodeView child = e.input.node as NodeView;
					parent.nodeData.LinkNode(key, child.nodeData);
				}
			});
		}
	}
	return graphViewChange;
}

还有我们重新构造一下右键菜单,用来创建我们自己定义的节点:

public override void BuildContextualMenu(ContextualMenuPopulateEvent evt)
{
	if (treeData != null)
	{
		// 枚举所有的类型,这样当我们创建新的类型时,原则上可以不用改这里的代码。
		foreach (Type t in TypeCache.GetTypesDerivedFrom<DialogNodeBase>())
		{
			string menuItem;
			if (t == typeof(StartNode))
			{
				// 一棵树有且只有一个开始节点
				if (treeData.startNode != null)
					return;

				menuItem = "创建[开始]节点";
			}
			else if (t == typeof(SelectorBranch))
				menuItem = "创建[玩家]节点";
			else if (t == typeof(RandomBranch))
				menuItem = "创建[NPC]节点";
			else
				menuItem = $"创建[{t.name}]节点";

			evt.menu.AppendAction( menuItem, x =>
			{
				DialogNodeBase node = treeData.CreateNode(t);
				BuildNodeView(node);
			});
		}
	}
}

还有关键的一点,我们需要知道节点中哪一些port是兼容的,也就是可以连线的。由于我们对话系统中,只有一种连线,不涉及不同的数据类型的连线,因此这里就比较简单了:

public override List<Port> GetCompatiblePorts(Port startPort, NodeAdapter nodeAdapter)
{
	// 相同方向的连线不兼容,就是说一个节点的输出必须接到另一个节点的输入上,而不能输出接输出,或输入接输入。
	// 自己的输出不能接自己的输入
	return ports.ToList().Where(endPorts =>
		endPorts.direction != startPort.direction &&
		endPorts.node != startPort.node &&
		endPorts.portType == startPort.portType
	).ToList();
}

NodeView

这个当然是用来处理节点的了,也是非常关键的类。
比较关键的是创建接口了:

private void CreateOutputPorts()
{
	if (nodeData != null && nodeData.outputItems != null)
	{
		foreach (var item in nodeData.outputItems)
		{
			Port o = InstantiatePort(Orientation.Horizontal, Direction.Output, Port.Capacity.Single, typeof(bool));
			o.name = "OutPut";
			o.portName = item;
			o.portColor = Color.green;
			outputPorts.Add(item, o);
			outputContainer.Add(o);
		}
	}
}

private void CreateInputPorts()
{
	if( ! ( nodeData is StartNode ))
	{
		Port i = InstantiatePort(Orientation.Horizontal, Direction.Input, Port.Capacity.Multi, typeof(bool));
		i.name = "InPut";
		i.portName = "";
		i.portColor = Color.green;
		inputPort = i;
		inputContainer.Add(i);
	}
}

事实上,这里创建接口时,InstantiatePort的最后一个参数,是什么类型无所谓的。甚至可以是自定义的类型,只要保持一致即可。如果将来有不同的连线类别,那这里就可以来区分一下。
还有处理位置信息的函数:

// 将位置信息记录起来,以便下次加载时,将节点设置到正确的位置上
public override void SetPosition(Rect newPos)
{
    base.SetPosition(newPos);
    nodeData.position = newPos.min;
}

InspectorView

整个属性面板是最简单的了:

public class InspectorView : VisualElement
{
    public new class UxmlFactory : UxmlFactory<InspectorView, VisualElement.UxmlTraits> { }

    private Editor editor = null;
    public void UpdateSelection(NodeView node)
    {
        Clear();
        if (editor != null)
            Object.DestroyImmediate(editor);
        editor = Editor.CreateEditor(node.nodeData);
        IMGUIContainer container = new IMGUIContainer(() => editor.OnInspectorGUI());
        
        Add(container);
    }
}

除此之外

上面列出的都是 一些关键性的代码,除此之外,就是各种消息的处理了。比如当节点被选择时,在inspector面板中显示属性,其实就是调用(InspectorView.UpdateSelection,如上已列出),当节点信息被改变时,重新构造一下NodeView里面的接口。

你可能感兴趣的:(Unity,Graph,GraphView,Node,Editor,Node,Graph,对话系统)