Search Unity

How does OnValidate() Work !?

Discussion in 'Scripting' started by MGGDev, Jan 21, 2019.

Thread Status:
Not open for further replies.
  1. MGGDev

    MGGDev

    Joined:
    Nov 6, 2018
    Posts:
    27
    Hello,

    I use OnValidate() to fill script variable, for example:
    Code (CSharp):
    1. public RectTransform MainRect;
    2. private void OnValidate()
    3. {
    4. MainRect = GetComponent<RectTransform>();
    5. }
    Now the inspector shows MainRect is not null anymore. When I build for Android, MainRect suddenly becomes null, even though it is still not-null in the inspector.

    I have to initialize MainRect again in Awake(); in order for it to work in build.

    What is going on here :) ?!
     
    ow3n likes this.
  2. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Something is probably resetting that main rect to null in runtime.
    Check if that variable is accessed elsewhere (if you're using VS that is Shift+F12 I think?).
     
  3. MGGDev

    MGGDev

    Joined:
    Nov 6, 2018
    Posts:
    27
    I have just checked, and that is not the case.

    It is as if the Editor resets when I Build. It resets any variables that was set by OnValidate(), and keeps the ones I sat myself.

    I use OnValidate() in different situations other than this, and all share the same behavior.
    It is more troublesome with private variables too. After many hours with Debug.Log() and Logcat, I traced a simple bug to one of the OnValidate()s that worked in Editor and reset after Build.

    Conclusion: If the Editor run fine, but the build is different and bugged; revise all your OnValidate().
    I would recommend making an Initialize() function with all the initialization code, and calling it once inside OnValidate() to fill the variables for you, and once in Awake() to avoid this bug when the game is built.
     
    ow3n and Ziplock9000 like this.
  4. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    I haven't encountered this neither at work, neither at home. Most of the bugs with OnValidate occur when somebody forgets to put [SerializeField] on private var or modifies the variable at the run time.

    Might be worth making a repro project and sending it to the issue tracker as a bug report.
     
    MGGDev likes this.
  5. https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnValidate.html

    It's not a bug.
    It won't initialize for you in the build if Unity didn't serialize the field by itself.

    It's highly advisable to use Awake to initialize your private variables instead of OnValidate. OnValidate is for Editor time to check the values when someone change them in the inspector. Nothing else.
     
  6. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    That's not quite true. If you dissamble some UI components of Unity's, you'll see plenty of component fetches via .OnValidate().

    Also, I'd advice using .OnValidate when possible. This is from loading / performance perspective.

    Unity does serialize fields, and does it really well.
    Private vars aren't serialized without [SerializeField], that is true. Everything else - not so much.

    Here's a keypoints:
    - Serialization occurs on play mode start.
    - Serialization occurs when scripts are reloaded.
    - Serialization (OnValidate) occurs when any of the fields are modified via inspector.

    If it's neccessary to 100% serialize everything in the whole project, small script can be made to add a hotkey / menu item for the script reload. It can be triggered quite easily by calling reimport on .cs file. That will cause assembly reload + re-serialization.

    What we practice is creating a button (Reflection method call for .OnValidate()) for the field that should be fetched via validation, plus a custom drawer. That will ensure that field is fetched correctly, even if the scripts is not being reloaded.


    This case is rather a missuse.
     
    Last edited: Jan 23, 2019
    DebugLogWarning and MGGDev like this.
  7. MGGDev

    MGGDev

    Joined:
    Nov 6, 2018
    Posts:
    27
    I did send one to Unity, but I knew I must be doing something wrong :)
    Turns out I was missing [SerializeField]. OnValidate() works now as intended, but I still don't know why :D !

    Using OnValidate with private/public/SerializeField variables works in the Editor, but not in the Build. It works in the Build only if the variable is public/SerializeField.
    I still don't know why ..
     
    ow3n likes this.
  8. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Private variables aren't serialized. That is exactly what's causing this.

    In editor they're fetched and assigned anyway (because OnValidate is called), but their references are lost as soon as assembly reloads.

    And in build, .OnValidate is not called ever.
     
    DanQuell likes this.
  9. If you simply write an OnValidate and then never change any value on the component, nothing will be serialized. It's prone to human mistake. I wouldn't roll a system like this.

    Because in the build it's not the OnValidate works, it's the serialization. If you serialize something (enter value in the inspector or change other way and Unity serialize the value, it will be loaded in build, OnValidate does not work in build, you need to cause the serialization in the editor).
     
    ow3n likes this.
  10. MGGDev

    MGGDev

    Joined:
    Nov 6, 2018
    Posts:
    27
    The serialization part is not mentioned in the doc. I sent feedback to Unity about that.

    Say we have 30 objects in the scene, instead of dragging & dropping them manually to the script, and instead of using FindWithTag at Runtime, I use OnValidate to FindWithTag and fill them during Edit mode.
     
  11. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    That's not true, unless someone never enters playmode.

    If that happens, well, pre-build processing script can be made to ensure the serialization happens at least once.

    Also, this is actually less human-error prone, as it forces an automatic fetch, instead of forcing artists / designers to assign components manually.
     
  12. Because it has nothing to do with OnValidate. Serialization is a separate subject. You're just mixing up the two and as we can see it's really not that great if you do that.

    Well, it's up to you how complicated backup systems you're willing to create just to avoid one command in the Awake method. I don't recommend to anyone, because as I said, it's not foolproof. You do however you like.
     
    xVergilx likes this.
  13. MGGDev

    MGGDev

    Joined:
    Nov 6, 2018
    Posts:
    27
    That's the answer I am looking for. Thank you!

    I thought private variables will be "kept" like public variables. If I understand correctly, Serialization is what keeps the references from getting lost, and since private variables are not serialized, they are not saved.
    So private variables are just to be used for runtime only stuff, not with anything related to the Edit mode.
    Is that correct?
     
    Hozgen90 likes this.
  14. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    I wish it was only one. It is never a single call.
     
  15. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Yes, that is true.
     
    MGGDev likes this.
  16. Serialization is like a snapshot about the object created in memory. If you put a reference in it, it will be saved with the other serializable data.
    In Unity, by default the private variables aren't serialized unless you put the [SerializeField] on the front of them.
    The public variables are automatically serialized unless you put the [NonSerialized] attribute in the front of them.
    More info here: https://docs.unity3d.com/Manual/script-Serialization.html

    Also you can write your own serialization if you wish.
     
    MGGDev and xVergilx like this.
  17. danBourquin

    danBourquin

    Joined:
    Nov 16, 2016
    Posts:
    34
    You can force to serialize your gameobject after you get the component reference.

    Code (CSharp):
    1.     private void OnValidate()
    2.     {
    3. #if UNITY_EDITOR
    4.         _rectTransform = GetComponent<RectTransform>();
    5.         UnityEditor.EditorUtility.SetDirty(this);
    6. #endif
    7.     }
    I didn't test it but it should work
     
  18. Ziplock9000

    Ziplock9000

    Joined:
    Jan 26, 2016
    Posts:
    360
    The related issue I have is with the following code:
    It keeps thinking _guid is empty and generates a new string guid. It should only do this once, then stick as the _guild is serialised. The emptying of _guid seems to happen on play.
    FYI the object this script is on is a prefab and I have multiple instances embedded in the scene

    Code (CSharp):
    1.     [SerializeField] public string _guid;
    2.  
    3.     void OnValidate()
    4.     {
    5.         if (string.IsNullOrEmpty(_guid))
    6.         {
    7.             _guid = Guid.NewGuid().ToString();
    8.         }
    9.     }
     
    marie-hmm likes this.
  19. Ziplock9000

    Ziplock9000

    Joined:
    Jan 26, 2016
    Posts:
    360
    This was the fix, thank you very much.
     
  20. Hozgen90

    Hozgen90

    Joined:
    Jan 20, 2021
    Posts:
    19
    You are a hero, man!
     
  21. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,500
    Please use the Like button to show appreciation rather than necroing threads.

    Thanks.
     
    Bunny83 and Hozgen90 like this.
Thread Status:
Not open for further replies.