Victor
Victor

Reputation: 373

Unity3D Prevent adding certain built-in components

I have made my own component which conflicts with some Unity built-in components (like Rigidbody conflicts with Rigidbody2D). So I need to be sure that those components will not exist together in the same GameObject. Is there a way to do it? It seems to be easy to check when my own component is added (by Reset), but what to do if Unity' built-in component is added? Is there some callback, message, or event sent when new component attached to the GameObject?

Precisions

I do not need to hide components it in the editor, or prevent adding my own components. I am asking about preventing adding certain Unity' built-in components while my component is attached. From both Editor GUI (by "add component" button) and Unity API (by GameObject.AddComponent).

Upvotes: 2

Views: 1490

Answers (3)

Programmer
Programmer

Reputation: 125245

Is there some callback, message, or event sent when new component attached to the GameObject?

No.

Is there a way to do it?

Yes, but a bit complicated.

If you want to prevent your custom script from being added, that would have been easy and this question should handle that.

This is complicated because you want to prevent a component written by another person(built-in) from being added to a GameObject which means that you first need a way to detect when that component has been added to a GameObject then destroy it. This has to be done every frame (Both in the Editor and during run-time).

You can call the components you don't want to be added to a GameObject blacklisted components.

Here are the steps:

1.Store the blacklisted components in an array.

private static Type[] blacklistedComponents =
{
    typeof(Rigidbody),
    typeof(Rigidbody2D)
    //...
};

2.Get the root GameObjects in the scene and store them in a List.

private static List<GameObject> rootGameObjects = new List<GameObject>();
Scene.GetRootGameObjects(rootGameObjects);

3.Loop through each root GameObject and use GetComponentsInChildren to get all the components attached to each GameObject under that root GameObject.

private static List<Component> allComponents = new List<Component>();
currentLoopRoot.GetComponentsInChildren<Component>(true, allComponents);

4.During the loop from #3, loop through the retrieved components and check if it has any blacklisted component. If it does, destroy that blacklisted component.

for (int i = 0; i < allComponents.Count; i++)
{
    //Loop through each blacklisted Component and see if it is present
    for (int j = 0; j < blacklistedComponents.Length; j++)
    {
        if (allComponents[i].GetType() == blacklistedComponents[j])
        {
            Debug.Log("Found Blacklisted Component: " + targetComponents[i].GetType().Name);
            Debug.Log("Removing Blacklisted Component");
            //Destroy Component
            DestroyImmediate(allComponents[i]);

            Debug.LogWarning("This component is now destroyed");
        }
    }
}

That's it. You or others may have few questions about this answer.

Q 1.Wonder why FindObjectsOfType and FindObjectsOfTypeAll are not used?

A 1.These functions are usually used to simplify getting everything in the scene but the problem is that they return array. Calling these functions every frame will kill your game performance since it allocates memory and will cause garbage collector to run more often.

This is why Scene.GetRootGameObjects is used which you can pass a List inside it and it will fill the list for you. It does not return array.


Q 2.Why did you pass List to GetComponentsInChildren and not return the result from it?

A 2. Technically the-same reason I explained above. I used a version of the GetComponentsInChildren function that does not allocate memory. Simply pass List to it and it will fill it up with every component it found. This prevents it from returning an array which is expensive.


I wrote a complete working code for this below but you need to improve it. That's why I explained every process so that you can either improve or rewrite it yourself. It currently prevents Rigidbody and Rigidbody2D from being added from the Editor or from code in the Editor or in a build.You can add more components you want to block to the blacklistedComponents variable. It runs in the Editor also during runtime. UNITY_EDITOR is used to remove the Editor codes and make sure that it compiles for platforms.

1.Create a script called ComponentDetector and copy every code below into it.

2.Save and go back to the Editor. That's it. You don't have to attach it to any Object. You should never be able to add Rigidbody and Rigidbody2D to any GameObject.

using System.Collections.Generic;
using UnityEngine;
using System;
using UnityEngine.SceneManagement;

#if UNITY_EDITOR
using UnityEditor;
#endif

public class ComponentDetector : MonoBehaviour
{
    //Add the blacklisted Components here
    private static Type[] blacklistedComponents =
        {
        typeof(Rigidbody),
        typeof(Rigidbody2D)
        //...
    };

    private static List<Component> allComponents = new List<Component>();
    private static List<GameObject> rootGameObjects = new List<GameObject>();

    private static void GetAllRootObject()
    {
        Scene activeScene = SceneManager.GetActiveScene();
        activeScene.GetRootGameObjects(rootGameObjects);
    }

    private static void GetAllComponentsAndCheckIfBlacklisted()
    {
        for (int i = 0; i < rootGameObjects.Count; ++i)
        {
            GameObject obj = rootGameObjects[i];
            //Debug.Log(obj.name);

            //Get all child components attached to this GameObject
            obj.GetComponentsInChildren<Component>(true, allComponents);

            //Remove component if present in the blacklist array
            RemoveComponentIfBlacklisted(allComponents, blacklistedComponents);
        }


    }

    private static void RemoveComponentIfBlacklisted(List<Component> targetComponents, Type[] blacklistedList)
    {
        //Loop through each target Component
        for (int i = 0; i < targetComponents.Count; i++)
        {
            //Debug.Log(targetComponents[i].GetType());
            //Loop through each blacklisted Component and see if it is present
            for (int j = 0; j < blacklistedList.Length; j++)
            {
                if (targetComponents[i].GetType() == blacklistedList[j])
                {
                    Debug.Log("Found Blacklisted Component: " + targetComponents[i].GetType().Name);
                    Debug.LogError("You are not allowed to add the " + targetComponents[i].GetType().Name + " component to a GameObject");
                    Debug.Log("Removing Blacklisted Component");
                    //Destroy Component
                    DestroyImmediate(targetComponents[i]);

                    Debug.LogWarning("This component is now destroyed");
                }
            }
        }
    }

    public static void SearchAndRemoveblacklistedComponents()
    {
        //Get all root GameObjects
        GetAllRootObject();

        //Get all child components attached to each GameObject and remove them
        GetAllComponentsAndCheckIfBlacklisted();
    }

    void Awake()
    {
        DontDestroyOnLoad(this.gameObject);
    }

    // Update is called once per frame
    void Update()
    {
        //Debug.Log("Update: Run-time");
        SearchAndRemoveblacklistedComponents();
    }
}

#if UNITY_EDITOR
[InitializeOnLoad]
class ComponentDetectorEditor
{
    static ComponentDetectorEditor()
    {
        createComponentDetector();
        EditorApplication.update += Update;
    }

    static void Update()
    {
        //Debug.Log("Update: Editor");
        ComponentDetector.SearchAndRemoveblacklistedComponents();
    }

    static void createComponentDetector()
    {
        GameObject obj = GameObject.Find("___CDetector___");
        if (obj == null)
        {
            obj = new GameObject("___CDetector___");
        }

        //Hide from the Editor
        obj.hideFlags = HideFlags.HideInHierarchy;
        obj.hideFlags = HideFlags.HideInInspector;

        ComponentDetector cd = obj.GetComponent<ComponentDetector>();
        if (cd == null)
        {
            cd = obj.AddComponent<ComponentDetector>();
        }

    }
}
#endif

Upvotes: 1

There is the [DisallowMultipleComponent] attribute which prevents two of the same type from being added to the same game object. This works for subtypes as well (which is how Rigidbody and Rigidbody2d are handled).

I am not sure if this will work for you or not, as you haven't said your components are related to each other, but it is what I can find.

Upvotes: 2

Brandon Miller
Brandon Miller

Reputation: 1584

If you're trying to determine if the component exists before runtime, which I assume you already know, you can use the Start() method. But, like I said, I assume you already know that, so the only other way to check something like that during runtime would be to continually check for it in the Update() method, every frame. Although, I am not sure as to why a Unity component might be added to a gameobject during runtime. If this is an issue, maybe the component in question could be added to a child or parent gameobject instead?

If you really feel like making some changes, which may require a lot of refactoring for your project, you could always create a ComponentManager class that handles adding and removing components to GameObjects and create your own callbacks.

Upvotes: 0

Related Questions