It broke here and i don't know why

I don’t know exactly what happen, but like the title says everything stop working as it should here, regarding the Dialogue editor.I will show the step by steps now of what’s happening.

So i created the following dialogue file.

From here i create a another dialogue:

Now i go back and look at the original file.

It still works, but lets hook up my dialogue to the PlayerConversant and see what happens now.
Without being hook up to the PlayerConversant

Now i hook it up

Run the game and it throws this error

How the Dialogue had nodes and childs, let’s look at it again.

… HOW!? My code is not that different to what’s done in the class, here is what it does in the PlayerConversant

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace RPG.Dialogue{
    public class PlayerConversant : MonoBehaviour{

        // CONFIG
        [SerializeField] Dialogue currentDialogue;

        // PUBLIC
        public string GetText(){

            if(currentDialogue == null) return "";

            return currentDialogue.GetRootNode().GetText();
        }

    }
}

And here is what it does the DialogueUI:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using RPG.Dialogue;
using TMPro;

namespace RPG.UI{
    public class DialogueUi : MonoBehaviour{
        PlayerConversant playerConversant;
        [SerializeField] TextMeshProUGUI AIText;

        void Start(){
            playerConversant = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerConversant>();
            AIText.text = playerConversant.GetText();
        }

        void Update()
        {

        }
    }
}

I have no idea of what’s going on.

Let’s take a look at your Dialogue.cs and DialogueEditor.cs

Of course, im passing now my Dialogue.cs:

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEditor;
using UnityEngine;

namespace RPG.Dialogue{
    [CreateAssetMenu(fileName = "New Dialogue", menuName = "RPG/Dialogue")]
    public class Dialogue : ScriptableObject, ISerializationCallbackReceiver{

        public Vector2 size = new Vector2(7000, 1000);
        Dictionary<string, DialogueNode> nodeLookUp = new Dictionary<string, DialogueNode>();
        [NonSerialized] List<DialogueNode> nodes = new List<DialogueNode>();
        [NonSerialized] Vector2 newNodeOffset = new Vector2(500, 0);
        
        

        // PUBLIC
        public IEnumerable<DialogueNode> GetAllNodes(){
            return nodes;
        }
        public DialogueNode GetRootNode(){
            return nodes[0];
        }

        public IEnumerable<DialogueNode> GetAllChildren(DialogueNode parentNode){

            foreach (string child in parentNode.GetChildren())
                if (nodeLookUp.ContainsKey(child)) yield return nodeLookUp[child];
        }

        public void OnBeforeSerialize(){
#if UNITY_EDITOR
            if (nodes.Count == 0){
                DialogueNode child = MakeNode(null);
                AddNode(child);
            }

            if (AssetDatabase.GetAssetPath(this) == "") return;

            foreach(DialogueNode node in GetAllNodes()){

                if(AssetDatabase.GetAssetPath(node) == "") AssetDatabase.AddObjectToAsset(node, this);
            }
#endif
        }

        public void OnAfterDeserialize(){
        }

        // UNITY EDITOR
#if UNITY_EDITOR
        public void CreateNode(DialogueNode parent){

            DialogueNode newChild = MakeNode(parent);

            Undo.RegisterCreatedObjectUndo(newChild, "New node created");
            Undo.RecordObject(this, "New Dialogue Node");

            AddNode(newChild);
        }

        

        public void DeleteNode(DialogueNode toDelete){

            Undo.RecordObject(this, "Delete Dialogue Node");
            nodes.Remove(toDelete);
            CleanChildren(toDelete);
            Undo.DestroyObjectImmediate(toDelete);

            OnValidate();
        }

        private DialogueNode MakeNode(DialogueNode parent){
            DialogueNode newChild = CreateInstance<DialogueNode>();

            newChild.name = Guid.NewGuid().ToString();


            if (parent != null){

                parent.AddChild(newChild.name);
                newChild.SetPlayerSpeaking(!parent.IsPlayerSpeaking());
                newChild.SetPosition(parent.GetRect().position + newNodeOffset);
            }

            
            return newChild;
        }

        private void AddNode(DialogueNode newChild){
            nodes.Add(newChild);
            OnValidate();
        }

        private void CleanChildren(DialogueNode toDelete){

            foreach(DialogueNode node in GetAllNodes()) node.RemoveChild(toDelete.name);
        }
#endif

        // PRIVATE

        private void Awake()
        {
            OnValidate();
        }

        private void OnValidate(){
            nodeLookUp.Clear();

            foreach (DialogueNode node in GetAllNodes()) nodeLookUp[node.name] = node;
        }
    }

}

And here it’s my DialogueEditor.cs:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.Callbacks;
using System;

namespace RPG.Dialogue.Editor{

    public class DialogueEditor : EditorWindow{
        // CONFIG
        Dialogue selectedDialogue = null;
        Vector2 scrollPosition;

        [NonSerialized] GUIStyle nodeStyle;
        [NonSerialized] GUIStyle playerNodeStyle;
        [NonSerialized] DialogueNode draggingNode = null;
        [NonSerialized] Vector2 draggingOffset;
        [NonSerialized] DialogueNode creatingNode = null;
        [NonSerialized] DialogueNode deletingNode = null;
        [NonSerialized] DialogueNode linkingParentNode = null;
        [NonSerialized] bool draggingScrolling = false;
        [NonSerialized] Vector2 draggingScrollingOffset;

        const float backgroundSize = 50;

        // Equivalent of start method UnityEngine
        [MenuItem("Window/Dialogue Editor")]
        public static void ShowEditorWindow(Dialogue candidate = null){
            DialogueEditor window = GetWindow(typeof(DialogueEditor), false, "Dialogue Editor") as DialogueEditor;

            if (candidate) window.OnSelectionChange();
        }

        [OnOpenAsset(1)]
        public static bool OnOpenAsset(int instanceID, int line){

            var candidate = EditorUtility.InstanceIDToObject(instanceID) as Dialogue;

            if (candidate != null){
                ShowEditorWindow(candidate);
                return true;
            }

            return false;
        }

        private void OnEnable(){
            Selection.selectionChanged += OnSelectionChange;

            nodeStyle = new GUIStyle();

            nodeStyle.normal.background = EditorGUIUtility.Load("node0") as Texture2D;
            //To change color use nodeStyle.normal.textColor = Color.*desire color*
            nodeStyle.padding = new RectOffset(20, 20, 20, 20);
            nodeStyle.border = new RectOffset(12, 12, 12, 12);

            playerNodeStyle = new GUIStyle();
            playerNodeStyle.normal.background = EditorGUIUtility.Load("node1") as Texture2D;
            playerNodeStyle.padding = new RectOffset(20, 20, 20, 20);
            playerNodeStyle.border = new RectOffset(12, 12, 12, 12);
        }

        private void OnSelectionChange(){

            Dialogue activeDialogue = Selection.activeObject as Dialogue;

            if (activeDialogue != null){
                selectedDialogue = activeDialogue;
                Repaint();
            }
        }

        private void OnGUI(){
            if(selectedDialogue == null) EditorGUILayout.LabelField("No Dialogue Selected");
            else{

                ProcessEvents();

                scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);

                Rect canvas = GUILayoutUtility.GetRect(selectedDialogue.size.x, selectedDialogue.size.y);
                Texture2D backgroundTexture = Resources.Load("background") as Texture2D;
                Rect textureSize = new Rect(0, 0,
                    selectedDialogue.size.x / backgroundSize, selectedDialogue.size.y / backgroundSize);

                GUI.DrawTextureWithTexCoords(canvas,backgroundTexture, textureSize);

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

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

                EditorGUILayout.EndScrollView();

                if (creatingNode != null){
                    selectedDialogue.CreateNode(creatingNode);

                    creatingNode = null;
                }
                if(deletingNode != null){

                    selectedDialogue.DeleteNode(deletingNode);

                    deletingNode = null;
                }
            }
        }

        private void ProcessEvents() {
            if (Event.current.type == EventType.MouseDown && draggingNode == null) {

                draggingNode = GetNodeAtPoint(Event.current.mousePosition + scrollPosition);

                if (draggingNode != null){

                    draggingOffset = draggingNode.GetRect().position - Event.current.mousePosition;
                    Selection.activeObject = draggingNode;

                }else{

                    draggingScrolling = true;
                    draggingScrollingOffset = Event.current.mousePosition + scrollPosition;
                    Selection.activeObject = selectedDialogue;
                }

            } else if (Event.current.type == EventType.MouseDrag && draggingNode != null) {

                draggingNode.SetPosition(Event.current.mousePosition + draggingOffset);
                GUI.changed = true;

            }else if(Event.current.type == EventType.MouseDrag && draggingScrolling){

                scrollPosition = draggingScrollingOffset - Event.current.mousePosition;
                GUI.changed = true;

            }else if (Event.current.type == EventType.MouseUp && draggingNode != null){

                draggingNode = null;

            }else if(Event.current.type == EventType.MouseUp && draggingScrolling){

                draggingScrolling = false;

            }
        }

        private void DrawNode(DialogueNode node){
            GUIStyle style = nodeStyle;

            if (node.IsPlayerSpeaking()) style = playerNodeStyle;

            GUILayout.BeginArea(node.GetRect(), style);

            //If you add EditorStyle.*choose style*, you can change the color of the label
            //EditorGUILayout.LabelField("Node:");
            node.SetText(EditorGUILayout.TextField(node.GetText()));

            GUILayout.BeginHorizontal();
            
            if (GUILayout.Button("Create dialogue")) creatingNode = node;

            DrawLinkButtons(node);

            if (GUILayout.Button("Delete")) deletingNode = node;

            GUILayout.EndHorizontal();

            GUILayout.EndArea();
        }

        private void DrawLinkButtons(DialogueNode node){
            if (linkingParentNode == null){

                if (GUILayout.Button("Link")){
                    linkingParentNode = node;
                }

            } else if(linkingParentNode == node){

                if (GUILayout.Button("Cancel")){
                    linkingParentNode = null;
                }

            }else if (linkingParentNode.GetChildren().Contains(node.name)){

                if (GUILayout.Button("Unlink")){

                    linkingParentNode.RemoveChild(node.name);
                    linkingParentNode = null;
                }

            }else{

                if(GUILayout.Button("Child")){

                    linkingParentNode.AddChild(node.name);
                    linkingParentNode = null;
                }
            }
            
        }

        private void DrawConnections(DialogueNode node){
            
            Vector2 start = new Vector2(node.GetRect().xMax, node.GetRect().center.y);

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

                
                Vector2 end = new Vector2(child.GetRect().xMin, child.GetRect().center.y);
                Vector2 controlOffPoint = end - start;
                controlOffPoint.y = 0;
                controlOffPoint.x *= 0.8f;

                Handles.DrawBezier(start, end, start + controlOffPoint, end - controlOffPoint,
                    Color.white, null, 4f);
            }
        }
        
        private DialogueNode GetNodeAtPoint(Vector2 point){
            DialogueNode toReturn = null;
            foreach(DialogueNode node in selectedDialogue.GetAllNodes())
                if(node.GetRect().Contains(point)) toReturn = node;

            return toReturn;
        }

    }
}

Everything looks right… so I’m not sure what’s going on. I’ll need to take a closer look.

I’m at work right now, and don’t have access to Unity, but in preparation for when I get home tonight, zip up your project and upload it to https://gdev.tv/projectupload (if you have a public repo, you can link that instead). Remember to remove the Library, temp, and obj folders from the zip file as they will be recreated on my end.

Okay, i will do that, thanks for the help.

I should have caught this one sooner, but once I realized that when the Dialogue was selected, the Inspector was showing no Nodes, I looked once again at the Dialogue…

[NonSerialized] is used when you don’t want the serializer (the thing that actually saves the Dialogue to disk) to keep track of that thing… hence it never shows up in the inspector, and it is also never saved.

In DialogueEditor, this is correct, we really don’t need to save those fields. In our Dialogue, however, it’s somewhat important to [SerializeField] instead

[SerializeField] List<DialogueNode> nodes = new List<DialogueNode>();
[SerializeField] Vector2 newNodeOffset = new Vector2(500,0);

Once these changes were made, I was off to the races.


A quick note on uploading your project. Don’t zip/Rar the individual directories under the project. At first, I actually thought I was in an empty project, and those files would have already been well compressed within the outer .rar without the inner archives. It also risks GameObjects not hooking up to scripts properly if things are unpacked in the wrong order. Simply zip the project (without the afore mentioned directories that are auto generated anyways).

1 Like

Thank you so much, i don’t know why i through about putting them as a NonSerialiazed fields.
As for the zip, got it will not do it, i just through about making it smaller, didn’t through about that possibility.

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms