This isn’t actually impeding development in any way but it’s weird, so I’d like to try and understand what’s causing the root behavior. To begin with I made a super-simple class to hold NPC data, then in a monobehaviour I made two inspector-accessible variables of type NPC and set one equal to the other:
[System.Serializable]
public class NPC
{
public string name;
public Vector3 location;
}
//in some random monobehaviour:
public NPC testNPC;
public NPC NPCref;
void Start () {
testNPC = new NPC();
NPCref = testNPC;
}
My expectation at this point is to have a single variable of type NPC in memory, and two references (testNPC and NPCref) that both point to that variable. Thus, by changing one, I expect to see changes propagate to the other. This happens if I change either class’s members in code, but in the inspector it behaves oddly: if I edit a field in NPCref, the changes propagate correctly, but if I make changes to testNPC they will revert the instant I un-highlight the field.
My assumption is that there’s something unity-specific happening here, and the latest reference to a class will always be treated as the “master”, but that seems counterintuitive and weird. So what, exactly, is causing the behavior I’m seeing?
There’s definitely something Unity-specific going on here, and it has to do with the inspector.
It’s not that the changes to the original gets discarded. It’s that the changes to the uppermost NPC gets discarded. Try swapping the order of testNPC and NPCref:
//change this:
public NPC testNPC;
public NPC NPCref;
//into this:
public NPC NPCref;
public NPC testNPC;
Now the behaviour will be inversed!
The default inspector probably uses the EditorGUILayout.PropertyField or some version behind the scenes. Indeed, drawing this with PropertyFields causes the same error. EditorGUILayout.TextField does not have this bug.
I’m going to guess that the serializedProperty system unity uses has an issue where a propertyField reverts to cached data if it’s set earlier. Or something. Here’s the test, by the way:
using System;
using UnityEngine;
using UnityEditor;
public class Test : MonoBehaviour {
public NPC npc_first;
public NPC npc_second;
}
[Serializable]
public class NPC {
public string name;
}
[CustomEditor(typeof (Test))]
public class TestEditor : Editor {
private Test script;
private void OnEnable() {
script = (Test) target;
}
public override void OnInspectorGUI() {
if (GUILayout.Button("Overwrite second with first")) {
script.npc_second = script.npc_first;
}
var firstName = serializedObject.FindProperty("npc_first").FindPropertyRelative("name");
var secondName = serializedObject.FindProperty("npc_second").FindPropertyRelative("name");
script.npc_first.name = EditorGUILayout.TextField("name of first", script.npc_first.name);
script.npc_second.name = EditorGUILayout.TextField("name of second", script.npc_second.name);
//This one doesn't work after pressing the override button!
EditorGUILayout.PropertyField(firstName);
//Change the order of npc_first and npc_second in Test, and this one won't work instead!
EditorGUILayout.PropertyField(secondName);
serializedObject.ApplyModifiedProperties();
}
}
I’ll send a bug report on this, but I’m going to guess that it’s too esoteric to get a quick fix.
That’s really kind of neat, thanks for taking the time to work up a test case! This is practically the definition of trivial, since it’s dirt-easy to change in code, but it’s really reassuring to see that it’s not a me problem- I’d half convinced myself that using tons and tons of reference types had somehow irked the C# deities that dwell in the deep and occasionally send up segfaults to confound random programmers.
Edit to add: Oh yeah! I have no clue if this will help anyone else who ends up messing with this, but to compound weirdness with weirdness, putting your references in lists can “cure” this. Steps to repro:
Copy my logic in the OP with lists instead of individual members; List A = new List, List B.AddRange(A). You now have two lists, but only B can be edited.
Alter the size of either list; it doesn’t seem to matter whether you add or remove indices, I think it’s the act of re-indexing that does it. (I still don’t fully understand how lists are handled in C#, but I believe any act of re-indexing requires the system to create a new list, add the old’s contents to the new, then GC the old?)
Now both lists should be fully editable in the inspector!