Correct replacement for changing Unity values in OnValidate()?

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:

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

1 Like

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?

An answer to this would be greatly appreciated.

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:

public bool isActive;
public Transform oldChild;

void Update() {
    oldChild.SetActive(isActive)
}
  1. Modify parent script

Parent Script:

public bool newChildIsActive;
public Transform newChild

void OnValidate() {
    newChild.isActive = childIsActive;
}

This is a hacky workaround
The forum is full with questions about this basic functionality…will someone @ 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.

+1

This is a bit annoying…

My nasty workaround is using MonoBehavior.Invoke like so:

    [SerializeField, Tooltip("The GameObject to show or hide.")]
    private GameObject myGameObject = default;

    [SerializeField, Tooltip("Whether to show or hide the desired GameObject.")]
    private bool show = true;

    private void OnValidate() => Invoke(nameof(UpdateVisibility), time: 0);

    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.

    private void OnValidate()
    {
        if(isActiveAndEnabled)
            StartCoroutine(UpdateVisibility(show));
    }

    private IEnumarator UpdateVisibility(bool visible)
    {
        yield return new WaitForEndOfFrame();
        myGameObject.SetActive(visible);
    }

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.

3 Likes

I had to yield return null instead, but then it seems to work~!

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.

Hi, you might find a solution in this thread :

Cheers

1 Like