Warning: GetComponentInParent works differently between scene and asset objects

I wanted to share with the community a huge bug I found. I filed a case for it but I don’t expect to be fixed because it is so fundamental it would affect existing projects. I’m sharing it here so others will know to be on the lookout.

According to the documentation Unity - Scripting API: Component.GetComponentInParent
“Returns the component of Type type in the GameObject or any of its parents.”

While this IS the case with scene objects, it is NOT the case with assets. GetComponentInParent will fail to find the component if it is on the same object when it is assets.

I’ve attached a script that shows the issue. If you add a Rigidbody to an object, the script will pass if the object is in the scene, but fail as an asset.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

static public class GetComponentInParentBug
{
    [MenuItem("GameObject/3D Object/GetComponentInParentTest/Test")]
    private static void TestFromScene()
    {
        GetComponentInParentBugTest();
    }


    [MenuItem("Assets/GetComponentInParentTest/Test", false)]
    private static void TestFromProject()
    {
        GetComponentInParentBugTest();
    }
    private static void GetComponentInParentBugTest()
    {
        UnityEngine.Object[] selectedObjects =
            Selection.GetFiltered(typeof(UnityEngine.GameObject), SelectionMode.Assets);

        foreach (GameObject selectedObject in selectedObjects)
        {
            Rigidbody parentRb = selectedObject.GetComponentInParent<Rigidbody>();
            Rigidbody thisRb = selectedObject.GetComponent<Rigidbody>();

            if (parentRb == null && thisRb != null && thisRb.gameObject == selectedObject)
            {
                Debug.LogError("GetComponentInParent<Rigidbody>() failed but GetComponent<Rigidbody>() succeeded on the same object!", selectedObject);
            }
            else
            {
                Debug.Log("All is good", selectedObject);
            }
        }
    }
}

GameObject Assets are treated as inactive, so GetComponentInParent behaves the same way on an Asset as it does on a inactive GameObject in the scene. (I’m not quite sure where this is mentioned in the manual, and I do understand the confusion.)

If you deactivate a GameObject in the scene, you’ll find the behavior is the same there.

Or, you can pass true to the GetComponentInParent method to make it also consider inactive objects, and you’ll find it works fine for Assets.

This argument exists for GetComponentInChildren only, not for GetComponentInParent.
https://docs.unity3d.com/2020.1/Documentation/ScriptReference/Component.GetComponentInParent.html

2 Likes

Why? That seems unexpected.

Because active objects would add stuff to the physics scene if they have physics components and get picked up by the rendering if the have a Renderer and so on.

… What.

You’re saying that we have to pretend that prefabs can meaningfully be active/not active, because otherwise Unity would just dump them into the game?

Prefab assets shouldn’t have an active flag! Why can stuff be added to the physics scene or the renderer without being in a scene to begin with?

I mean I’m a fan of most of what you did with nested prefabs, but it does sound like what you should have done instead was to burn everything relating to prefabs to the ground and made it from scratch, because right now you’re describing a pile of ancient, leaky abstractions and bad ideas.

No, you don’t have to pretend they can be active. Objects that are persistent are inactive no matter the state of the active flag.

Since prefabs are just a collection of GameObjects they have all the same serialized state as regular GameObjects including the active flag, so burning existings prefabs to the ground and starting over would have changing a lot more than just how prefabs works.