// Saves nodes private bool SaveNodes(BTDataContainer btContainer, string fileName) { // If there are no connections then display error if (!edges.Any()) { EditorUtility.DisplayDialog("No connections", "Create some connections in your tree before saving!", "Ok"); return(false); } // Save node connections Edge[] connectedPorts = edges.Where(x => x.input.node != null).ToArray(); for (int i = 0; i < connectedPorts.Length; i++) { BTEditorNode outputNode = connectedPorts[i].output.node as BTEditorNode; BTEditorNode inputNode = connectedPorts[i].input.node as BTEditorNode; btContainer.nodeLinks.Add(new NodeLinkData { BaseNodeGuid = outputNode.GUID, PortName = connectedPorts[i].output.portName, TargetNodeGuid = inputNode.GUID }); } // Save individual node data foreach (BTEditorNode node in nodes) { NodeData temp = new NodeData { nodeTitle = node.title, nodeName = node.nodeName, GUID = node.GUID, Position = node.GetPosition().position, nodeType = (int)node.nodeType, topNode = node.topNode }; if (node.compositeInstance != null) { temp.compositeInstance = (Composite)SaveNode(node.title + node.GUID, node.compositeInstance, fileName); } else if (node.decoratorInstance != null) { temp.decoratorInstance = (Decorator)SaveNode(node.title + node.GUID, node.decoratorInstance, fileName); } else if (node.actionInstance != null) { temp.actionInstance = (Action)SaveNode(node.title + node.GUID, node.actionInstance, fileName); } btContainer.nodeData.Add(temp); } return(true); }
/// <summary> /// Called when a search tree entry is selected /// </summary> /// <param name="SearchTreeEntry"></param> /// <param name="context"></param> /// <returns></returns> public bool OnSelectEntry(SearchTreeEntry SearchTreeEntry, SearchWindowContext context) { // Get mouse world mosue position in editor window Vector2 worldMousePosition = _editorWindow.rootVisualElement.ChangeCoordinatesTo(_editorWindow.rootVisualElement.parent, context.screenMousePosition - _editorWindow.position.position); // Convert it to graphview coords to spawn nodes at mouse position Vector2 localMousePosition = _graphView.contentViewContainer.WorldToLocal(worldMousePosition); BTEditorNode tempNode = (BTEditorNode)SearchTreeEntry.userData; _graphView.CreateNode(tempNode.nodeName, tempNode.nodeName, tempNode.nodeType, localMousePosition); return(true); }
/// <summary> /// Adds a port to a target node /// </summary> /// <param name="targetNode"></param> /// <param name="overriddenPortName"></param> public void AddPort(BTEditorNode targetNode, string overriddenPortName = "") { // Generate port Port generatedPort = GeneratePort(targetNode, Direction.Output); // Removes default label from port in favour of editable labels Label oldLabel = generatedPort.contentContainer.Q <Label>("type"); generatedPort.contentContainer.Remove(oldLabel); // Adds default port name int outputPortCount = targetNode.outputContainer.Query("connector").ToList().Count; generatedPort.portName = outputPortCount.ToString(); // Check if default portname is overridden string portName = string.IsNullOrEmpty(overriddenPortName) ? outputPortCount.ToString() : overriddenPortName; // Adds name field TextField textField = new TextField { name = string.Empty, value = portName }; // textField.RegisterValueChangedCallback(evt => generatedPort.portName = evt.newValue); generatedPort.contentContainer.Add(new Label(" ")); generatedPort.contentContainer.Add(textField); // Adds delete port button Button deleteButton = new Button(() => RemovePort(targetNode, generatedPort)) { text = "X" }; generatedPort.contentContainer.Add(deleteButton); generatedPort.portName = portName; targetNode.outputContainer.Add(generatedPort); targetNode.RefreshPorts(); targetNode.RefreshExpandedState(); }
/// <summary> /// Returns list of child nodes based on node GUID /// </summary> /// <param name="nodeGUID"></param> /// <returns></returns> public List <BTEditorNode> GetChildNodes(string nodeGUID, string fileName) { // Update cache to contain current nodes SaveGraph(fileName); _containerCache = Resources.Load <BTDataContainer>(fileName); List <NodeLinkData> connections = _containerCache.nodeLinks.Where(x => x.BaseNodeGuid == nodeGUID).ToList(); // Get connections from active container cache List <BTEditorNode> childNodes = new List <BTEditorNode>(); // Loop through connections for a given node and find its child nodes via GUID matching for (int i = 0; i < connections.Count; i++) { string targetNodeGUID = connections[i].TargetNodeGuid; BTEditorNode targetNode = nodes.First(x => x.GUID == targetNodeGUID); childNodes.Add(targetNode); } return(childNodes); }
// Removes port from target node private void RemovePort(BTEditorNode targetNode, Port portToRemove) { // Find port in node that matches port to remove IEnumerable <Edge> targetEdge = edges.ToList().Where(x => x.output.portName == portToRemove.portName && x.output.node == portToRemove.node); // If no edges got added to the list only remove port if (!targetEdge.Any()) { targetNode.outputContainer.Remove(portToRemove); } else // If edges remove edges then remove port { // Get edge matching the above requirements and remove connections and then the port Edge edge = targetEdge.First(); edge.input.Disconnect(edge); RemoveElement(targetEdge.First()); targetNode.outputContainer.Remove(portToRemove); targetNode.RefreshPorts(); targetNode.RefreshExpandedState(); } }
// Generate nodes based on save data private void CreateNodes() { BTEditorNode tempNode = null; foreach (NodeData nodeData in _containerCache.nodeData) { // Generate node based on node data. We pass node position later so we can use zerovector while loading switch ((NodeTypes)nodeData.nodeType) { case NodeTypes.Composite: tempNode = _targetGraphView.GenerateNode(nodeData.nodeTitle, nodeData.nodeName, (NodeTypes)nodeData.nodeType, Vector2.zero, nodeData.topNode, nodeData.compositeInstance); tempNode.GUID = nodeData.GUID; break; case NodeTypes.Decorator: tempNode = _targetGraphView.GenerateNode(nodeData.nodeTitle, nodeData.nodeName, (NodeTypes)nodeData.nodeType, Vector2.zero, nodeData.topNode, nodeData.decoratorInstance); tempNode.GUID = nodeData.GUID; break; case NodeTypes.Action: tempNode = _targetGraphView.GenerateNode(nodeData.nodeTitle, nodeData.nodeName, (NodeTypes)nodeData.nodeType, Vector2.zero, nodeData.topNode, nodeData.actionInstance); tempNode.GUID = nodeData.GUID; break; default: break; } // Add ports to node based on node data. If it's a decorator node the port will be generated automatically so theres no need to add ports if (tempNode.nodeType != NodeTypes.Decorator) { List <NodeLinkData> nodePorts = _containerCache.nodeLinks.Where(x => x.BaseNodeGuid == nodeData.GUID).ToList(); nodePorts.ForEach(x => _targetGraphView.AddPort(tempNode, x.PortName)); } _targetGraphView.AddElement(tempNode); } }
/// <summary> /// Generates and returns an node instance without adding it to the editor window /// </summary> /// <param name="nodeTitle"></param> /// <param name="type"></param> /// <returns></returns> public BTEditorNode GenerateNode(string nodeTitle, string nodeName, NodeTypes type, Vector2 position, bool isTopNode = false, AbstractNode instance = null) { BTEditorNode node = null; switch (type) { case NodeTypes.Composite: node = GenerateCompositeNode(nodeTitle, nodeName, position, isTopNode, instance); break; case NodeTypes.Decorator: node = GenerateDecoratorNode(nodeTitle, nodeName, position, isTopNode, instance); break; case NodeTypes.Action: node = GenerateActionNode(nodeTitle, nodeName, position, isTopNode, instance); break; default: break; } return(node); }
// Generate a port private Port GeneratePort(BTEditorNode target, Direction portDir, Port.Capacity capacity = Port.Capacity.Single) { return(target.InstantiatePort(Orientation.Horizontal, portDir, capacity, typeof(float))); }
// Generate decorator node private BTEditorNode GenerateDecoratorNode(string nodeTitle, string name, Vector2 position, bool isTopNode = false, AbstractNode instance = null) { BTEditorNode node = new BTEditorNode { title = nodeTitle, nodeName = name, GUID = System.Guid.NewGuid().ToString(), nodeType = NodeTypes.Decorator, topNode = isTopNode }; // Create new instance if not loading from already existing behaviour tree if (instance != null) { node.decoratorInstance = instance as Decorator; } else { node.decoratorInstance = ScriptableObject.CreateInstance(name) as Decorator; } // Stash and remove old title and minimize button elements Label oldTitleLabel = node.titleContainer.Q <Label>("title-label"); node.titleContainer.Remove(oldTitleLabel); VisualElement oldTitleButton = node.titleContainer.Q <VisualElement>("title-button-container"); node.titleContainer.Remove(oldTitleButton); // Create and add text field for title input TextField textField = new TextField { name = string.Empty, value = node.title }; textField.RegisterValueChangedCallback(evt => node.title = evt.newValue); node.titleContainer.Add(textField); node.titleContainer.Add(oldTitleButton); // Add back minimize button in title container after adding title input field // Input/Output port node.inputContainer.Add(GeneratePort(node, Direction.Input, Port.Capacity.Multi)); node.outputContainer.Add(GeneratePort(node, Direction.Output, Port.Capacity.Multi)); // Object field for behaviour instance ObjectField objectField = new ObjectField(); objectField.objectType = typeof(ScriptableObject); objectField.value = node.decoratorInstance; node.mainContainer.Add(objectField); // Node state element Label nodeStateLabel = new Label { name = "node-state-label", text = node.decoratorInstance.NodeState.ToString() }; node.titleContainer.Add(nodeStateLabel); node.RefreshExpandedState(); node.RefreshPorts(); node.SetPosition(new Rect(position, defaultNodeSize)); return(node); }
// Generates entry point node at editor startup private BTEditorNode GenerateEntryPointNode(string nodeName) { // Instantiate new node BTEditorNode node = new BTEditorNode { title = nodeName, nodeName = "Selector", compositeInstance = (Composite)ScriptableObject.CreateInstance("Selector"), GUID = System.Guid.NewGuid().ToString(), nodeType = NodeTypes.Composite, topNode = true, }; // Stash and remove old title and minimize button elements Label oldTitleLabel = node.titleContainer.Q <Label>("title-label"); node.titleContainer.Remove(oldTitleLabel); VisualElement oldTitleButton = node.titleContainer.Q <VisualElement>("title-button-container"); node.titleContainer.Remove(oldTitleButton); // Create and add text field for title input TextField textField = new TextField { name = nodeName, value = "Top Node" }; textField.RegisterValueChangedCallback(evt => node.title = evt.newValue); node.titleContainer.Add(textField); node.titleContainer.Add(oldTitleButton); // Add back minixmize button in title container after adding title input field // Node state element Label nodeStateLabel = new Label { name = "node-state-label", text = node.compositeInstance.NodeState.ToString() }; node.titleContainer.Add(nodeStateLabel); // Instantiate add port button Button button = new Button(() => { AddPort(node); }); button.text = "New Child Behaviour"; node.titleContainer.Add(button); // Instance field ObjectField objectField = new ObjectField(); objectField.objectType = typeof(ScriptableObject); objectField.value = node.compositeInstance; node.mainContainer.Add(objectField); AddPort(node); AddPort(node); node.capabilities &= ~Capabilities.Movable; node.capabilities &= ~Capabilities.Deletable; // Refresh node node.RefreshExpandedState(); node.RefreshPorts(); node.SetPosition(new Rect(new Vector2(100, 200), defaultNodeSize)); return(node); }