Different Movement of Nodes for Dialogue Editor

So, I wasn’t super happy with how the nodes and movement worked - it did not feel intuitive - and I was struggling. The main problem I was facing was: editing dialogue, doesn’t allow you to click and drag to select multiple words, etc… trying to click and drag in the text box in any way shape or form forces you into moving the node. I created an additional button which toggles on the movement of a node - so you can toggle it on then ANYWHERE you drag, will move the node until you toggle it off. It doesn’t work as perfectly as I would have hoped - but if you want to try it out, go for it. I also incorporated some other suggestions - such as dynamically resizing the grid based on where you position nodes, so your editor will get bigger as you add more nodes, I also color coded buttons etc. I tried to follow a suggestion for wrapping text - but I am striking out there.

using UnityEngine;
using UnityEditor;
using UnityEditor.Callbacks;
using System;

namespace RPG.Dialogue.Editor
{
    public class DialogueEditor : EditorWindow
    {
        Dialogue selectedDialogue = null;
        Vector2 scrollPosition;
        [NonSerialized] GUIStyle nodeStyle;
        [NonSerialized] DialogueNode draggingNode = null;
        [NonSerialized] Vector2 mouseOffset;
        [NonSerialized] DialogueNode creatingNode = null;
        [NonSerialized] DialogueNode deletingNode = null;
        [NonSerialized] DialogueNode linkingNode = null;
        [NonSerialized] DialogueNode dragNodeToggle = null;

        const float backgroundSize = 50f;

        [MenuItem("Window/Dialogue Editor")]
        public static void ShowEditorWindow()
        {
            GetWindow(typeof(DialogueEditor), false, "Dialogue Editor");
        }

        [OnOpenAsset(1)]
        public static bool OpenAsset(int instanceID, int line)
        {
            Dialogue dialogueAsset = EditorUtility.InstanceIDToObject(instanceID) as Dialogue;
            if (dialogueAsset != null)
            {
                ShowEditorWindow();
                return true;
            }
            
            return false;
        }

        private void OnEnable()
        {
            NodeStyles();
            selectedDialogue = Selection.activeObject as Dialogue;
        }

        private void NodeStyles()
        {
            nodeStyle = new GUIStyle();
            nodeStyle.normal.background = EditorGUIUtility.Load("node0") as Texture2D;
            nodeStyle.padding = new RectOffset(20, 20, 20, 20);
            nodeStyle.border = new RectOffset(12, 12, 12, 12);
            nodeStyle.wordWrap = true;
        }

        private void OnSelectionChange()
        {
            Dialogue dialogueAsset = Selection.activeObject as Dialogue;
            if (dialogueAsset != null)
            {
                selectedDialogue = dialogueAsset;
                Repaint();
            }
        }

        private void OnGUI()
        {
            if (selectedDialogue == null)
            {
                EditorGUILayout.LabelField("No Dialogue Selected.", EditorStyles.boldLabel);
            }
            else
            {
                ProcessEvents();
                EditorGUILayout.LabelField(selectedDialogue.name, EditorStyles.boldLabel);
                scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
                Rect bounds = selectedDialogue.CalculateNeededWindowSize();
                Rect canvas = GUILayoutUtility.GetRect(bounds.width, bounds.height);
                Texture2D backgroundTexture = Resources.Load("background") as Texture2D;
                Rect textureCoordinates = new Rect(0, 0, bounds.height / backgroundSize, bounds.width / backgroundSize);
                GUI.DrawTextureWithTexCoords(canvas, backgroundTexture, textureCoordinates);

                foreach (DialogueNode node in selectedDialogue.GetAllNodes())
                {
                    DrawConnections(node);
                }

                foreach (DialogueNode node in selectedDialogue.GetAllNodes())
                {
                    DrawNode(node);
                }

                EditorGUILayout.EndScrollView();

                if (creatingNode != null)
                {
                    Undo.RecordObject(selectedDialogue, "Added Dialogue Node");
                    selectedDialogue.CreateNode(creatingNode);
                    creatingNode = null;
                }
                if (deletingNode != null)
                {
                    Undo.RecordObject(selectedDialogue, "Deleted Dialogue Node");
                    selectedDialogue.DeleteNode(deletingNode);
                    deletingNode = null;
                }
            }
        }

        private void ProcessEvents()
        {
            if (Event.current.type == EventType.MouseDown && draggingNode == null)
            {
                draggingNode = dragNodeToggle;
                if (draggingNode != null)
                {
                    mouseOffset = draggingNode.position.position - Event.current.mousePosition;
                }
                else
                {
                    mouseOffset = Event.current.mousePosition + scrollPosition;
                }
            }
            else if (Event.current.type == EventType.MouseDrag && draggingNode != null)
            {
                Undo.RecordObject(selectedDialogue, "Move Dialogue Node");
                draggingNode.position.position = Event.current.mousePosition + mouseOffset;
                GUI.FocusControl(null);
                GUI.changed = true;
            }
            else if (Event.current.type == EventType.MouseDrag && GetNodeAtPoint(Event.current.mousePosition) == null)
            {
                scrollPosition = mouseOffset - Event.current.mousePosition;
                GUI.FocusControl(null);
                GUI.changed = true;
            }
            else if (Event.current.type == EventType.MouseUp && draggingNode != null)
            {
                draggingNode = null;
            }
        }

        private DialogueNode GetNodeAtPoint(Vector2 mousePosition)
        {
            DialogueNode foundNode = null;
            foreach (DialogueNode node in selectedDialogue.GetAllNodes())
            {
                if (node.position.Contains(mousePosition))
                {
                    foundNode = node;
                }
            }
            return foundNode;
        }

        private void DrawNode(DialogueNode node)
        {
            GUILayout.BeginArea(node.position, nodeStyle);
            EditorGUI.BeginChangeCheck();

            MoveNodeButton(node);

            string newText = EditorGUILayout.TextArea(node.text, GUILayout.Height(125));

            if (EditorGUI.EndChangeCheck())
            {
                Undo.RecordObject(selectedDialogue, "Update Dialogue Text");
                node.text = newText;
            }

            GUILayout.BeginHorizontal();

            if (GUILayout.Button("+"))
            {
                creatingNode = node;
            }

            DrawLinkButtons(node);

            if (GUILayout.Button("-"))
            {
                deletingNode = node;
            }

            GUILayout.EndHorizontal();

            GUILayout.EndArea();
        }

        private void MoveNodeButton(DialogueNode node)
        {
            if (dragNodeToggle == null)
            {
                if (GUILayout.Button("===== Move ====="))
                {
                    dragNodeToggle = node;
                }
            }
            else 
            {
                if (dragNodeToggle == node)
                {
                    GUI.color = Color.red;
                    if (GUILayout.Button("===== Stop ====="))
                    {
                        dragNodeToggle = null;
                    }
                    GUI.color = Color.white;
                }
                else if (dragNodeToggle != null)
                {
                    if (GUILayout.Button("================"))
                    {
                        dragNodeToggle = null;
                    }
                }
            }
        }

        private void DrawLinkButtons(DialogueNode node)
        {
            if (linkingNode == null)
            {
                if (GUILayout.Button("Link"))
                {
                    linkingNode = node;
                }
            }
            else
            {
                if (linkingNode == node)
                {
                    if (GUILayout.Button("Cancel"))
                    {
                        linkingNode = null;
                    }
                }
                else if (linkingNode.children.Contains(node.uniqueID))
                {
                    GUI.color = Color.red;
                    if (GUILayout.Button("Unlink"))
                    {
                        Undo.RecordObject(selectedDialogue, "Remove Child Dialogue");
                        linkingNode.children.Remove(node.uniqueID);
                        linkingNode = null;
                    }
                    GUI.color = Color.white;
                }
                else
                {
                    GUI.color = Color.green;
                    if (GUILayout.Button("Child"))
                    {
                        Undo.RecordObject(selectedDialogue, "Add Child Dialogue");
                        linkingNode.children.Add(node.uniqueID);
                        linkingNode = null;
                    }
                    GUI.color = Color.white;
                }
            }
        }

        private void DrawConnections(DialogueNode node)
        {
            Vector3 startPosition = node.position.center;

            foreach (DialogueNode childNode in selectedDialogue.GetAllChildren(node))
            {

                Vector3 endPosition = childNode.position.center;
                Vector3 controlPointOffset = endPosition - startPosition;
                controlPointOffset.y = 0f;
                controlPointOffset.x *= 0.9f;
                Handles.DrawBezier(
                    startPosition, 
                    endPosition, 
                    startPosition + controlPointOffset, 
                    endPosition - controlPointOffset, 
                    Color.white, null, 4f);
            }
        }
    }
}
1 Like

Well done! I like seeing what others do with the editor to make it handle the features they want!

Privacy & Terms