Controlla se il componente di un oggetto di gioco può essere distrutto


11

Sviluppando un gioco in Unity, sto facendo un uso liberale [RequireComponent(typeof(%ComponentType%))]per garantire che i componenti abbiano soddisfatto tutte le dipendenze.

Ora sto implementando un sistema tutorial che evidenzia vari oggetti dell'interfaccia utente. Per evidenziare, sto prendendo un riferimento a GameObjectnella scena, quindi lo clonio con Instantiate () e quindi rimuovo in modo ricorsivo tutti i componenti che non sono necessari per la visualizzazione. Il problema è che con l'uso liberale di RequireComponentquesti componenti, molti non possono essere semplicemente rimossi in qualsiasi ordine, poiché dipendono da altri componenti, che non sono stati ancora eliminati.

La mia domanda è: esiste un modo per determinare se un Componentpuò essere rimosso dal GameObjectquale è attualmente collegato.

Il codice che sto postando tecnicamente funziona, tuttavia genera errori Unity (non catturati nel blocco try-catch, come mostrato nell'immagine

inserisci qui la descrizione dell'immagine

Ecco il codice:

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

Quindi il modo in cui questo codice ha generato le ultime tre righe del registro di debug sopra è il seguente: Ha preso come input a GameObjectcon due componenti: Buttone PopupOpener. PopupOpenerrichiede che un Buttoncomponente sia presente nello stesso GameObject con:

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

La prima iterazione del ciclo while (indicato dal testo "Invito pulsante ITERATION ON (clone)") ha tentato di eliminare il Buttonprimo, ma non è stato possibile in quanto dipende dal PopupOpenercomponente. questo ha gettato un errore. Quindi ha tentato di eliminare il PopupOpenercomponente, cosa che ha fatto, poiché non dipende da altro. Nella successiva iterazione (indicata dal secondo testo "Invito pulsante ITERATION ON (clone)"), ha tentato di eliminare il Buttoncomponente rimanente , cosa che ora poteva fare, poiché è PopupOpenerstato eliminato nella prima iterazione (dopo l'errore).

Quindi la mia domanda è se è possibile verificare in anticipo se un componente specificato può essere rimosso dalla sua corrente GameObject, senza invocare Destroy()o DestroyImmediate(). Grazie.


Informazioni sul problema originale: l'obiettivo è creare una copia non interattiva (= visiva) degli elementi della GUI?
Wondra,

Sì. L'obiettivo è quello di creare una copia identica e non interagibile nella stessa posizione, ridimensionata allo stesso modo, mostrando lo stesso testo dell'oggetto di gioco reale sottostante. Il motivo per cui ho seguito questo metodo è che il tutorial non avrebbe bisogno di modifiche se l'interfaccia utente fosse modificata in un secondo momento (cosa che dovrebbe accadere se mostrassi degli screenshot).
Liam Lime,

Non ho esperienza con Unity ma mi chiedo se invece di clonare il tutto e rimuovere le cose potresti copiare solo le parti che ti servono?
Zan Lynx,

Non l'ho provato, ma Destroyrimuove il componente alla fine del frame (al contrario di Immediately). DestroyImmediateè comunque considerato un cattivo stile, ma sto pensando che il passaggio a Destroypotrebbe effettivamente eliminare questi errori.
CAD97,

Ho provato entrambi, iniziando con Destroy (poiché quello è il modo corretto) e poi sono passato a DestroyImmediate per vedere se avrebbe funzionato. Distruggere sembra ancora andare nell'ordine specificato, il che provoca le stesse eccezioni, proprio alla fine del fotogramma.
Liam Lime,

Risposte:


9

È sicuramente possibile, ma richiede un sacco di legwork: puoi verificare se è presente un Componentallegato al GameObjectquale ha un Attributetipo RequireComponentche ha uno dei suoi m_Type#campi assegnabile al tipo di componente che stai per rimuovere. Tutto racchiuso in un metodo di estensione:

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

dopo essere caduto in GameObjectExtensionsqualsiasi punto del progetto (o in alternativa renderlo un metodo regolare sullo script), è possibile verificare se un componente può essere rimosso, ad esempio:

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

Tuttavia, ci possono essere altri modi per creare un oggetto GUI non interattivo, ad esempio renderlo in trama, sovrapponendolo a un pannello quasi trasparente che intercetterà i clic o disabiliterà (invece di distruggere) tutti gli Components derivanti da MonoBehaviouro IPointerClickHandler.


Grazie! Mi chiedevo se una soluzione pronta fosse fornita con Unity, ma credo di no :) Grazie per il tuo codice!
Liam Lime

@LiamLime Beh, non posso davvero confermare che non ne sia incorporato nessuno, ma è improbabile poiché il tipico paradigma Unity sta rendendo il codice tollerante agli errori (cioè catch, log, continue) piuttosto che prevenire gli errori (cioè ifpossibili, quindi). Questo, tuttavia, non è molto stile C # (come uno in questa risposta). Alla fine il tuo codice originale potrebbe essere stato più vicino al modo in cui le cose vengono fatte in genere ... e la migliore pratica è una questione di preferenza personale.
Wondra,

7

Invece di rimuovere i componenti sul clone, puoi semplicemente disabilitarli impostandoli .enabled = false. Ciò non attiverà i controlli di dipendenza, poiché non è necessario abilitare un componente per soddisfare la dipendenza.

  • Quando un comportamento è disabilitato, rimarrà comunque sull'oggetto e potrai persino modificarne le proprietà e chiamarne i metodi, ma il motore non chiamerà più il suo Updatemetodo.
  • Quando un renderizzatore è disabilitato, l'oggetto diventa invisibile.
  • Quando un Collider è disabilitato, non si verificherà alcun evento di collisione.
  • Quando un componente dell'interfaccia utente è disabilitato, il giocatore non può più interagire con esso, ma verrà comunque visualizzato, a meno che non si disattivi anche il suo renderer.

I componenti non hanno una nozione di abilitato o disabilitato. Comportamenti, Collider e altri tipi lo fanno. Quindi ciò richiederebbe al programmatore di effettuare più chiamate a GetComponent per le varie sottoclassi.
DubiousPusher
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.