Why is Unity ignoring the initialized value of a non-static public field?

user3956566 picture user3956566 · Mar 2, 2016 · Viewed 9.8k times · Source

I'm using InvokeRepeating() to call a method in a game. I call InvokeRepeating() in the Start() method of one of the GameObject classes. To set the repeatRate parameter for InvokeRepeating(), I am passing it a public field called secondsBetweenBombDrops.

Unity ignores the value I specify for secondsBetweenBombDrops in the code and instead uses some default value (i.e. 1) when secondsBetweenBombDrops is declared without a static modifier:

public float secondsBetweenBombDrops = 10f;
void Start() {
    InvokeRepeating("DropBomb", 1f, secondsBetweenBombDrops);
}

However, once I add the static modifier to secondsBetweenBombDrops, the code behaves as expected and the correct value of 10 is used:

public static float secondsBetweenBombDrops = 10f;
void Start() {
    InvokeRepeating("DropBomb", 1f, secondsBetweenBombDrops);
}

Why does this field require the static modifier to use the appropriate value?

In the Unity inspector, the script component shows that secondsBetweenBombDrops is 1. This default value of 1 is present regardless of whether I instantiate the prefab on game start or create prefab instances while the game is running.

Answer

Everts picture Everts · Mar 2, 2016

The double-edged sword of serialization

Unity wants to make things easier for everyone, including people with limited coding knowledge (beginners, designers).

To help them out, Unity displays data in the inspector. This allows the coder to code and the designer to design by tweaking the values without opening MonoDevelop/an IDE.

There are two ways to have values display in the inspector:

public int myVar = 10;
[SerializeField] private int myOtherVar = 0; // Can also be protected

The second one is better since it complies with encapsulation principle (variables are private/protected and modified via methods or properties).

When you display a variable in the Editor, the value given in the script is only used when dragging the script. Unity then serializes those values and does not care about any script modification anymore. This can lead to confusion if, for instance, myVar is set to 20 inside the script after the fact, it will not be used. The serialization is written in the scene file.

The two lines in the example work exactly in the same way.

Possible solutions

It is possible to get Unity to consider new values in a script by pressing Reset on the settings wheel of the script component. That will also reset all the other variables of the component, so only do this if that is intended.

Making the variable private and omitting the attribute [SerializeField] will disable the serialization process, so Unity will no longer look in the scene file for a value to display - instead, the value will be created at runtime by the script.

When adding a component to Unity, a new object of the type of the component is created. The values that are displayed are the serialized values from that object. For this reason, only member values can be displayed and static variables are not, as they are not serializable. (This is a .NET specification, not strictly specific to Unity.) Because Unity does not serialize static fields, this is why adding the static modifier seemed to solve the problem.

Explaining the OP

In the OP case, based on the comments, your public field was showing a value of 1 in the editor. You thought this value was a default one, when it was actually the value you most likely gave to the field when originally declaring it. After you added the script as a component, you made the value 10 and thought it was buggy as it was still using the value of 1. You should now understand that it was working just fine, as designed.

What does Unity serialize?

By default, Unity will serialize and display value types (int, float, enum and so on) as well as string, array, List and MonoBehaviour. (It is possible to modify their appearance with Editor scripts, but this is off-topic.)

The following:

public class NonMonoBehaviourClass{
   public int myVar;
}

is not serialized by default. Here again, this is .NET specification. Unity serializes MonoBehaviour by default as part of the engine requirement (this will save the content to the scene file). If you wish to display a "classic" class in the editor, just say so:

[System.Serializable]
public class NonMonoBehaviourClass{
   public int myVar = 10;
}

Obviously, you cannot add it to a game object so you need to use within a MonoBehaviour:

public class MyScript:MonoBehaviour{
     public NonMonoBehaviourClass obj = new NonMonoBehaviourClass();
}

this will display the object in the inspector and allow modifications to the myVar variable in the instance of NonMonoBehaviourClass. And again, any changes to myVar within the script will not be considered after the value is serialized and stored to the scene.

Extra tips on displaying things in the inspector

To finish, interfaces are not displayed in the inspector either since they don't contain any variables - just methods and properties. In debug mode, properties are not displayed by default. You can change this mode using the button with three lines in the top right corner of the Inspector. The first two settings are Normal/Debug. The first one is the default one, the second will also display private variable. This is useful to watch their values but cannot be altered from editor.

So if you need an interface to be displayed, you would have to consider an abstract class as it offers a similar functionality (except for multi inheritance) but can be a MonoBehaviour.

References:

http://docs.unity3d.com/ScriptReference/SerializeField.html

http://docs.unity3d.com/Manual/script-Serialization.html

https://www.youtube.com/watch?v=9gscwiS3xsU

https://www.youtube.com/watch?v=MmUT0ljrHNc