Comprueba si el componente de un objeto de juego puede destruirse

11

Al desarrollar un juego en Unity, estoy haciendo un uso liberal [RequireComponent(typeof(%ComponentType%))]para asegurar que los componentes tengan todas las dependencias satisfechas.

Ahora estoy implementando un sistema tutorial que destaca varios objetos de la interfaz de usuario. Para resaltar, tomo una referencia a la GameObjectescena, luego la clono con Instantiate () y luego elimino recursivamente todos los componentes que no son necesarios para mostrar. El problema es que con el uso liberal de RequireComponentestos componentes, muchos no pueden eliminarse simplemente en cualquier orden, ya que dependen de otros componentes que aún no se han eliminado.

Mi pregunta es: ¿Hay alguna manera de determinar si Componentse puede eliminar un archivo al GameObjectque está conectado actualmente?

El código que estoy publicando técnicamente funciona, sin embargo, arroja errores de Unity (no atrapados en el bloque try-catch, como se ve en la imagen

ingrese la descripción de la imagen aquí

Aquí está el código:

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

Entonces, la forma en que este código generó las últimas tres líneas del registro de depuración anterior es la siguiente: tomó como entrada a GameObjectcon dos componentes: Buttony PopupOpener. PopupOpenerrequiere que un Buttoncomponente esté presente en el mismo GameObject con:

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

La primera iteración del bucle while (denotado por el texto "ITERATION ON Button Invite (clone)") intentó eliminar el Buttonprimero, pero no pudo, ya que depende del PopupOpenercomponente. Esto arrojó un error. Luego intentó eliminar el PopupOpenercomponente, lo que hizo, ya que no depende de nada más. En la siguiente iteración (Denotado por el segundo texto "ITERATION ON Button Invite (clone)"), intentó eliminar el Buttoncomponente restante , lo que ahora podría hacer, ya que PopupOpenerse eliminó en la primera iteración (después del error).

Entonces, mi pregunta es si es posible verificar con anticipación si un componente específico se puede eliminar de su actual GameObject, sin invocar Destroy()o DestroyImmediate(). Gracias.

Liam Lime
fuente
Sobre el problema original: ¿el objetivo es hacer una copia no interactiva (= visual) de los elementos de la GUI?
wondra
Si. El objetivo es hacer una copia idéntica, no interactiva en la misma posición, escalada de la misma manera, mostrando el mismo texto que el objeto de juego real debajo. La razón por la que utilicé este método es para que el tutorial no necesite modificaciones si la interfaz de usuario se editó en un momento posterior (lo que tendría que suceder si mostrara capturas de pantalla).
Liam Lime
No tengo experiencia con Unity, pero me pregunto si, en lugar de clonar todo y eliminar cosas, ¿podría copiar las piezas que necesita?
Zan Lynx
No lo he probado, pero Destroyelimina el componente al final del marco (en lugar de Immediately). DestroyImmediateDe todos modos, se considera un mal estilo, pero estoy pensando que cambiar Destroypodría eliminar estos errores.
CAD97
Intenté ambos, comenzando con Destroy (ya que esa es la forma correcta) y luego cambié a DestroyImmediate para ver si funcionaría. La destrucción todavía parece ir en el orden especificado, lo que provoca las mismas excepciones, justo al final del marco.
Liam Lime

Respuestas:

9

Definitivamente es posible, pero requiere bastante trabajo preliminar: puede verificar si hay un Componentadjunto al GameObjectque tiene un Attributetipo RequireComponentque tiene uno de sus m_Type#campos asignables al tipo de componente que está a punto de eliminar. Todo envuelto en un método de extensión:

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

después de soltar GameObjectExtensionsen cualquier parte del proyecto (o, alternativamente, convertirlo en un método regular en el script), puede verificar si un componente puede eliminarse, por ejemplo:

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

Sin embargo, puede haber otros medios para crear un objeto GUI no interactivo: por ejemplo, convertirlo en textura, superponerlo con un panel casi transparente que interceptará los clics o deshabilitará (en lugar de destruir) todo lo que se Componentderive de MonoBehaviouro IPointerClickHandler.

wondra
fuente
¡Gracias! Me preguntaba si una solución preparada se envía con Unity, pero supongo que no :) ¡Gracias por su código!
Liam Lime
@LiamLime Bueno, realmente no puedo confirmar que no haya ninguno incorporado, pero es poco probable ya que el paradigma típico de Unity está haciendo que el código sea tolerante a fallas (es decir catch, registrar, continuar) en lugar de prevenir fallas (es decir if, posible, entonces). Sin embargo, eso no es mucho estilo C # (como uno en esta respuesta). Al final, su código original podría haber estado más cerca de cómo se hacen las cosas normalmente ... y la mejor práctica es cuestión de preferencia personal.
wondra
7

En lugar de eliminar los componentes del clon, puede deshabilitarlos configurándolos .enabled = false. Esto no activará las comprobaciones de dependencia, ya que no es necesario habilitar un componente para cumplir con la dependencia.

  • Cuando un comportamiento está deshabilitado, seguirá estando en el objeto e incluso puede cambiar sus propiedades y llamar a sus métodos, pero el motor ya no llamará a su Updatemétodo.
  • Cuando un Renderer está deshabilitado, el objeto se vuelve invisible.
  • Cuando un Collider está desactivado, no provocará ningún evento de colisión.
  • Cuando un componente de la interfaz de usuario está deshabilitado, el jugador ya no puede interactuar con él, pero aún se renderizará, a menos que también desactive su procesador.
Philipp
fuente
Los componentes no tienen una noción de habilitado o deshabilitado. Comportamientos, colisionadores y algunos otros tipos hacen. Por lo tanto, esto requeriría que el programador realice varias llamadas a GetComponent para las distintas subclases.
DubiousPusher