UI자동화(3회차때 다시와라)

·2023년 4월 18일
0

Unity

목록 보기
16/22

📌UI자동화를 위한 시작


개선하고 싶은 방식

1.Onclick() 등록시 드래그 앤 드롭해서 연결하는 방식

-> 개선 : 이름만 건네주면 알아서 매핑해주는 것

2.다른 Text에 접근해서 바꾸고 싶은데

SerializeField를 사용해서 Text를 드래그 앤 드롭 방식으로 연결하는 방식
-> 개선 : 이것도 이름만 건네주면 알아서 매핑해주는 것

Bind

게임오브젝트의 이름을 가져와서 Bind에서 저장한다

게임오브젝트 지정

Enum으로 버튼, 텍스트 등 게임오브젝트 종류에 따라 이름을 넣어둠

    enum Buttons
    {
        PointButton,
    }
    enum TMP_Texts
    {
        PointText,
        ScoreText,
    }

Bind

enum값을 넘겨서 이름 겹치는게 있다면 알아서 저장하는걸 Bind에서 처리
enum값을 bind에 어떻게 전달할 것인가? -> 리플렉션을 사용해서 추출

    private void Start()
    {
        Bind(typeof(Buttons));
        Bind(typeof(TMP_Texts));
    }
    void Bind(Type type)
    {

    }

Type받아와서 추출한다

제네릭

제네릭을 사용해서 버튼, 텍스트 컴포넌트를 가진 오브젝트를 찾아달라고한다.

private void Start()
    {
        Bind<Button>(typeof(Buttons));
        Bind<TMP_Text>(typeof(TMP_Texts));
    }
    void Bind<T>(Type type)
    {

    }

받아온 게임오브젝트 저장

저장은 딕셔너리로 저장한다

 //딕셔너리로 받아올타입, 유니티엔진.오브젝트로 모든 오브젝트 저장가능
Dictionary<Type, UnityEngine.Object[]> _objects = new Dictionary<Type, UnityEngine.Object[]>();

void Bind<T>(Type type)
 {
        //Enum 이름을 string배열로 받아온다
        string[] names = Enum.GetNames(type);
        //딕셔너리에 넣기위해서 배열로 바꿔준다
        UnityEngine.Object[] objects = new UnityEngine.Object[names.Length];
        _objects.Add(typeof(T), objects);
 }

자식찾기

자식을 찾아서 반환해준다

void Bind<T>(Type type) where T : UnityEngine.Object
    {
        //Enum 이름을 string배열로 받아온다
        string[] names = Enum.GetNames(type);
        //딕셔너리에 넣기위해서 배열로 바꿔준다
        UnityEngine.Object[] objects = new UnityEngine.Object[names.Length];
        _objects.Add(typeof(T), objects);

        //찾기
        for (int i = 0; i < names.Length; i++)
        {
            objects[i] = Util.FindChild<T>(gameObject, names[i], true);
        }
    }
//최상위 게임오브젝트, 이름(없으면 타입으로 찾음), 재귀적으로 찾을지(자식의 자식을 찾을지)
    public static T FindChild<T>(GameObject go, string name = null, bool recursive = false) where T : UnityEngine.Object
    {
        //최상위 오브젝트가 null이면 리턴
        if (go == null)
            return null;

        //직속 자식만 찾음
        if(recursive == false)
        {
            for(int i = 0; i < go.transform.childCount; i++)
            {
                //직속 자식 찾는데 몇번째 자식 찾을지
                Transform transform = go.transform.GetChild(i);
                //이름이 비어있거나 내가 원하는 이름이면 반환
                if (string.IsNullOrEmpty(name) || transform.name == name)
                {
                    //transform에서 component를 받아와서 
                    T component = transform.GetComponent<T>();
                    //컴포넌트가 null이 아니라면 반환
                    if (component != null)
                        return component;
                }
            }
           
        }
        //자식의 자식도 찾음
        else
        {
            //GetComponentsInChildren 자식객체들을 찾음
            foreach (T component in go.GetComponentsInChildren<T>())
            {
                //이름이 비어있거나 내가 원하는 이름이면 반환
                if (string.IsNullOrEmpty(name) || component.name == name)
                    return component;
            }
        }
        return null;
    }

Get

Bind로 저장된 게임오브젝트를 받아와서 사용한다

 T Get<T>(int idx) where T : UnityEngine.Object
    {
        UnityEngine.Object[] objects = null;
        //TryGetValue로 꺼내오는데 false라면 null반환
        if (_objects.TryGetValue(typeof(T), out objects) == false)
            return null;

        return objects[idx] as T;
    }

Get<TMP_Text>((int)TMP_Texts.ScoreText).text = "바인드테스트";

📌이벤트 연동


콜백방식으로 연동해줘야한다
Event System을 사용해서 UI와 연동하겠다

인터페이스구현

인터페이스로 받아서 인터페이스를 구현해준다

public class UI_EventHandler : MonoBehaviour, IBeginDragHandler, IDragHandler
{
    public void OnBeginDrag(PointerEventData eventData)
    {
        Debug.Log("OnBeginDrag");
    }

    public void OnDrag(PointerEventData eventData)
    {
        Debug.Log("OnDrag");
    }
}

Image에만 UI_EventHandler 적용 후 사용

위치변경

public void OnDrag(PointerEventData eventData)
    {
        transform.position = eventData.position;
        Debug.Log("OnDrag");
    }

이벤트 콜백방식으로 만들기

public class UI_EventHandler : MonoBehaviour, IBeginDragHandler, IDragHandler
{
    public Action<PointerEventData> OnBeginDragHandler = null;
    public Action<PointerEventData> OnDragHandler = null;

    public void OnBeginDrag(PointerEventData eventData)
    {
        if(OnBeginDragHandler != null)
            OnBeginDragHandler.Invoke(eventData);
    }

    public void OnDrag(PointerEventData eventData)
    {
        if (OnDragHandler != null)
            OnDragHandler.Invoke(eventData);
    }
}

콜백연결 사용

다른코드에서 콜백방식으로 연결해서 사용한다

 Bind<Image>(typeof(Images));

//Image의 GameObject를 뽑아왔다가 다시 UI_EventHadnler를 뽑아옴
        GameObject go = GetImage((int)Images.ItemIcon).gameObject;
        UI_EventHandler evt = go.GetComponent<UI_EventHandler>();
        evt.OnDragHandler += ((PointerEventData data) => { go.transform.position = data.position; });

콜백방식 함수로 만들기

 //게임오브젝트, 함수를 받아올 Action, UIEvent(드래그, 클릭 ...)
    public static void AddUIEvent(GameObject go, Action<PointerEventData> action, Define.UIEvent type = Define.UIEvent.Click)
    {

        UI_EventHandler evt = go.GetComponent<UI_EventHandler>();
        if(evt == null)
            evt = go.AddComponent<UI_EventHandler>();

        evt.OnDragHandler += ((PointerEventData data) => { go.transform.position = data.position; });
    }

여기서

UI_EventHandler evt = go.GetComponent<UI_EventHandler>();
        if(evt == null)
            evt = go.AddComponent<UI_EventHandler>();

이부분은 자주 나오기 때문에 Generic으로 함수를 따로 만든다

 public static T GetorAddComponent<T>(GameObject go) where T : UnityEngine.Component
    {
        T component = go.GetComponent<T>();
        if (component == null)
            component = go.AddComponent<T>();
        return component;
    }

위와같이 만들면 기존의 코드를 아래와 같이 만들 수 있다

 public static void AddUIEvent(GameObject go, Action<PointerEventData> action, Define.UIEvent type = Define.UIEvent.Click)
    {

        UI_EventHandler evt = Util.GetorAddComponent<UI_EventHandler>(go);

        evt.OnDragHandler += ((PointerEventData data) => { go.transform.position = data.position; });
    }

콜백구현

이벤트 연결하는 부분

public static void AddUIEvent(GameObject go, Action<PointerEventData> action, Define.UIEvent type = Define.UIEvent.Click)
    {

        UI_EventHandler evt = Util.GetorAddComponent<UI_EventHandler>(go);

        switch(type)
        {
            case Define.UIEvent.Click:
                evt.OnClickHandler -= action;
                evt.OnClickHandler += action;
                break;
            case Define.UIEvent.Drag:
                evt.OnDragHandler -= action;
                evt.OnDragHandler += action;
                break;
        }
    }

이벤트 일어나면 Invoke하는부분

public class UI_EventHandler : MonoBehaviour, IPointerClickHandler, IDragHandler
{
    public Action<PointerEventData> OnClickHandler = null;
    public Action<PointerEventData> OnDragHandler = null;

    public void OnPointerClick(PointerEventData eventData)
    {
        if (OnClickHandler != null)
            OnClickHandler.Invoke(eventData);
    }
    public void OnDrag(PointerEventData eventData)
    {
        if (OnDragHandler != null)
            OnDragHandler.Invoke(eventData);
    }
}

실제로 연결해서 사용하는 부분

 GameObject go = GetImage((int)Images.ItemIcon).gameObject;
        AddUIEvent(go, (PointerEventData data) => { go.transform.position = data.position; }, Define.UIEvent.Drag);

📌Extension(이런게 있구나)


static을 사용해야한다

public static class Extension
{
    public static void AddUIEvent(this GameObject go, Action<PointerEventData> action, Define.UIEvent type = Define.UIEvent.Click)
    {
        UI_Base.AddUIEvent(go, action, type);
    }
}

이렇게 사용하면 여려줄로 사용하지 않고 한줄로 사용가능하다

 GetButton((int)Buttons.PointButton).gameObject.AddUIEvent(OnButtonClicked);

public void OnButtonClicked(PointerEventData data)
    {
        _score++;

        GetText((int)TMP_Texts.ScoreText).text = $"점수 :{_score}";
    }

참고자료

Part3: 유니티 엔진
섹션 7.UI(유아이)

profile
개인공부저장용(하루의 기록)

0개의 댓글