Unity's .SetActive vs .SetActiveRecursively
A story of how fixing warnings broke all my code. It's all very well obsoleting this stuff, but you have to make sure it's still compatible...
At some point during the 4.x cycle, Unity switched the way that a
GameObject is activated and deactivated in a scene at runtime. Previously, there was a boolean
.active property, and a method called
SetActiveRecursively() that could be used. This leads to some confusion: if I set
a.active = false then what is the state of a child of
a? Assuming that all children of
a are automatically disabled too, what's the purpose of
The recent change obsoletes the original members and adds a single place to change state: the
SetActive() method. This new method sets the state only on the current GameObject but, as expected, all descendant objects are automatically disabled. Two readonly properties can be queried to examine the active state of a GameObject:
activeSelf will return the value set through
activeInHierarchy will return
true only if the GameObject and all of its parents are enabled. It's possible therefore for
activeSelf to be
true even though
false - when a parent object has been disabled - and in this case the GameObject is disabled in the scene.
This is a Good Thing: it makes the API much clearer and more intuitive. However, it's not quite backward compatible, as I found out after tidying up some code.
I'd addressed some compiler warnings by replacing some obsolete
SetActiveRecursively() calls with
SetActive(). Suddenly, things went awry: runtime errors spewing through the console. My "quick tidy-up" had broken everything! What had happened is that
SetActive() isn't a drop-in replacement for
SetActiveRecursively(), even though the compiler warning suggests the switch.
When you call
SetActive(), the change in value doesn't take place until the end of the current frame. With the old methods, the change would happen immediately. This coul be a hugely important distinction because, in my case, the following line of code was a call to
GetComponentInChildren - which explicitly only returns components from active GameObjects. The call was returning nothing, because the object wasn't going to be active until the end of the frame.
The available solutions were to introduce a frame's delay (which gives a whole slew of new problems), switch the call to
GetComponentsInChildren (which has an override that will include results from inactive objects, but that can get messy in a complex scene), or continue using the obsolete call. For the sake of not breaking existing code, I had to stick with the obsolete call.
This highlights why backward compatibility in an API can be massively important - and why it's doubly important to have the behaviours of a public API well-documented: subtle changes can have huge effects!