检查游戏对象的组件是否可以销毁


11

在Unity中开发游戏时,我正在大量使用[RequireComponent(typeof(%ComponentType%))]以确保组件满足所有依赖性。

我现在正在实现一个突出各种UI对象的教程系统。要进行突出显示,我要引用GameObject场景中的,然后使用Instantiate()克隆它,然后以递归的方式删除显示不需要的所有组件。问题在于,随着RequireComponent这些组件的广泛使用,许多组件无法简单地以任何顺序删除,因为它们由尚未删除的其他组件所依赖。

我的问题是:有没有一种方法可以确定是否Component可以将a 从GameObject当前连接的对象中删除。

我发布的代码在技术上可以正常工作,但是会引发Unity错误(如图所示,未在try-catch块中捕获)

在此处输入图片说明

这是代码:

public void StripFunctionality(RectTransform rt)
{
    if (rt == null)
    {
        return;
    }

    int componentCount = rt.gameObject.GetComponents<Component>().Length;
    int safety = 10;
    int allowedComponents = 0;
    while (componentCount > allowedComponents && safety > 0)
    {
        Debug.Log("ITERATION ON "+rt.gameObject.name);
        safety--;
        allowedComponents = 0;
        foreach (Component c in rt.gameObject.GetComponents<Component>())
        {
            //Disable clicking on graphics
            if (c is Graphic)
            {
                ((Graphic)c).raycastTarget = false;
            }

            //Remove components we don't want
            if (
                !(c is Image) &&
                !(c is TextMeshProUGUI) &&
                !(c is RectTransform) &&
                !(c is CanvasRenderer)
                )
            {
                try
                {
                    DestroyImmediate(c);
                }
                catch
                {
                    //NoOp
                }
            }
            else
            {
                allowedComponents++;
            }
        }
        componentCount = rt.gameObject.GetComponents<Component>().Length;
    }

    //Recursive call to children
    foreach (RectTransform childRT in rt)
    {
        StripFunctionality(childRT);
    }
}

因此,此代码生成上面调试日志的最后三行的方式如下:将a作为输入GameObject包含两个组件:ButtonPopupOpenerPopupOpener要求Button组件存在于具有以下内容的同一个GameObject中:

[RequireComponent(typeof(Button))]
public class PopupOpener : MonoBehaviour
{
    ...
}

while循环的第一个迭代(由文本“ ITERATION ON Button Invite(clone)”表示)试图删除第Button一个,但是不能删除,因为它取决于PopupOpener组件。这引发了错误。然后,它尝试删除PopupOpener它所做的组件,因为它不受任何其他依赖。在下一个迭代中(由第二个文本“ ITERATION ON Button Invite(clone)”表示),它试图删除Button现在可以执行的剩余组件,因为它PopupOpener是在第一次迭代中(在错误之后)被删除的。

所以我的问题是,是否可以提前检查是否可以在GameObject不调用Destroy()或的情况下将指定的组件从当前组件中删除DestroyImmediate()。谢谢。


关于原始问题-目标是制作GUI元素的非交互式副本吗?
温德拉

是。目标是在相同位置制作相同的,不可交互的副本,以相同的方式缩放,以显示与真实游戏对象相同的文字。我采用这种方法的原因是,如果在以后的某个时间点编辑了UI,那么本教程就不需要修改(如果我显示了屏幕截图,则需要进行修改)。
Liam Lime'5

我没有使用Unity的经验,但我想知道是否可以只复制所需的部分,而不是克隆整个东西并删除内容?
Zan Lynx'5

我还没有测试过,但是Destroy删除了帧末尾的组件(与Immediately 相对)。DestroyImmediate无论如何都被认为是不好的风格,但是我想切换到Destroy实际上可以消除这些错误。
CAD97

我尝试了两种方法,首先从Destroy(因为这是正确的方法)开始,然后切换到DestroyImmediate,看看它是否可以工作。销毁似乎仍然按照指定的顺序进行,这会导致相同的异常,恰好在帧的结尾。
利亚姆·莱姆

Answers:


9

这绝对是可能的,但它需要相当多跑腿:您可以检查是否有Component连接到GameObject它有一个Attribute类型的RequireComponent有它的一个m_Type#领域分配给组件类型您要删除。全部包裹在扩展方法中:

static class GameObjectExtensions
{
    private static bool Requires(Type obj, Type requirement)
    {
        //also check for m_Type1 and m_Type2 if required
        return Attribute.IsDefined(obj, typeof(RequireComponent)) &&
               Attribute.GetCustomAttributes(obj, typeof(RequireComponent)).OfType<RequireComponent>()
               .Any(rc => rc.m_Type0.IsAssignableFrom(requirement));
    }

    internal static bool CanDestroy(this GameObject go, Type t)
    {
        return !go.GetComponents<Component>().Any(c => Requires(c.GetType(), t));
    }
}

将其GameObjectExtensions放到项目中的任何位置后(或使其成为脚本中的常规方法)之后,可以检查是否可以删除组件,例如:

rt.gameObject.CanDestroy(c.getType());

但是,还可以使用其他方法来制作非交互式GUI对象,例如,将其渲染为纹理,并用几乎透明的面板覆盖它,该面板将拦截单击或禁用(而不是破坏)ComponentMonoBehaviour或派生的所有s IPointerClickHandler


谢谢!我想知道Unity是否附带了现成的解决方案,但我想不是:)谢谢您的代码!
利亚姆·莱姆

@LiamLime好吧,我无法真正确认没有内置函数,但是这不太可能,因为典型的Unity范例使代码具有容错能力(例如catch,记录并继续),而不是防止错误发生(即if可能发生)。但是,也就是C#样式不多(例如此答案中的一种)。最后,您的原始代码可能更接近通常的工作方式了……最佳实践是个人喜好问题。
温德拉

7

除了删除克隆上的组件,您还可以通过设置它们来禁用.enabled = false它们。这不会触发依赖关系检查,因为不需要启用组件来实现依赖关系。

  • 禁用“行为”后,它仍将位于对象上,您甚至可以更改其属性并调用其方法,但是引擎将不再调用其Update方法。
  • 禁用渲染器后,对象变为不可见。
  • 禁用对撞机时,不会导致发生任何碰撞事件。
  • 禁用UI组件后,播放器将无法与其进行交互,但是除非您也停用其渲染器,否则它仍将被渲染。

组件没有启用或禁用的概念。行为,碰撞器和其他一些类型也可以。因此,这将要求程序员对各种子类多次调用GetComponent。
DubiousPusher
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.