Periksa apakah komponen objek game dapat dihancurkan


11

Mengembangkan permainan di Unity, saya memanfaatkan secara liberal [RequireComponent(typeof(%ComponentType%))]untuk memastikan semua komponen telah terpenuhi.

Saya sekarang menerapkan sistem tutorial yang menyoroti berbagai objek UI. Untuk melakukan penyorotan, saya mengambil referensi ke GameObjectdalam adegan, lalu saya mengkloningnya dengan Instantiate () dan kemudian menghapus semua komponen yang tidak diperlukan untuk ditampilkan secara rekursif. Masalahnya adalah bahwa dengan penggunaan liberal RequireComponentdalam komponen-komponen ini, banyak yang tidak dapat dihapus begitu saja dalam urutan apa pun, karena mereka bergantung pada komponen lain, yang belum dihapus.

Pertanyaan saya adalah: Apakah ada cara untuk menentukan apakah Componentdapat dihapus dari yang GameObjectsaat ini dilampirkan.

Kode yang saya posting secara teknis berfungsi, namun ia melempar kesalahan Unity (tidak tertangkap di blok try-catch, seperti yang terlihat pada gambar

masukkan deskripsi gambar di sini

Berikut kodenya:

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

Jadi cara kode ini menghasilkan tiga baris terakhir dari log debug di atas adalah sebagai berikut: Dibutuhkan sebagai input GameObjectdengan dua komponen: Buttondan PopupOpener. PopupOpenermeminta agar suatu Buttonkomponen hadir dalam GameObject yang sama dengan:

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

Iterasi pertama dari loop sementara (dilambangkan oleh teks "ITERATION ON Button Invite (clone)") berusaha untuk menghapus yang Buttonpertama, tetapi tidak bisa karena tergantung pada PopupOpenerkomponen. ini menimbulkan kesalahan. Kemudian ia mencoba untuk menghapus PopupOpenerkomponen, yang ia lakukan, karena tidak bergantung pada hal lain. Dalam iterasi berikutnya (Ditandai oleh teks kedua "ITERATION ON Button Invite (clone)"), itu berusaha untuk menghapus Buttonkomponen yang tersisa , yang sekarang bisa dilakukan, karena PopupOpenertelah dihapus dalam iterasi pertama (setelah kesalahan).

Jadi pertanyaan saya adalah apakah mungkin untuk memeriksa sebelumnya apakah komponen tertentu dapat dihapus dari saat ini GameObject, tanpa meminta Destroy()atau DestroyImmediate(). Terima kasih.


Tentang masalah awal - apakah tujuannya untuk membuat salinan non-interaktif (= visual) elemen GUI?
wondra

Iya. Tujuannya adalah membuat salinan yang identik dan tidak dapat berinteraksi dalam posisi yang sama, diskalakan dengan cara yang sama, menampilkan teks yang sama dengan objek game nyata di bawahnya. Alasan saya menggunakan metode ini adalah agar tutorial tidak perlu modifikasi jika UI diedit di kemudian hari (yang perlu terjadi jika saya menunjukkan tangkapan layar).
Liam Lime

Saya tidak memiliki pengalaman dengan Unity, tetapi saya bertanya-tanya apakah alih-alih mengkloning semuanya dan menghapus hal-hal yang dapat Anda lakukan hanya menyalin bagian yang Anda butuhkan?
Zan Lynx

Saya belum mengujinya, tetapi Destroymenghapus komponen di akhir frame (sebagai lawan Immediately). DestroyImmediatedianggap gaya yang buruk, tapi saya pikir beralih ke Destroysebenarnya dapat menghilangkan kesalahan ini.
CAD97

Saya mencoba keduanya, mulai dengan Destroy (karena itu adalah cara yang tepat) dan kemudian beralih ke DestroyImmediate untuk melihat apakah itu akan berhasil. Hancurkan tampaknya masih berjalan dalam urutan yang ditentukan, yang menyebabkan pengecualian yang sama, tepat di ujung bingkai.
Liam Lime

Jawaban:


9

Ini memang mungkin, tetapi membutuhkan kerja keras yang cukup banyak: Anda dapat memeriksa apakah ada Componentlampiran GameObjectyang memiliki Attributetipe RequireComponentyang memiliki salah satu m_Type#bidang yang dapat ditugaskan ke tipe komponen yang akan Anda hapus. Semua dibungkus dengan metode ekstensi:

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

setelah menjatuhkan GameObjectExtensionsdi mana saja dalam proyek (atau sebagai metode biasa pada skrip), Anda dapat memeriksa apakah komponen dapat dihapus, misalnya:

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

Namun ada cara lain untuk membuat objek GUI yang tidak interaktif - misalnya, menjadikannya tekstur, melapisinya dengan panel yang hampir transparan yang akan mencegat klik atau menonaktifkan (alih-alih menghancurkan) semua Componentyang berasal dari MonoBehaviouratau IPointerClickHandler.


Terima kasih! Saya bertanya-tanya apakah solusi yang sudah jadi dikirimkan bersama Unity, tapi saya rasa tidak :) Terima kasih atas kode Anda!
Liam Lime

@LiamLime Yah, saya benar-benar tidak dapat mengkonfirmasi tidak ada builtin tetapi tidak mungkin karena paradigma Unity khas membuat kode toleran-kesalahan (yaitu catch, log, lanjutkan) daripada mencegah kesalahan (yaitu ifmungkin, kemudian). Namun demikian, tidak banyak gaya C # (seperti yang ada dalam jawaban ini). Pada akhirnya kode asli Anda mungkin lebih dekat dengan bagaimana hal-hal biasanya dilakukan ... dan praktik terbaik adalah masalah preferensi pribadi.
wonderra

7

Alih-alih menghapus komponen pada klon, Anda bisa menonaktifkannya dengan mengaturnya .enabled = false. Ini tidak akan memicu pemeriksaan dependensi, karena komponen tidak perlu diaktifkan untuk memenuhi dependensi.

  • Ketika Perilaku dinonaktifkan, ia masih akan berada di objek dan Anda bahkan dapat mengubah propertinya dan memanggil metode-metodenya, tetapi mesin tidak akan lagi memanggil Updatemetodenya.
  • Saat Renderer dinonaktifkan, objek berubah menjadi tidak terlihat.
  • Ketika seorang Collider dinonaktifkan, itu tidak akan menyebabkan peristiwa tabrakan terjadi.
  • Ketika komponen UI dinonaktifkan, pemain tidak dapat lagi berinteraksi dengannya, tetapi komponen itu masih akan dirender, kecuali jika Anda juga menonaktifkan renderernya.

Komponen tidak memiliki gagasan diaktifkan atau dinonaktifkan. Perilaku, Colliders dan beberapa tipe lainnya. Jadi ini akan membutuhkan programmer untuk membuat beberapa panggilan ke GetComponent untuk berbagai subclass.
DubiousPusher
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.