Überprüfen Sie, ob die Komponente eines Spielobjekts zerstört werden kann

11

Ich entwickle ein Spiel in Unity und nutze es liberal, [RequireComponent(typeof(%ComponentType%))]um sicherzustellen, dass die Komponenten alle Abhängigkeiten erfüllen.

Ich implementiere jetzt ein Tutorial-System, das verschiedene UI-Objekte hervorhebt. Um die Hervorhebung durchzuführen, verweise ich auf die GameObjectin der Szene, klone sie dann mit Instantiate () und entferne rekursiv alle Komponenten, die für die Anzeige nicht benötigt werden. Das Problem ist, dass bei der liberalen Verwendung RequireComponentdieser Komponenten viele nicht einfach in beliebiger Reihenfolge entfernt werden können, da sie von anderen Komponenten abhängen, die noch nicht gelöscht wurden.

Meine Frage ist: Gibt es eine Möglichkeit festzustellen, ob ein Componentaus dem GameObjectaktuell angehängten entfernt werden kann?

Der Code, den ich veröffentliche, funktioniert technisch, löst jedoch Unity-Fehler aus (nicht im Try-Catch-Block abgefangen, wie im Bild dargestellt)

Geben Sie hier die Bildbeschreibung ein

Hier ist der Code:

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

Die Art und Weise, wie dieser Code die letzten drei Zeilen des obigen Debug-Protokolls generiert hat, ist die folgende: Als Eingabe wurde a GameObjectmit zwei Komponenten verwendet: Buttonund PopupOpener. PopupOpenererfordert, dass eine ButtonKomponente in demselben GameObject vorhanden ist mit:

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

Bei der ersten Iteration der while-Schleife (gekennzeichnet durch den Text "ITERATION ON Button Invite (Klon)") wurde versucht, die Buttonerste zu löschen , dies konnte jedoch nicht, da dies von der PopupOpenerKomponente abhängt . Dies warf einen Fehler. Dann wurde versucht, die PopupOpenerKomponente zu löschen , was auch der Fall war, da sie von nichts anderem abhängig ist. In der nächsten Iteration (gekennzeichnet durch den zweiten Text "ITERATION ON Button Invite (Klon)") wurde versucht, die verbleibende ButtonKomponente zu löschen , was jetzt möglich war, da PopupOpenersie in der ersten Iteration (nach dem Fehler) gelöscht wurde.

Meine Frage ist also, ob es möglich ist, im Voraus zu prüfen, ob eine bestimmte Komponente aus ihrer aktuellen entfernt werden kann GameObject, ohne Destroy()oder aufzurufen DestroyImmediate(). Vielen Dank.

Liam Lime
quelle
Über das ursprüngliche Problem - ist das Ziel, eine nicht interaktive Kopie (= visuell) von GUI-Elementen zu erstellen?
Wondra
Ja. Das Ziel ist es, eine identische, nicht interaktive Kopie an derselben Position zu erstellen, die auf dieselbe Weise skaliert ist und denselben Text wie das eigentliche Spielobjekt darunter zeigt. Der Grund, warum ich mich für diese Methode entschieden habe, ist, dass das Tutorial keine Änderungen benötigt, wenn die Benutzeroberfläche zu einem späteren Zeitpunkt bearbeitet wurde (was passieren müsste, wenn ich Screenshots zeigen würde).
Liam Lime
Ich habe keine Erfahrung mit Unity, aber ich frage mich, ob Sie, anstatt das Ganze zu klonen und Dinge zu entfernen, nur die Teile kopieren könnten, die Sie benötigen?
Zan Lynx
Ich habe es nicht getestet, Destroyentferne aber die Komponente am Ende des Rahmens (im Gegensatz zu Immediately). DestroyImmediatewird sowieso als schlechter Stil angesehen, aber ich denke, ein Wechsel zu Destroykann diese Fehler tatsächlich beseitigen.
CAD97
Ich habe beide ausprobiert, angefangen mit Destroy (da dies der richtige Weg ist) und dann zu DestroyImmediate gewechselt, um zu sehen, ob es funktionieren würde. Destroy scheint immer noch in der angegebenen Reihenfolge zu sein, was die gleichen Ausnahmen verursacht, nur am Ende des Frames.
Liam Lime

Antworten:

9

Es ist definitiv möglich, aber es erfordert ziemlich viel Beinarbeit: Sie können überprüfen, ob Componentan das angehängt ist, GameObjectdas einen AttributeTyp RequireComponenthat, dessen m_Type#Felder dem Komponententyp zugeordnet werden können, den Sie entfernen möchten. Alles in einer Erweiterungsmethode verpackt:

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

Nachdem Sie GameObjectExtensionsirgendwo im Projekt abgelegt haben (oder es alternativ zu einer regulären Methode im Skript gemacht haben), können Sie überprüfen, ob eine Komponente entfernt werden kann, z.

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

Es kann jedoch auch andere Mittel geben, um ein nicht interaktives GUI-Objekt zu erstellen - beispielsweise das Rendern in eine Textur, das Überlagern mit einem fast transparenten Bedienfeld, das Klicks abfängt oder alle von oder abgeleiteten Objekte deaktiviert (anstatt sie zu zerstören) .ComponentMonoBehaviourIPointerClickHandler

wondra
quelle
Vielen Dank! Ich habe mich gefragt, ob eine fertige Lösung mit Unity geliefert wird, aber ich denke nicht :) Vielen Dank für Ihren Code!
Liam Lime
@LiamLime Nun, ich kann nicht wirklich bestätigen, dass es keine eingebauten gibt, aber es ist unwahrscheinlich, da das typische Unity-Paradigma Code fehlertolerant macht (dh catchprotokollieren, fortfahren), anstatt Fehler zu verhindern (dh dann ifmöglich). Das ist jedoch nicht viel C # -Stil (wie einer in dieser Antwort). Am Ende war Ihr ursprünglicher Code möglicherweise näher an der typischen Vorgehensweise ... und bewährte Verfahren sind eine Frage der persönlichen Präferenz.
Wondra
7

Anstatt die Komponenten auf dem Klon zu entfernen, können Sie sie einfach deaktivieren, indem Sie sie festlegen .enabled = false. Dadurch werden die Abhängigkeitsprüfungen nicht ausgelöst, da eine Komponente nicht aktiviert werden muss, um die Abhängigkeit zu erfüllen.

  • Wenn ein Verhalten deaktiviert ist, befindet es sich weiterhin im Objekt, und Sie können sogar seine Eigenschaften ändern und seine Methoden aufrufen, aber die Engine ruft seine UpdateMethode nicht mehr auf .
  • Wenn ein Renderer deaktiviert ist, wird das Objekt unsichtbar.
  • Wenn ein Collider deaktiviert ist, treten keine Kollisionsereignisse auf.
  • Wenn eine UI-Komponente deaktiviert ist, kann der Player nicht mehr mit ihr interagieren, sie wird jedoch weiterhin gerendert, es sei denn, Sie deaktivieren auch den Renderer.
Philipp
quelle
Komponenten haben keine Vorstellung von aktiviert oder deaktiviert. Verhaltensweisen, Collider und einige andere Typen tun dies. Dies würde also erfordern, dass der Programmierer GetComponent für die verschiedenen Unterklassen mehrfach aufruft.
DubiousPusher