Hi! I’m curious if this is happening for everyone, or if I’m doing something wrong. Inside the Dialogue.cs script, I’m getting an error thrown when I create a new Dialogue during OnValidate(). (I’m pretty sure it’s because there’s no dialogue node created yet, so when I save I get an error about it not being set. Was there a catch I just missed when we call OnValidate?
OnValidate goes through a few changes from start to finish… Here’s where we are at this lecture:
private void OnValidate() {
nodeLookup.Clear();
foreach (DialogueNode node in GetAllNodes())
{
nodeLookup[node.name] = node;
}
}
In this case, there are only two possiblities for a null reference…
nodeLookup.Clear() would return an NRE if nodeLookup never existed in the first place. We should have the NodeLookup assigned in the declaration. The other possibility is that nodes is not initialized, same as nodeLookup.
This is all based, of course, on the OnValidate at this point in the course. If it’s not, pasted in our OnValidate, and we’ll take a look.
my OnValidate is the same as the one in the git repo, I’m just curious if we changed anything when we make the “root” dialogue node. I believe it’s throwing the null reference exception when we save the first node after creating the dialogue, because there’s nothing in nodeLookup.
I’m gonna paste the whole dialogue script in here for you to take a look at, but it’s very much throwing an error when I save the project after initially creating a new dialogue, and then it doesn’t have an error after that.
public class Dialogue : ScriptableObject, ISerializationCallbackReceiver
{
[SerializeField] List<DialogueNode> nodes = new List<DialogueNode>();
[SerializeField] Vector2 newNodeOffset = new Vector2(250, 0);
[NonSerialized] Dictionary<string, DialogueNode> nodeLookup = new Dictionary<string, DialogueNode>();
private void OnValidate()
{
nodeLookup.Clear();
foreach (DialogueNode node in GetAllNodes())
{
nodeLookup[node.name] = node;
}
}
public IEnumerable<DialogueNode> GetAllNodes()
{
return nodes;
}
public DialogueNode GetRootNode()
{
return nodes[0];
}
public IEnumerable<DialogueNode> GetAllChildren(DialogueNode parentNode)
{
foreach (string childID in parentNode.GetChildren())
{
if (nodeLookup.ContainsKey(childID)) { yield return nodeLookup[childID]; }
}
}
#if UNITY_EDITOR
public void CreateNode(DialogueNode parent)
{
DialogueNode newNode = MakeNode(parent);
Undo.RegisterCreatedObjectUndo(newNode, "Created Dialogue Node");
Undo.RecordObject(this, "Added Dialogue Node");
AddNode(newNode);
}
public void DeleteNode(DialogueNode nodeToDelete)
{
Undo.RecordObject(this, "Deleted Dialogue Node");
nodes.Remove(nodeToDelete);
OnValidate();
CleanChildren(nodeToDelete);
Undo.DestroyObjectImmediate(nodeToDelete);
}
private void CleanChildren(DialogueNode nodeToDelete)
{
foreach (DialogueNode node in GetAllNodes())
{
node.RemoveChild(nodeToDelete.name);
}
}
private void AddNode(DialogueNode newNode)
{
nodes.Add(newNode);
OnValidate();
}
private DialogueNode MakeNode(DialogueNode parent)
{
DialogueNode newNode = CreateInstance<DialogueNode>();
newNode.name = Guid.NewGuid().ToString();
if (parent != null) { parent.AddChild(newNode.name); newNode.SetPosition(parent.GetRect().position + newNodeOffset); }
return newNode;
}
#endif
public void OnBeforeSerialize()
{
#if UNITY_EDITOR
if (nodes.Count == 0)
{
DialogueNode newNode = MakeNode(null);
AddNode(newNode);
}
if (AssetDatabase.GetAssetPath(this) != "")
{
foreach (DialogueNode node in GetAllNodes())
{
if(AssetDatabase.GetAssetPath(node) == "")
{
AssetDatabase.AddObjectToAsset(node, this);
}
}
}
#endif
}
public void OnAfterDeserialize()
{
}
}
Actually, I’m surprised that the Editor isn’t locking up completely when you create a new Dialogue. There should have been a voiceover in one of the lectures pointing out a change to CreateNode. Check out this thread for more information, and see if this clears up the issue:
The TLDR is checking to make sure that the Dialogue has actually been saved by the system before registering an Undo in CreateNode.
if (AssetDatabase.GetAssetPath(this)!="")
{
Undo.RecordObject(this, "Added Dialogue Node");
}
Is this before we do both Undos in CreateNode? This is still giving me an error. It happens directly after I finish renaming the new dialogue.
Sanctuary.Harry.Dialogue.Dialogue.OnValidate () (at Assets/Harry/Scripts/Dialogue/Dialogue.cs:22)
this is the error I’m getting, if that helps at all
Without the using headers, it’s still hard to tell which line is the offending line.
What’s interesting is that both nodes and nodeLookup are initialized in the field declarations, so there’s nothing I can think of that would give the error…
Can you let me know which line is 22, and paste in the full text of the error (for the stack trace)?
so the line is
nodeLookup[node.name] = node;
which is inside the foreach loop in OnValidate().
[Worker0] NullReferenceException: Object reference not set to an instance of an object
Sanctuary.Harry.Dialogue.Dialogue.OnValidate () (at Assets/Harry/Scripts/Dialogue/Dialogue.cs:22)
and that’s the full text of the error.
So that’s what’s throwing me off… the node should exist, and it should have a name (a Guid, set in MakeNode()). The nodeLookup is initialized in it’s declaration, and we’ve already passed a de-facto null check when we cleared it. If nodes was null, the error would be on the foreach line, and nodes should contain one node, created by MakeNode.
Let’s muddy this up with some Debug code
private void OnValidate()
{
if(nodeLookup==null)
{
Debug.LogError("nodeLookup is not assigned");
return;
}
Debug.Log($"GetAllNodes returns {GetAllNodes().Length}, nodes is {nodes.Count}"
);
foreach (DialogueNode node in GetAllNodes())
{
if(node==null || node.name==null)
{
Debug.LogError($"Node is null or not properly initialized");
continue;
}
Debug.Log($"Adding {node.name}");
nodeLookup[node.name] = node;
}
}
I typed that in on my lunch break straight into the editor, so I might have a typo in there. You might also need to add
using System.Linq;
to the usings clauses.
So the first node I created after implemented this threw an error saying that the node was null or not properly initialized, I cleared that and tried it again, after deleting all previous dialogues, and I got these in the console.
So the thing I’m seeing now is that when I have the line
nodeLookup.Clear();
then it has an error, when I omit that line, I no longer have an error.
What if you change it to
nodeLookup = new Dictionary(string, DialogueNode);
Same error, except with Worker4 instead of Worker0. I’m using 2021.2.11f1 if that helps with anything.
I’m starting to wonder if this is an unwanted side effect of Unity switching to burst compiling…
It honestly doesn’t look like an error that has any bad effects.
Dialogues work just as intended, it just… has a small error when I make a new dialogue.
As long as the Dictionary updates properly when a dialogue is removed, and when the built player starts (that’s another issue, make sure you have an Awake() that calls OnValidate())
So 2 questions, how do I check if the dictionary updates properly (i only use nodeLookup in Get All Children, and On Validate), and which script should I put Awake in that’ll call OnValidate()? Did one of get made throughout the section of the course?
It was more of an afterthought. I can’t remember which lecture Sam edited to mention it. The problem is that OnValidate is only called automagically in the Editor, not in a built player. Then the dialogues don’t work, and debugging a built player is a pain in the… The solution is to have
void Awake()
{
OnValidate();
}
in the file. Problem solved.
This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.