본문 바로가기
Study/Unity

[Unity] 유니티 - UI 자동 바인딩 구현(Reflection 이용, enum)

by 스테디코디스트 2024. 2. 4.
반응형
인프런 루키스님의 강좌를 참고했습니다.

 

- 드래그 앤 드롭을 매번 해주는 것을 대체하기 위해서 구현했다.

- dictionary에 사용할 오브젝트를 담고, 드래그 앤 드롭 대신 enum을 이용했다.

- enum의 원소이름은 사용할 오브젝트의 실제 이름과 동일해야 한다.

 

// UI_Base

using System;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class UI_Base : MonoBehaviour
{
    // 컴포넌트 타입별 오브젝트들을 담아 관리할 딕셔너리
    // Key : enum 이름, Value : 오브젝트 배열
    Dictionary<Type, UnityEngine.Object[]> dict = new Dictionary<Type, UnityEngine.Object[]>();


    // enum의 원소이름을 바인딩할 자식 오브젝트의 이름과 같게 세팅한 뒤
    // binding 함수를 이용해 해당 오브젝트를 바인딩시킴
    // ComponentName는 연결할 오브젝트의 컴포넌트의 이름을 뜻함.
    // ComponentName은 UnityEngine.Object를 상속받은 컴포넌트여야함.
    protected void Binding<ComponentName>(Type enumType) where ComponentName : UnityEngine.Object
    {
        // 1. Reflect를 이용해 enum의 원소들을 가져옴
        // names는 enum의 원소들의 이름
        string[] names = enumType.GetEnumNames();

        // 2. 해당 원소들의 갯수만큼 오브젝트를 찾아서 바인딩 할 것임.
        // 따라서 해당 오브젝트들을 담을 배열을 해당 원소들의 갯수만큼의 크기를 가진 배열로 선언
        UnityEngine.Object[] objs = new UnityEngine.Object[names.Length];

        // 3.이 오브젝트의 자식 오브젝트 중에서 앞서 찾은 names와 같은 이름인 오브젝트들을 찾아
        // 오브젝트 배열에 담음
        for (int i = 0; i < names.Length; i++)
        {
            // enum의 이름을 이용해 자식들 중에서 같은 이름의 오브젝트를 찾고,
            // 해당 오브젝트를 오브젝트 배열에 담음
            // 찾을 오브젝트의 타입이 GameObject인 경우 -> GameObject 타입은 Component로 인식하지 못하므로 따로 처리해주어야 함!
            if (typeof(ComponentName) == typeof(GameObject))
                objs[i] = Util.FindComponentInChild(this.gameObject, names[i], true);
            else
                objs[i] = Util.FindComponentInChild<ComponentName>(this.gameObject, names[i], true);

            // 바인딩에 실패한 경우
            if (objs[i] == null)
                Debug.LogError($"바인딩 실패 {names[i]}");
        }

        // 위에서 담은 오브젝트 배열을 딕셔너리에 담음
        dict.Add(typeof(ComponentName), objs);
    }

    // TODO  
    // Get 함수를 이용해 딕셔너리에 바인딩된 오브젝트의 해당되는 컴포넌트를 가져옴
    // 매개변수 index는 enum의 이름을 이용한 인덱스를 의미
    protected ComponentName Get<ComponentName>(int enumNameToInt) where ComponentName : UnityEngine.Object
    {
        UnityEngine.Object[] objs = null;

        // 딕셔너리의 key값을 이용해 value를 가져옴
        // enum의 이름을 이용해 해당 enum이름의 컴포넌트를 가진 오브젝트들의 배열을 가져옴
        // 오브젝트 배열을 가져오지 못한 경우 -> 리턴
        if (dict.TryGetValue(typeof(ComponentName), out objs) == false)
            return null;

        // 오브젝트의 컴포넌트를 리턴
        // as : UnityEngine.Object 타입인 objs[index]를 ComponentName으로 캐스팅해줌
        // objs[index]는 원래 ComponentName 컴포넌트를 가진 오브젝트인데,
        // 현재 범용성을 위해 UnityEngine으로 업캐스팅된 상태이므로, as를 이용해 다시 원래대로 놓는 것임.
        return objs[enumNameToInt] as ComponentName;
    }

    // 자주 사용될 컴포넌트의 타입들을 제네릭 선언없이 간편하게 사용하기 위해 선언
    protected GameObject GetGameObject(int enumNameToInt) { return Get<GameObject>(enumNameToInt); }
    protected Button GetButton(int enumNameToInt) { return Get<Button>(enumNameToInt); }
    protected Text GetText(int enumNameToInt) { return Get<Text>(enumNameToInt); }
    protected TMP_Text GetTMP(int enumNameToInt) { return Get<TMP_Text>(enumNameToInt); }
    protected Image GetImage(int enumNameToInt) { return Get<Image>(enumNameToInt); } 
}

 

// Util

using UnityEngine;

public class Util : MonoBehaviour
{
    // root : 자식을 찾을 가장 상위의 오브젝트, root의 자식들을 검색
    // name : 찾을 이름, 이름이 null인 경우는 ComponeneName라는 이름의 컴포넌트를 가졌으면 리턴
    // recursive : 재귀적으로 찾을 건지 여부, 자식들의 자식들까지 찾을 건지, 직속 자식들 중에서만 찾을 건지 선택
    // 리턴 : 찾은 컴포넌트 이름을 리턴
    public static ComponentName FindComponentInChild<ComponentName>(GameObject root, string name = null, bool recursive = false) where ComponentName : UnityEngine.Object
    {
        // root의 자식들을 찾아야 하는데 root가 없는 경우 -> 리턴
        if (root == null)
            return null;

        // 직속 자식들 중에서만 찾는 경우
        if (recursive == false)
        {
            // 직속자식의 수를 나타내는 childCount를 이용하여 반복문 진행
            for (int i = 0; i < root.transform.childCount; i++)
            {
                // 모든 오브젝트는 Transform을 가짐
                // 따라서 Transform을 이용해 직속자식에 접근할 수 있음
                // child는 직속자식을 의미함.
                Transform child = root.transform.GetChild(i);

                // 입력받은 이름이 없는 경우 -> 그냥 같은 컴포넌트를 찾기만 하면 바로 리턴
                // 입력받은 이름이 있는 경우 -> 이름이 같을 경우에만 리턴
                // 따라서 입력받은 이름이 없거나, 이름이 같은 경우에 해당 컴포넌트를 리턴
                if (string.IsNullOrEmpty(name) || name == child.name)
                {
                    // child에서 ComponentName 컴포넌트를 찾음
                    ComponentName component = child.GetComponent<ComponentName>(); // -> 여기서 GameObject를 찾지못함 1

                    // child가 ComponentName 컴포넌트를 가진 경우에만 동작                    
                    // 해당 컴포넌트 리턴
                    if (component != null)
                        return component;
                }
            }
        }
        // 모든 자식들에 대해 찾는 경우
        else
        {
            // ComponentName 컴포넌트를 모두 찾아서 담음
            ComponentName[] components = root.GetComponentsInChildren<ComponentName>(); // -> 여기서 GameObject를 찾지못함 2

            // 담은 컴포넌트들을 돌면서 찾음         
            foreach (ComponentName component in components)
            {
                if (string.IsNullOrEmpty(name) || name == component.name)
                    return component;
            }
        }

        return null;
    }

    public static GameObject FindComponentInChild(GameObject root, string name = null, bool recursive = false)
    {
        // GameObject는 Compnent를 상속받지 않아서 유니티 내부의 GetComponent를 통해 찾을 수 없음
        // 따라서 GameObject가 항상 가지고 있는 Transform으로 찾아서 판단.
        Transform transform = FindComponentInChild<Transform>(root, name, recursive);

        if (transform != null)
            return transform.gameObject;

        return null;
    }
}

 

 

//UI_Scene : 사용 예시

using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class UI_Scene : UI_Base
{
    enum GameObjects
    {
        // 게임 오브젝트의 이름들...
        Lee,
        Eun,
        Chan,
    }

    enum Texts
    {
        // 텍스트 이름들...
        Lee,
    }

    private void Start()
    {
        Binding<TMP_Text>(typeof(Texts));
        Binding<GameObject>(typeof(GameObjects));

        //Get<GameObject>((int)GameObjects.Lee);
        Get<Text>((int)Texts.Lee);
        GetTMP((int)Texts.Lee).text = "hi";
    }
}