Reputation: 373
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
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
Reputation: 15941
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
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