본문 바로가기
유니티 공부/Unity

Unity - Bind event, Extension Method

by 코딩하는 돼징 2023. 2. 22.
반응형

1. Bind에 Gameobject도 추가하기

01 enum 추가

enum GameObjects
{
    TestObject,
}

GameObject type의 Bind도 받고 싶다고 추가

Bind<GameObject>(typeof(GameObjects));

02  T의 타입이 GameObject인지 확인

if(typeof(T) == typeof(GameObject))
    objects[i] = Util.FindChild(gameObject, names[i], true);
else
    objects[i] = Util.FindChild<T>(gameObject, names[i], true);
if (objects[i] == null)
    Debug.Log($"Faild to bind!{names[i]}");

03 GameObject반환

우리가 이전에 썼단 FindChild에서는 GameObject를 반환하지 못하므로 GameObject자체 반환을 위한 FindChild를 작성한다.

public static GameObject FindChild(GameObject go, string name = null, bool recursive = false)
{
    Transform transform =  FindChild<Transform>(go, name, recursive);
    if (transform == null)
        return null;
    return transform.gameObject;
}

04 반복적으로 쓰기 귀찮으니까

Text GetText(int idx){return Get<Text>(idx);}
Button GetButton(int idx){return Get<Button>(idx);}
Image GetImage(int idx){return Get<Image>(idx);}

아래와 같이 사용할 수 있다.

//Get<Text>((int)Texts.scoreText).text = "BindText";
GetText((int)Texts.scoreText).text = "BindText";

3. UI_Base 만들기

위의 Bind와 Get같은 경우 모든 UI에서 사용된다. 그러므로 공용적으로 사용될 수 있게 베이스클래스를 만드는 것이 좋다.

public class UI_Base : MonoBehaviour
{
    Dictionary<Type, UnityEngine.Object[]> _objects = new Dictionary<Type, UnityEngine.Object[]>();
    void Bind<T>(Type type) where T : UnityEngine.Object
    {
        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++)
        {
            if (typeof(T) == typeof(GameObject))
                objects[i] = Util.FindChild(gameObject, names[i], true);
            else
                objects[i] = Util.FindChild<T>(gameObject, names[i], true);
            if (objects[i] == null)
                Debug.Log($"Faild to bind!{names[i]}");
        }
    }

    T Get<T>(int idx) where T : UnityEngine.Object
    {
        UnityEngine.Object[] objects = null;
        if (_objects.TryGetValue(typeof(T), out objects) == false)
            return null;
        return objects[idx] as T;
    }
    Text GetText(int idx) { return Get<Text>(idx); }

    Button GetButton(int idx) { return Get<Button>(idx); }

    Image GetImage(int idx) { return Get<Image>(idx); }
}

01 UI_Button 상속

public class UI_Buttons : UI_Base

02 보호수준

UI_Base를 상속하고나면 보호수준 관련에러가 뜨는 것을 확인할 수 있다.

이러한 문제들을 해결하기 위해 private로 막아주는 것이 아니라 protected르로 자손들이 사용할 수 있게 변경해주면 된다.

protected void Bind<T>(Type type) where T : UnityEngine.Object
protected T Get<T>(int idx) where T : UnityEngine.Object
protected Text GetText(int idx) { return Get<Text>(idx); }
protected Button GetButton(int idx) { return Get<Button>(idx); }
protected Image GetImage(int idx) { return Get<Image>(idx); }

다시 되돌아가보면 문제가 해결됐음을 확인할 수 있다.


UI와 Event연동

01 Unity 이벤트 시스템 활용

public class UI_EventHandler : MonoBehaviour, IDragHandler
{
    public void OnDrag(PointerEventData eventData)
    {
        transform.position = eventData.position;
    }
}

02 직접 이벤트 처리 로직을 제어

public class UI_EventHandler : MonoBehaviour, IDragHandler
{
    Action<PointerEventData> onDragHandler = null;
    public void OnDrag(PointerEventData eventData)
    {
        transform.position = eventData.position;
        if (onDragHandler != null)
            onDragHandler.Invoke(eventData);
    }
}

이미지를 사용할거니까 Images enum추가

enum Images
{
    ItemIcon,
}

이미지를 받아오지 않고 게임 오브젝트로 한 번 더 이렇게 뽑아오는 이유

private void Start()
{
    GameObject go = GetImage((int)Images.ItemIcon).gameObject;
    UI_EventHandler evt = go.GetComponent<UI_EventHandler>();
}

이미지 컴포넌트를 가진 게임 오브젝트를 얻고 이는 이미지 컴포넌트가 게임 오브젝트에 부착되어 있으므로 이미지 컴포넌트가 속한 게임 오브젝트에 접근하기 위해 사용된다. 그리고 게임 오브젝트에 연결된 UI_EventHandler컴포넌트를 가져온다.

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

이렇게 하면 Image가 움직이지 않는다. 왜냐하면 UI_Buttons는 UI_Button에 붙어있기 때문이다. 그래서 Image를 이동시키기 위해서는 아래와 같이 변경해야 한다.

evt.onDragHandler += ((PointerEventData data) => { evt.gameObject.transform.position = data.position; });

BindEvent

01 AddUIEvent

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

    Util.GetOrAddComponent<UI_EventHandler>(go);  
    UI_EventHandler evt = go.GetComponent<UI_EventHandler>();
    if (evt == null)
        evt = go.GetComponent<UI_EventHandler>();
    evt.onDragHandler += ((PointerEventData data) => { evt.gameObject.transform.position = data.position; });
}

매개변수

go : UI이벤트를 추가할 GameObject

action : UI 이벤트 발생시 실행할 액션(델리게이트)이다. PointerEventData를 입력 매개변수로 받아 UI이벤트 핸들링을 수행한다.

type : UI 이벤트 유형을 지정하는 열거형 매개변수이다. 기본값은 Define.UIEvent.Click으로 설정되어있다.

02 GetOrAddComponent

주어진 GameObject에서 컴포넌트를 찾거나 컴포넌트가 없는 경우에는 새로운 컴포넌트를 추가하여 반환

Util.cs

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

위의 메서드를 통해

Util.GetOrAddComponent<UI_EventHandler>(go);  
UI_EventHandler evt = go.GetComponent<UI_EventHandler>();

위의 코드를 아래와 같이 한줄로 표현할 수 있다.

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

03 UI_EventHandler컴포넌트의 이벤트 핸들러에 대한 subscribe, unsubscribe설정

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

코드 전문

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

04 AddUIEvent 사용

UIButtons.cs에서 코드 변경

GameObject go = GetImage((int)Images.ItemIcon).gameObject;
UI_EventHandler evt = go.GetComponent<UI_EventHandler>();
evt.onDragHandler += ((PointerEventData data) => { evt.gameObject.transform.position = data.position; });

위의 코드를 아래와 같이 변경

아래와 같이 변경할 경우 이벤트 핸들러 추가 코드가 더 간결하고 중복을 줄일 수 있으며 코드 재사용에 용이하다.

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

Extension Method

아래와 같이 바로 Event를 연결할 수 있는 방법은 없을까?

GameObject go = GetImage((int)Images.ItemIcon).gameObject.AddUIEvent();

C#에서 기존의 클래스나 인터페이스에 새로운 메서드를 추가하는 방법 중 하나이다. Unity의 GameObject클래스는 수정할 수 없는 외부 라이브러리에서 제공되는 클래스이기때문에 직접 코드를 추가할 수 없지만 C# Extension Method를 통해 기존클래스에 새로운 메서드를 추가할 수 있다.

 

확장 메서드 특징

1) 정적 클래스 내부에 정의된다.

2) 첫번째 매개변수로 this를 사용하여 어떤 클래스나 인터페이스의 인터페이스의 확장 메서드인지 명시한다.

3) 이는 모든 인스턴스에 사용할 수 있다.

예를 들어 AddUIEvent라는 확장 메서드는 GameObject클래스에 새로운 메서드처럼 추가되었다. 그러면 GameObject인스턴스에서 AddUIEvent 메서드를 호출할 수 있게 된다.

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으로 얻은 버튼 게임 오브젝트에 대해 AddUIEvent확장 메서드를 호출하여 이벤트를 연결한다.

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

 

 

 

 

 

 

 

 

 

참고 :  본 내용은 MMORPG  PART3 강의를 수강하여 작성하였습니다.

https://www.inflearn.com/course/mmorpg-%EC%9C%A0%EB%8B%88%ED%8B%B0/dashboard

 

 

 

 

 

 

 

 

반응형

댓글