Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question When is OnValidate called in a prefab?

Discussion in 'Prefabs' started by eofirdavid, Oct 10, 2022.

  1. eofirdavid

    eofirdavid

    Joined:
    Jan 3, 2020
    Posts:
    4
    I am having some trouble with the initialization of scripts when it comes to the OnValidate function calls. In particular I don't understand when and why it is called in prefabs, and I would appreciate if someone can explain to me what happens.

    More specifically, it seems that every time I edit anything in the inspector (in the prefab mode) the OnValidate is called several times, and the variables of the prefab itself keep changing.

    I have the following example where a prefab has 1 public variable (so I could activate the OnValidate), and 3 private variables - 1 primitive (integer), 1 standard class, and 1 MonoBehaviour (Line renderer).
    Also, since I want the intialization to happened just once, I added a boolean variable which checks if the script was initialized already or not (The Init method is what I usually call in the Awake()).

    Code (CSharp):
    1. class SecondClass
    2. {
    3.     public int number = 0;
    4. }
    5.  
    6. public class InitTry : MonoBehaviour
    7. {
    8.  
    9.     private bool initialized = false;
    10.  
    11.     public int index = 0;
    12.     private int temp = 0;
    13.     private SecondClass secondClass;
    14.     private LineRenderer lineRenderer;
    15.  
    16.     public void Init()
    17.     {
    18.         if (initialized)
    19.             return;
    20.         TrueInitialize();
    21.         initialized = true;
    22.     }
    23.  
    24.     public void TrueInitialize()
    25.     {
    26.         temp = 1;                           // private primitive
    27.         secondClass = new SecondClass();    // private class
    28.         secondClass.number = 2;
    29.         lineRenderer = gameObject.GetComponent<LineRenderer>();
    30.     }
    31.  
    32.  
    33.     private void OnValidate()
    34.     {
    35.         Debug.Log($"BEFORE: initialized = {initialized}, temp = {temp}, index = {index}, " +
    36.             $"second class = {(secondClass == null ? -1 : secondClass.number)} " +
    37.             $"renderer = {(lineRenderer == null ? -1 : 100)}");
    38.         Init();
    39.         Debug.Log($"AFTER: initialized = {initialized}, temp = {temp}, index = {index}, " +
    40.             $"second class = {(secondClass == null ? -1 : secondClass.number)} " +
    41.             $"renderer = {(lineRenderer == null ? -1 : 100)}");
    42.     }
    43. }
    When I change the public variable, the OnValidate is usually called 4 times:
    1. When I change the variable, but before pressing enter, or loosing the focus in the field input, there is a single OnValidate call where (1) the call stack starts at the GUI process, (2) the 'initialized' flag is true before and after the Init call, and (3) all of the other variables are already initialized properly:

    BEFORE: initialized = True, temp = 1, index = 0, second class = 2 renderer = 100
    AFTER: initialized = True, temp = 1, index = 0, second class = 2 renderer = 100


    2. Once I press enter, or move out of the input field, there is a second call. This time (1) the call stack moves through a "SavePrefabAsAsset" stage, (2) the 'initialized' flag is false BEFORE the init call and (3) after the init call it becomes true and all the other variables are initialized properly :

    BEFORE: initialized = False, temp = 0, index = 0, second class = -1 renderer = -1
    AFTER: initialized = True, temp = 1, index = 0, second class = 2 renderer = 100


    3. Afterwards there are two more calls, where (1) they are still part of the same "SavePrefabAsAsset" call (2) the 'initialized' flag is true before and after the Init call and (3) the only variable with the right value is the primitive float 'temp' variable:


    BEFORE: initialized = True, temp = 1, index = 0, second class = -1 renderer = -1
    AFTER: initialized = True, temp = 1, index = 0, second class = -1 renderer = -1
    BEFORE: initialized = True, temp = 1, index = 0, second class = -1 renderer = -1
    AFTER: initialized = True, temp = 1, index = 0, second class = -1 renderer = -1


    The first call seems to be what needs to happen with the prefab that I am editing. The second is like Unity generates another copy to save from scratch. My problem is the two other calls. Not only that I don't know why they are happening, because the 'initialized' is always true at the beginning, the other non primitive values don't get the right values. This is a problem for me because usually after the initialization I want to use these values, and I keep getting null pointer errors.

    Sometimes there are extra calls, either of the second or third kind.

    Anyway, I would appreciate any help with understanding what happens here.
     
  2. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    5,533
    One-time initialization should be done in Awake or Start.
    Be sure you have only one object in the scene and in a prefab that has this script on it, otherwise you may be getting OnValidate logs from other objects too. It might help to also log GetInstanceID() or the .name property.

    Note that OnValidate will not be called at runtime! If any of this init code relies on OnValidate running at runtime, it won't work.
     
  3. eofirdavid

    eofirdavid

    Joined:
    Jan 3, 2020
    Posts:
    4
    The initialization in the OnValidate was meant just for stuff that I want to see already in the editor mode. The main initialization is done in the Awake\Start methods. More specifically, I am trying to have some connection between different objects, like having parameters in one object which control another, for example its size and location. I want to see how it works already in the editor (so I won't have to play\stop the game every time), so I "partially" initialize stuff in the OnValidate, and the full initialization is in the awake.

    I added the Instance ID to the logs, and three of the logs were from the opened prefab, and one more from an instance of the prefab in the scene. I still think that one prefab log was from the object in the prefab mode, and one for the "real" prefab in the background. I am not sure what is the third one (which cause the problems).
     
  4. Mads-Nyholm

    Mads-Nyholm

    Unity Technologies

    Joined:
    Aug 19, 2013
    Posts:
    217
    Try adding
    EditorUtility.IsPersistent(this)
    to your logging code. The persistent objects that get OnValidate() called are the objects generated during the import process of a Prefab.
    Hope this helps.
     
    danielrusnac_tmg likes this.
  5. Dawdlebird

    Dawdlebird

    Joined:
    Apr 22, 2013
    Posts:
    88
    EditorUtility.IsPersistent(this) *