I’m pulling my hair out trying to understand what is unexpectedly causing a value to be assigned to a variable which should be null in one of my scripts.
I have a simple serializable data class along the lines of:
[Serializable]
public class SimpleData {
public string field;
}
And I have a MonoBehavior which has a variable of this data class’s type along the lines of:
public abstract class MyMono: MonoBehaviour {
[HideInInspector]
public SimpleData myVariable = null;
private void Awake() {
// prints true
Debug.Log($"is myVariable null? {myVariable== null}");
}
private void Start() {
// prints false :(
Debug.Log($"is myVariable null? {myVariable== null}");
}
}
I have code that expects myVariable to be null initially but this field seems to be unexpectedly set to some default value. I don’t assign to this field myself. I believe that whatever is setting this variable, is setting it to the Unity serialization default value (like how the instance would initially look if the inspector populated default values in all of it’s fields). What is even more puzzling to me is that my debug statement Awake() says the variable is null, but says it’s assigned in the Start() log message.
Is this not just normal Unity serialization? Just because you have [HideInInspector] does not stop Unity from serializing and deserializing this field, it’s just now hidden from the inspector. If you don’t want Unity to inject its serialized values into this field, you should make it private or give it [NonSerialized].
I also found the old thread with the same kind of topic. If you want to fix it, try make private field with public property wrapping it. It should fix it.
Unity probably serializes public fields with whole component (just like common serialization works in C#, all public fields are serialized) and prevent null if it isn’t UnityEngine.Object thing.
Yeah, the basic cause is that Unity’s serializer doesn’t support null for anything that’s not a UnityEngine.Object, so any class will be initialized to a default value.
But that doesn’t mean Unity will guarantee serialized objects to be non-null, that will only be the case if Unity has serialized the object before.
Here’s a quick test:
Script added before playing: Both Awake and Start non-null
Script added in inspector when playing: Awake is null, Start non-null
Script added through AddComponent: Both Awake and Start are null
I think the difference between the second and third case comes down to the inspector. In the second the case the object is selected in the inspector and it forces it to be serialized. But Awake is called before the inspector is updated and that’s why Awake still sees null but Start non-null.
Newer version of Unity have the [SerializeReference] attribute, which changes the serialization to support null but make sure you check the documentation for what exactly it does.
Not sure what you mean with pure null. UnityEngine.Object-derived fields do support serializing null, i.e. when you add such a field in your script and check it in the inspector, it’s not set to anything at the beginning.
(Unity does insert a fake null object in the editor to make error messages more helpful but that shouldn’t be a problem in most situations.)
By pure I mean no “Null Object” but pure null reference.
My use case:
Many times I have got components that I want to set something for example OnEnable.
But sometimes I don’t want to link with the field in editor.
public class SetMaterialComponent:MonoBehaviour{
[SerializeReference]
public Renderer targetRenderer;
Public Material material;
void OnEnable(){
if(object.ReferenceEquals(targetRenderer, null)){
targetRenderer = GetComponent<Renderer>();
}
if(targetRenderer != null){
targetRenderer.sharedMaterial = material;
}
}
}
Class above basically should fill the targetRenderer at runtime with Renderer that is on this object if targetRenderer is pure null. (Obviously it is not working correctly since targetRenderer reference is never null)
And I need it to be “reference null” since I want to distinguish between objects that are destroyed and not set from the beginning.
Not directly, but the documentation does state this:
Given this documentation, and the fact that 0 is the default value of int, and the fact that 0 always seems to be the value that unassigned fake null objects have in practice, it’s not a huge leap to assume the system will continue to function like this into the foreseeable future.
Yes that would work, but only for detecting a “fake null” object in the editor. If I recall correctly, the fake null value is only given to unassigned serialized fields in the editor, but not in builds.
I’m just now discovering that Unity will automatically construct instances of everything for serializable fields. Is this behavior documented anywhere?