ตรวจสอบว่าองค์ประกอบของวัตถุในเกมสามารถถูกทำลายได้หรือไม่


11

การพัฒนาเกมใน Unity ฉันใช้ประโยชน์อย่างมากมาย[RequireComponent(typeof(%ComponentType%))]เพื่อให้แน่ใจว่าส่วนประกอบต่างๆมีการพึ่งพาทั้งหมด

ตอนนี้ฉันกำลังใช้ระบบการสอนที่เน้นวัตถุ UI ต่างๆ เพื่อทำการไฮไลต์ฉันกำลังอ้างอิงถึงGameObjectในฉากจากนั้นฉันโคลนด้วย Instantiate () จากนั้นปลดส่วนประกอบทั้งหมดที่ไม่จำเป็นสำหรับการแสดงซ้ำ ปัญหาคือว่าด้วยการใช้งานอย่างเสรีRequireComponentในองค์ประกอบเหล่านี้หลายคนไม่สามารถลบออกได้ในลำดับใด ๆ เนื่องจากมันขึ้นอยู่กับส่วนประกอบอื่น ๆ ซึ่งยังไม่ถูกลบ

คำถามของฉันคือ: มีวิธีการตรวจสอบว่าComponentสามารถลบออกจากที่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);
    }
}

ดังนั้นวิธีการสร้างรหัสนี้สามบรรทัดสุดท้ายของบันทึกการแก้ปัญหาดังกล่าวข้างต้นคือต่อไปนี้: มันต้องใช้เวลาเป็นใส่GameObjectกับสององค์ประกอบและButton ร้องขอให้มีส่วนประกอบอยู่ใน GameObject เดียวกันด้วย:PopupOpenerPopupOpenerButton

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

การวนซ้ำครั้งแรกของ while loop (แสดงโดยข้อความ "ITERATION ON Button Invite (clone)") พยายามที่จะลบอันButtonแรก แต่ไม่สามารถทำได้เนื่องจากPopupOpenerส่วนประกอบนั้นขึ้นอยู่กับ นี่ทำให้เกิดข้อผิดพลาด จากนั้นจึงพยายามลบPopupOpenerองค์ประกอบที่ทำเนื่องจากไม่ได้ขึ้นอยู่กับสิ่งใด ในการทำซ้ำครั้งถัดไป (แสดงเป็นข้อความที่สอง "ITERATION ON Button Invite (clone)") มันพยายามที่จะลบButtonองค์ประกอบที่เหลือซึ่งตอนนี้สามารถทำได้เนื่องจากPopupOpenerถูกลบในการทำซ้ำครั้งแรก (หลังจากข้อผิดพลาด)

ดังนั้นคำถามของฉันไม่ว่าจะเป็นไปได้ที่จะตรวจสอบก่อนเวลาถ้าเป็นส่วนประกอบที่ระบุสามารถลบออกจากปัจจุบันGameObjectโดยไม่ต้องกล่าวอ้างหรือDestroy() DestroyImmediate()ขอขอบคุณ.


เกี่ยวกับปัญหาดั้งเดิม - มีเป้าหมายที่จะทำสำเนาแบบไม่โต้ตอบ (= visual) ขององค์ประกอบ GUI หรือไม่
Wondra

ใช่. เป้าหมายคือการทำสำเนาที่เหมือนกันและไม่สามารถโต้ตอบได้ในตำแหน่งเดียวกันปรับขนาดในลักษณะเดียวกันโดยแสดงข้อความเดียวกันกับ gameobject จริงที่อยู่ด้านล่าง เหตุผลที่ฉันไปด้วยวิธีนี้คือการสอนไม่จำเป็นต้องแก้ไขหาก UI ถูกแก้ไขในภายหลัง (ซึ่งจะต้องเกิดขึ้นหากฉันแสดงภาพหน้าจอ)
เลียม Lime

ฉันไม่ได้มีประสบการณ์กับ Unity แต่ฉันสงสัยว่าแทนที่จะโคลนสิ่งทั้งหมดและลบสิ่งต่าง ๆ คุณสามารถคัดลอกเฉพาะส่วนที่คุณต้องการหรือไม่
Zan Lynx

ฉันยังไม่ได้ทดสอบ แต่Destroyลบส่วนประกอบที่ส่วนท้ายของเฟรม (ตรงข้ามกับImmediately) DestroyImmediateถือว่าเป็นรูปแบบที่ไม่ดี แต่ฉันคิดว่าการเปลี่ยนไปใช้Destroyอาจกำจัดข้อผิดพลาดเหล่านี้ได้จริง
CAD97

ฉันลองทั้งสองเริ่มต้นด้วย Destroy (เนื่องจากเป็นวิธีที่เหมาะสม) จากนั้นเปลี่ยนเป็น DestroyImmediate เพื่อดูว่าจะใช้งานได้หรือไม่ ดูเหมือนว่าการทำลายยังคงดำเนินต่อไปตามลำดับที่ระบุซึ่งทำให้เกิดข้อยกเว้นแบบเดียวกันที่ส่วนท้ายของเฟรม
Liam Lime

คำตอบ:


9

เป็นไปได้อย่างแน่นอน แต่มันต้องมีจำนวนมากของ legwork: คุณสามารถตรวจสอบว่ามีสิ่งที่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 ยกเลิกการโต้ตอบ - ตัวอย่างเช่นการแสดงผลไปยังพื้นผิวซ้อนทับกับแผงเกือบโปร่งใสซึ่งจะสกัดกั้นการคลิกหรือปิดการใช้งาน (แทนที่จะทำลาย) ทั้งหมดComponents มาจากหรือMonoBehaviourIPointerClickHandler


ขอบคุณ! ฉันสงสัยว่าโซลูชันแบบสำเร็จรูปมาพร้อมกับ Unity หรือไม่ แต่ฉันเดาว่าไม่ :) ขอบคุณสำหรับโค้ดของคุณ!
เลียม Lime

@ LiamLime ฉันไม่สามารถยืนยันได้เลยว่าไม่มีใครในตัว แต่ไม่น่าเป็นไปได้เพราะกระบวนทัศน์ทั่วไปของ Unity ทำให้รหัสผิดพลาด (เช่นcatchบันทึกต่อไป) แทนที่จะป้องกันความผิดพลาด (เช่นที่ifเป็นไปได้) นั่นคืออย่างไรก็ตามมีลักษณะ C # ไม่มาก (เช่นคำตอบเดียวในนี้) ในที่สุดรหัสต้นฉบับของคุณอาจใกล้เคียงกับวิธีการทำสิ่งต่าง ๆ โดยทั่วไป ... และวิธีปฏิบัติที่ดีที่สุดคือการตั้งค่าส่วนตัว
Wondra

7

แทนที่จะลบส่วนประกอบบนโคลนคุณสามารถปิดการใช้งานพวกเขาโดยการตั้งค่า.enabled = falseพวกเขา สิ่งนี้จะไม่เรียกการตรวจสอบการพึ่งพาเนื่องจากองค์ประกอบไม่จำเป็นต้องเปิดใช้งานเพื่อเติมเต็มการพึ่งพา

  • เมื่อพฤติกรรมถูกปิดใช้งานมันจะยังคงอยู่ในวัตถุและคุณสามารถเปลี่ยนคุณสมบัติและเรียกวิธีการของมันได้ แต่เอ็นจิ้นจะไม่เรียกUpdateวิธีการของมันอีกต่อไป
  • เมื่อ Renderer ถูกปิดการใช้งานวัตถุจะมองไม่เห็น
  • เมื่อปิดการใช้งาน Collider จะไม่ทำให้เกิดเหตุการณ์การชนใด ๆ เกิดขึ้น
  • เมื่อปิดการใช้งานองค์ประกอบ UI ผู้เล่นจะไม่สามารถโต้ตอบกับมันได้อีก แต่จะยังคงแสดงผลจนกว่าคุณจะปิดการใช้งานโหมดแสดงภาพ

ส่วนประกอบไม่ได้มีความคิดของการเปิดใช้งานหรือปิดการใช้งาน พฤติกรรมคอลไลเดอร์และประเภทอื่น ๆ ดังนั้นสิ่งนี้ต้องการให้โปรแกรมเมอร์ทำการโทรหลายครั้งไปยัง GetComponent สำหรับคลาสย่อยต่างๆ
DubiousPusher
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.