The code below is a generic form of code that is running in development. The base class has the correct strings in all cases. The XML loads during parse XML and sets the strings. But the Unity objects only display the data on pass #1 when the contents are initially empty. On the second and all subsequent passes the code never throws any errors and will not draw anything except the unmodified Resource file. All object references are valid but they are not the correct objects… So I guess I am a bit confused as to why GetComponent in children is finding objects… but they appear to be impostors. Any ideas of how to find the real parents and children? Thanks for the help.
public class Class1 : MonoBehaviour, IClass1
{
public string Str1 { get { } set { } }
public string Str2 { get { } set { } }
protected virtual void SetData() { }
public void ParseXML(XmlNode n)
{
//...
// set Str1 and Str2
SetData();
}
}
public class Class2 : Class1
{
protected override void SetData()
{
Debug.Log("Data: " + Str1 + " " + Str2);
image = transform.FindChild("SomeImage").gameObject;
image.SetActive(false);
panel = transform.FindChild("SomePanel").gameObject;
toggletext = panel.GetComponentInChildren<Text>();
if (toggletext != null)
toggletext.text = Str1 + " " + Str2;
check = panel.GetComponentInChildren<Toggle>();
if (check != null)
check.isOn = false;
}
}
public class Class3 : MonoBehaviour
{
private Object padLock = new Object();
private List<GameObject> slots;
public void CreateList()
{
lock (padLock)
{
ClearList();
}
foreach (XmlNode item in items)
{
GameObject slot = Instantiate(Resources.Load("Panels/Slot"), Vector3.zero, Quaternion.identity) as GameObject;
lock (padLock)
{
slots.Add(slot);
}
slot.transform.SetParent(Contents.transform);
GameObject s = Instantiate(Resources.Load("Panels/Item"), Vector3.zero, Quaternion.identity) as GameObject;
s.transform.SetParent(slot.transform);
IClass1 ic = s.GetComponent(typeof(IClass1)) as IClass1;
if (ic != null)
{
ic.ParseXML(item);
}
}
}
#region Implementation
private void ClearList()
{
Contents.DetachChildren(); // TransformRect with items...
slots = new List<GameObject>();
}
#endregion
}
The objects are created and accept all method calls. Respond to Debug.Log and throw no errors when called, so I believe they are valid objects and of the right type. But the rendered screen shows the information contained in the resource, also a valid Instantiated object, which was not changed by the assignment of the strings or the SetActive(false) call. But they all ran and worked just not on the objects that are the objects being drawn on the screen.
What may help you pin this down would be to use Debug.Log’s optional parameter, which allows you to pass in any Unity object; when you click on that debug message in the console, that object will be highlighted. If you really do have ‘imposters’, this will point you right to them.
Here is the result of the test… not sure how this points me to the impostors. When I put the Object in the optional parameter it Logs “GameObject” so I know the type but which instance it is is a mystery.
string chain = "";
GameObject go = gameObject;
while (go != null)
{
chain += go.name + "\n";
if (go.transform.parent == null)
break;
go = go.transform.parent.gameObject;
}
Debug.Log(chain);
I see “GOOD” objects. The type is correct and the calls all work. But they don’t draw correctly after the first pass. That would indicate to me that the object I am changing is not in the call chain for Update… or Render. So which GameObject is actually returned by lines 23-29 in the example?
Lines 51 and 54 set the parents… unless that is a message and does not apply until an update?
So here is proof of a bug… probably it is mine. The last 2 lines of the console are Soldier_2 the latest Instance of the Soldier Object and the Name of the Object actually being rendered to the screen in the last line of the console “Soldier_0”. Does SetParent actually set all the dependencies and muster the objects into the video render pipeline. Or is that some other call that I should also make…?
The objects are mustered into the video render pipeline just by being active, having enabled Renderers, and existing in the scene within the view of a Camera; in other words, it’s automatic.
The word “object” doesn’t. The way that helps you is that it highlights the object (if it still exists) - like literally whatever you passed into that function will turn yellow and “pop” in the editor’s UI, wherever it is, when you click on the debug. If that’s not happening, then whatever you passed into it no longer existed when you clicked on it.
Instantiate object and name it the Prefab name + pass of the code.
Assign it to the container as the parent.
Set the Child components text and active state.
Render the object successfully on pass 1
detach the children of the container.
goto step 1 but fail at the second pass of step 4… (so it is a bug, if it is mine does anyone see it?)
I will try and code around this. But lifecycle problems like this seem like they would lead to crash bugs if they are not fixed. So though this is an odd case it seems like it should be dug into.
Or does that mean that Line 66 DetachChildren() is only a suggestion… unlike Clear() on a list which clears it.
int children = Contents.childCount;
for (int i = 0; i < children; i++)
{
DestroyImmediate(Contents.GetChild(i).gameObject);
}
Replaced the DetachChildren for the above code. It errors on only “1” pass with a null reference and then runs without an null reference but with all the lifecycle problems still there. …
[Unrelated comment by an old guy] I used to walk down the hall into Andy Hollis’s office and just ask him why the transform was behaving weird… same with Darryl Dennis. [End old guy comment]
class Utility
{
static public T GetComponent<T>(GameObject m)
{
T result = default(T);
for (int i = 0; i < m.transform.childCount; i++)
{
result = m.transform.GetChild(i).GetComponent<T>();
if (result != null)
break;
}
return result;
}
}
This fixes the problem…
So here is the core assumption in Unity that causes problems with standard data binding techniques. Unity assumes that an inactive object should not be returned when it is a child of an inactive object. I assume that this has something to do with optimizing render behavior. So when I was setting the Data the GetComponentInChildren() returned null because the object was still inactive even though it existed. I inadvertently masked the problem by protecting against a null return, so the object did not set the values and did not error on a null return. I simply missed the null.
Why this is a problem for design patterns that assume creation of objects to match the data received from a remote source is: the contents are not known at design time; binding happens in response to a message from a communication event so the response is not synchronous but rather asynchronous and therefore won’t bind in sequence with the object construction; delayed data binding is a normal MMO problem and the assumption that the object is active when the state changes is not always true.
There’s the GetComponentsInChildren(Type t, bool includeInactive) method to get around that. For some reason, the GetComponentInChildren(Type t), the GetComponentInChildren() and the GetComponentsInChildren() does not have a version with that bool parameter.
I made a post about including those on Feedback, but it got no votes. It’s a major problem with the Feedback page - small quality-of-life code improvements are not flashy enough to have people spend one of their 10 allocated votes on. The Extension method is also very easy to write, and you can do interesting things with them, so it’s not all bad.
Example
public static class ComponentExtension {
/// <summary>
/// Implementation of GetComponentInChildren that allows you to include inactive objects, and not check self
/// </summary>
public static T GetComponentInChildren<T>(this Component comp, bool includeInactive, bool checkSelf = true) where T : Component {
if (!includeInactive && checkSelf)
return comp.GetComponentInChildren<T>();
if (checkSelf) {
T t = comp.GetComponent<T>();
if (t != null)
return t;
}
foreach (Transform child in comp.transform) {
if (!includeInactive && !child.gameObject.activeSelf)
continue;
T t = child.GetComponent<T>();
if (t != null)
return t;
}
foreach (Transform child in comp.transform) {
if (!includeInactive && !child.gameObject.activeSelf)
continue;
T t = child.GetComponentInChildren<T>(includeInactive, false);
if (t != null)
return t;
}
return null;
}
}