Search Unity

Correct replacement for changing Unity values in OnValidate()?

Discussion in 'Editor & General Support' started by Arkade, Feb 3, 2020.

  1. Arkade

    Arkade

    Joined:
    Oct 11, 2012
    Posts:
    655
    TL;DR What's the Unity-approved code-based way to make changes to serialized values during Edit time for simple components rather than in OnValidate()? (simple i.e. without a custom Editor)

    In the past, something like UI layout code might update its children's positions during edit-time in OnValidate(). That now generates hundreds of errors like this:

    OnValidate-errors.png

    Many of these in this example are from open-source RadialLayout here but I have plenty of similar from other sources including my own code. Obviously UI code isn't the only thing that might do this. Say you want to set some serialized field to a known child component, you might check it and use GetComponent() if it's unset.

    According to posts like this, we shouldn't rely on this approach. There's a 'fixed' bug in 2019.1 but I'm seeing hundreds of these in 2019.3.0f6. But maybe a better questions is:

    What's the 'correct' way to do that now?

    Thanks
     
    ZorDKinG likes this.
  2. pryankster

    pryankster

    Joined:
    Aug 6, 2015
    Posts:
    24
    I'm seeing this in an OnValidate() function where I activate/deactivate child objects (i.e.: childObj.setActive(true)). Is there a correct way to do this?
     
  3. pbritton

    pbritton

    Joined:
    Nov 14, 2016
    Posts:
    160
    An answer to this would be greatly appreciated.
     
  4. AdamBebko

    AdamBebko

    Joined:
    Apr 8, 2016
    Posts:
    168
    Yes I'm running into the exact same problem. Trying to activate/deactivate a child in editor mode based on when a bool is flipped. In OnValidate()

    Edit: I have a solved workaround

    Child's script:

    1) move child one level deeper in hierarchy by creating a new empty gameobject. so you now have Original parent > new child > old Child
    2) create new script on new child. set to [ExecuteInEditMode]
    3) new public bool isActive.
    4) add this to update method:

    New Child Script:

    Code (CSharp):
    1. public bool isActive;
    2. public Transform oldChild;
    3.  
    4. void Update() {
    5.     oldChild.SetActive(isActive)
    6. }
    5) Modify parent script

    Parent Script:
    Code (CSharp):
    1.  
    2. public bool newChildIsActive;
    3. public Transform newChild
    4.  
    5. void OnValidate() {
    6.     newChild.isActive = childIsActive;
    7. }
     
    Last edited: Apr 9, 2020
  5. LeFx_Tom

    LeFx_Tom

    Joined:
    Jan 18, 2013
    Posts:
    88
    This is a hacky workaround
    The forum is full with questions about this basic functionality...will someone @Unity maybe have mercy and come down to us and explain for once? There is no official answer anywhere in those posts, just user looking for help.
     
  6. RoyBarina

    RoyBarina

    Joined:
    Jul 3, 2017
    Posts:
    98
    +1

    This is a bit annoying..

    My nasty workaround is using MonoBehavior.Invoke like so:
    Code (CSharp):
    1.     [SerializeField, Tooltip("The GameObject to show or hide.")]
    2.     private GameObject myGameObject = default;
    3.  
    4.     [SerializeField, Tooltip("Whether to show or hide the desired GameObject.")]
    5.     private bool show = true;
    6.  
    7.     private void OnValidate() => Invoke(nameof(UpdateVisibility), time: 0);
    8.  
    9.     private void UpdateVisibility() => myGameObject.SetActive(show);
    This is just a basic example but it's working great and zero "SendMessage cannot be called in OnValidate.." warnings.

    Edit:
    But if I'm not mistaken, Invoke uses reflections and we better use Coroutines instead anyway as suggested in the link above.
    When I tried to use coroutines I had some issues (sometimes) saying that they don't work with inactive GameObject's (although the object was definitely active).

    Edit 2:
    Alright when using coroutines we can make sure that our component isActiveAndEnabled before firing the coroutine.
    Then in the coroutine we can WaitForEndOfFrame and then run our command.
    Code (CSharp):
    1.     private void OnValidate()
    2.     {
    3.         if(isActiveAndEnabled)
    4.             StartCoroutine(UpdateVisibility(show));
    5.     }
    6.  
    7.     private IEnumarator UpdateVisibility(bool visible)
    8.     {
    9.         yield return new WaitForEndOfFrame();
    10.         myGameObject.SetActive(visible);
    11.     }
    Just tested this and it works. not perfect as it can potentially loose some functionality..

    BTW calling WaitUntil(() => isActiveAndEnabled) in the coroutine won't work of course.
     
    Last edited: Jan 28, 2021
  7. EmmaEwert

    EmmaEwert

    Joined:
    Mar 13, 2014
    Posts:
    30
    I had to
    yield return null
    instead, but then it seems to work~!
     
  8. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,924
    I've not tested this, but a possibly simple solution is to use EditorApplication.delayCall.

    I'm guessing editing values in the inspector is threaded in some way, meaning objects can't invoke methods of other objects or use data from other objects.
     
  9. lemartialou

    lemartialou

    Joined:
    Jul 10, 2015
    Posts:
    16