Unity3D 防止添加某些内置组件

Posted

技术标签:

【中文标题】Unity3D 防止添加某些内置组件【英文标题】:Unity3D Prevent adding certain built-in components 【发布时间】:2018-06-01 23:41:42 【问题描述】:

我自己制作的组件与一些 Unity 内置组件冲突(例如刚体与 Rigidbody2D 冲突)。所以我需要确保这些组件不会一起存在于同一个 GameObject 中。有没有办法做到这一点?似乎很容易检查何时添加了我自己的组件(Reset),但是如果添加了 Unity 的内置组件怎么办?新组件附加到游戏对象时是否发送了一些回调、消息或事件?

精度

我不需要在编辑器中隐藏它的组件,或者阻止添加我自己的组件。我问的是在我的组件附加时防止添加某些 Unity 的 内置 组件。来自编辑器 GUI(通过“add component”按钮)和 Unity API(通过GameObject.AddComponent)。

【问题讨论】:

你需要在运行时做这个检查吗? @Ambo100 在运行时和编辑器中 副本解释了如何做到这一点。 @Programmer 我不需要在编辑器中隐藏它的组件。我需要一个解决方案来防止在附加组件时添加某些 Unity 的内置组件。来自编辑器 GUI(通过“添加组件”按钮)和 Unity API(通过 GameObject.AddComponent)。 隐藏组件是另一个问题的部分,但不是那里的主要问题。主要问题(见标题)是关于防止向游戏对象添加组件。请在那里阅读我的答案的第二部分。 【参考方案1】:

[DisallowMultipleComponent] 属性可以防止将两个相同类型的对象添加到同一个游戏对象中。这也适用于子类型(这就是 Rigidbody 和 Rigidbody2d 的处理方式)。

我不确定这是否适合您,因为您没有说您的组件彼此相关,但这是我能找到的。

【讨论】:

是的,这是一个不错的属性,但它无济于事,我的组件不是从内置派生的(而且不能,它们大部分都是密封的)。并且此属性不用于解决 RigidBody 和 RigidBody2D 之间的冲突,因为它仅适用于类及其派生类(它们都是从 Component 派生的,并且对象可以有多个组件,例如 Transform 是从 Component 派生的)。如果您会注意到,当您尝试添加 RigidBody 而已添加 RigidBody2D 时,消息会有所不同。 Unity 清楚地将其声明为冲突,这就是我需要重现的内容。 @VictorF 唉,这是我能找到的唯一接近的信息。【参考方案2】:

新组件时是否有一些回调、消息或事件发送 附加到游戏对象?

没有。

有办法吗?

是的,但有点复杂。

如果您想阻止添加自定义脚本,这很容易,this question 应该可以解决这个问题。

这很复杂,因为您想防止将另一个人(内置)编写的组件添加到 GameObject,这意味着您首先需要一种方法来检测该组件何时已添加到 @987654323 @ 然后销毁它。 这必须在每一帧都完成(在编辑器中和运行时)。

您可以将不想添加的组件调用到GameObject黑名单组件

步骤如下:

1.将列入黑名单的组件存储在一个数组中。

private static Type[] blacklistedComponents =

    typeof(Rigidbody),
    typeof(Rigidbody2D)
    //...
;

2.获取场景中的根GameObjects并将它们存储在List中。

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

3.循环遍历每个根GameObject 并使用GetComponentsInChildren 获取附加到该根GameObject 下每个GameObject 的所有组件。

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

4.在#3的循环中,遍历检索到的组件并检查它是否有任何列入黑名单的组件。如果是,则销毁该列入黑名单的组件。

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");
        
    

就是这样。您或其他人可能对此答案有一些疑问。

Q 1想知道为什么不使用FindObjectsOfTypeFindObjectsOfTypeAll

A 1。这些函数通常用于简化获取场景中的所有内容,但问题是它们返回数组。每帧调用这些函数会降低游戏性能,因为它会分配内存并导致垃圾收集器更频繁地运行。

这就是使用Scene.GetRootGameObjects 的原因,您可以在其中传递List,它会为您填写列表。它不返回数组。


Q 2为什么你将 List 传递给 GetComponentsInChildren 却没有返回结果?

A 2。技术上与我上面解释的原因相同。我使用了不分配内存的GetComponentsInChildren 函数版本。只需将List 传递给它,它就会用它找到的每个组件填充它。这可以防止它返回一个昂贵的数组。


我在下面为此编写了一个完整的工作代码,但您需要改进它。这就是为什么我解释了每个过程,以便您可以自己改进或重写。它目前阻止从编辑器或编辑器中的代码或构建中添加RigidbodyRigidbody2D。您可以将更多要阻止的组件添加到blacklistedComponents 变量。它也在运行时在编辑器中运行。 UNITY_EDITOR 用于删除编辑器代码并确保它可以针对平台进行编译。

1。创建一个名为ComponentDetector 的脚本并将下面的所有代码复制到其中。

2.保存并返回编辑器。而已。您不必将其附加到任何对象。 您永远不能将RigidbodyRigidbody2D 添加到任何游戏对象中。

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

【讨论】:

“我问的是在我的组件附加时防止添加某些 Unity 的内置组件” 我专注于这个问题的标题,以便这个问题可以可重用于其他用户,但您可以通过此答案轻松做到这一点。如果您阅读此答案,我将这部分留给您,这很容易。当在if (targetComponents[i].GetType() == blacklistedList[j])这行代码中找到被列入黑名单的组件时,你可以使用GetComponent来检查当前targetComponents是否有你的Rigidbody和你的CustomRigidbody然后销毁它。 只需添加if (targetComponents[i].GetComponent&lt;Rigidbody&gt;() &amp;&amp; targetComponents[i].GetComponent&lt;CustomRigidbody&gt;()) //Destroy Component DestroyImmediate(targetComponents[i]); inside if (targetComponents[i].GetType() == blacklistedList[j])。此外,将所有旧代码和 Debug 语句移到新的 if 语句中。而已。你可以添加尽可能多的else if 语句来覆盖你所有的if 组件。 您的第二条评论无效,只是咆哮。 1.该答案中的代码不会一次返回所有游戏对象或所有组件。它返回根游戏对象,它只是存储游戏对象,它是一个对象,而不是图像或大量数据。 2.遍历每个root GameObject,并在每个循环中获取所有子组件。它不会一次获取场景中的所有组件。资源应该不是问题。 3.它甚至不返回一个数组。它使用列表来填充对象。我的回答中提到了所有这些,但您没有阅读它们。 4.它适用于编辑器和运行时。你只是决定随意发表评论。最后,它不存储 bazillion 的对象/组件。它从每个根对象获取很少,对其执行操作,重置并移动到下一个对象。如果我想一次获得所有对象,我会使用Resources.FindObjectsOfTypeAllFindObjectsOfType,我在回答中明确提到我不想在回答中这样做。再次阅读答案,因为我确实做到了,而不仅仅是代码!使用我的第二条评论在第一条评论中获得您想要的内容。 我没有在该代码中使用递归。遍历列表/数组不等于执行递归。你可能应该谷歌“递归”。同时获取所有组件/对象需要使用分配内存的函数。分配保存结果所需的内存量将减慢循环并增加“处理器滴答声”。此外,它会导致 GC 运行导致您的游戏偶尔暂时冻结。我在回答中提到了 GC。再说一次,你没有读过。【参考方案3】:

如果您尝试确定组件在运行时之前是否存在,我假设您已经知道,您可以使用Start() 方法。但是,就像我说的,我假设您已经知道这一点,因此在运行时检查类似内容的唯一其他方法是在每一帧中不断地在 Update() 方法中检查它。虽然,我不确定为什么在运行时将 Unity 组件添加到游戏对象中。如果这是一个问题,是否可以将相关组件添加到子游戏对象或父游戏对象中?

如果您真的想进行一些更改,这可能需要对您的项目进行大量重构,您始终可以创建一个 ComponentManager 类来处理向游戏对象添加和删除组件并创建您自己的回调。

【讨论】:

以上是关于Unity3D 防止添加某些内置组件的主要内容,如果未能解决你的问题,请参考以下文章

使用 Unity3D 进行 Ninject

Unity3D 在自定义脚本中实现Button组件上的OnClick面板

unity3d 怎么用脚本控制播放制定音乐文件

unity3d怎么做碰撞?unity3d如何添加碰撞体?求解!

unity3d动态操作组件

如何防止汽车在使用MapBox地图的Unity3D驾驶模拟中偏离?