I don’t know for certain whether this is a Unity bug or an issue with my own code, but something is acting very strangely. I have a system of nodes (I’m calling them NavNodes) where each one is a step in an AI pathing route. Every node is its own GameObject with an associated NavNode script where each of its connections are represented by a list of neighbor NavNodes like so:
public class NavNode : MonoBehaviour
{
public List<NavNode> connections;
public NavNodeSystem system;
}
I have some logic that handles cleanup of adjacent nodes when a NavNode is deleted. In this code I use Undo.RecordObject() to ensure this cleanup can be properly undone if need be. NavNodeEditor.cs (Custom editor for NavNode)
public void Unlink()
{
foreach (NavNode node in connections)
{
Undo.RecordObject(node, "Unlink node");
node.RemoveConnection(this);
}
Undo.RecordObject(system, "Unlink node");
system.RemoveNode(this);
}
When I hit undo, this code works as intended and the list of connections is preserved. Strangely enough, however, I still get a MissingReferenceException implying that the connections field is unassigned:
MissingReferenceException: The variable connections of NavNode doesn't exist anymore.
You probably need to reassign the connections variable of the 'NavNode' script in the inspector.
NavNode.DrawNodeLineGizmos () (at Assets/Scripts/AI/NavNode.cs:104)
NavNode.OnDrawGizmos () (at Assets/Scripts/AI/NavNode.cs:87)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr, Boolean&)
The connections field is seemingly normal in the inspector panel (see attached). If I cause any script to recompile, this error disappears and Unity seems to properly recognize the field. Am I using RecordObject incorrectly? This is my first attempt at doing anything in-depth with custom editors so sorry if this is a dumb question. Any help is appreciated, thank you!
I have actually seen this in some edge cases with Undo / Redo… I think it has to do with properties (such as these nodes / connections) that actually do some additional internal bookkeeping when manipulated, and there isn’t a clean way to undo that internal bookkeeping. That’s my theory anyway… I think you can ignore these.
Usually, Undo methods are to be used as a result of user inputs in the Editor UI. OnDestroy can be called from many different places, and it’s not really a callback associated with UI actions, so I’m not sure this kind of thing is supported there.
More over, when registering with Undo the destruction of an object and deleting all references to it, one usually needs to delete all references to it before destroying it:
First remove references to the object.
Then destroy the object.
If it’s done the other way around, ctrl+z will try to restore references to the object before the object is created. That is to say, if Undo is registered like this:
First destroy the object
Then remove references to the object.
On ctrl+z it will look like this:
First restore references to the object, but it can’t because the object doesn’t exist yet.
Then recreate the destroyed object.
I imagine this information doesn’t really fix your problem, because you are using Unity’s default GameObject deleting interface instead of creating your own. You could make your own UI or MenuItem to destroy GameObjects by yourself, so you can control this behavior, but then, if someone forgets to delete a GameObject with your special UI, the NavNodes would not be updated.
I’m not sure there’s a good way to solve this. Serializing relationships between GameObjects that require consistent information stored in multiple different places is very tricky and bug-prone; I tend to avoid it. Some ideas:
You could avoid serialization of bidirectional relationships between Objects by storing those relationships as unidirectional and then, if you need them to be bidirectional, generate bidirectional relationships from the unidirectional ones at runtime.
You could serialize connections with ids instead of Object references. Then generate the actual Object references from the ids at runtime.
You could leave the null references when a GameObject is destroyed in your NavNodes, and have them ignore connections when they are null. The connections should be restored on ctrl+z, but only after your scene has been reloaded or, at least, when the objects that have those connections have been reserialized; otherwise, the connections will appear to be restored in the inspector, but the references will still be null internally.
This seems like the best option for me. I was already experimenting with IDs for different reasons so this shouldn’t be much work to implement. Thank you both for your helpful replies!