본문 바로가기
1일 1 구현

[1일 1 구현] UI Base

by LemongO 2024. 5. 13.

[Rookiss UI_Base를 참고하였음]

오늘 구현해볼 것 : UI_Base.cs

 

UI_Base

UI_Base 클래스의 역할은 다음과 같다.

  • 모든 UI의 부모 클래스 역할을 한다.
  • 사용할 컴포넌트를 캐싱할 수 있다.
public class UI_Base : MonoBehaviour
{
    // To Do
    // 사용할 컴포넌트들을 캐싱해둘 자료구조
    // 컴포넌트 Bind
    // 컴포넌트 Get    
}

 

캐싱해둘 자료구조를 정해야 하는데 UI의 컴포넌트라면 Button, Text, InputField, Image 등등 다양하게 있다.

List나 배열로 하기에는 여러 타입의 자료구조를 각각 만들어야 하니 패스.

 

Dictionary 의 value를 UnityEngine.Object[] 로 담으면 같은 컴포넌트를 여러개 담을 수 있으면서도

Dictionary 하나로 관리가 가능하니 이 방법을 사용한다.

 

그렇다면 Key는?

string? int? 무엇을 쓰더라도 하드코딩이 될 여지가 있으므로 사용하지 않는다.

대신 Type을 Key로 사용한다.

public class UI_Base : MonoBehaviour
{    
    protected Dictionary<Type, UnityEngine.Object[]> _dict = new Dictionary<Type, UnityEngine.Object[]>();
    
    // To Do
    // 컴포넌트 Bind
    // 컴포넌트 Get    
}

Type을 쓰는 이유는

어느 타입의 UI 컴포넌트라도 typeof(T) 를 사용해서 Type으로 바꿀 수 있고

그걸 이용해 Button, Image 등을 Type으로 변환해 하나의 Dictionary 에서 관리할 수 있게 하기 위함이다.


 

UIManager에서 원하는 UI를 꺼낼 때

[프리팹 이름 == 클래스 이름 ]으로 매칭 시켜준 것과 같은 맥락으로

컴포넌트 역시 오브젝트 이름으로 찾을 예정이고, 이 때 쓰이는 것은 Enum이다.

 

예를들어

UI_Tester라는 Canvas의 Popup/Linked 오브젝트는 둘 다 Button 컴포넌트를 가지고 있다.

그리고 UI_Tester에서 Popup/Linked 오브젝트의 Button 컴포넌트를 캐싱할 때

 

Enum을 컴포넌트를 가진 오브젝트와 같은 이름들로 구성하고

이름과 매칭되는 오브젝트를 찾아서 해당 오브젝트의 Button 컴포넌트를 Get 하여 캐싱을 한다.

 

코드를 작성해보면

enum Buttons
{
    Popup,
    Linked,
}

어떤 컴포넌트인지 알기 쉽도록 enum을 Buttons로 선언하고

내부를 Button 컴포넌트를 가진 오브젝트와 동일하게 정의한다.

 

public void Bind<T>(Type type) where T : UnityEngine.Object
{
    // To Do - enum 내부 값들과 같은 이름(string)을 가진 오브젝트를 찾아
    // T 컴포넌트를 Get -> Dictionary에 저장
}

private void Start()
{
    Bind<Button>(typeof(Buttons));
}

 

 

Bind 메서드로 Dictionary에 컴포넌트들을 저장한다. 이 때 과정은

  • 매개변수로 enum의 Type을 넘겨준다.
  • enum 안에 있는 모든 값들을 string 배열로 담는다.
  • 반복문을 돌며 오브젝트 이름과 string을 대조해 일치하면 GetComponent
  • 가져온 컴포넌트를 캐싱

위 순으로 진행된다.

 

매개변수로 Type을 받은 이유는 Enum 기능중에 Enum.GetNames() 라는 메서드가 있는데

enum 내부 모든 값의 string을 string[]으로 반환해준다.

이것이 위에서 말한 enum에 쓴 이름과 오브젝트 이름을 같게 하는 이유이다.

 

protected void Bind<T>(Type type) where T : UnityEngine.Object
{
    string[] names = Enum.GetNames(type);

    UnityEngine.Object[] objs = new UnityEngine.Object[names.Length];
    _dict.Add(typeof(T), objs);

    for (int i = 0; i < names.Length; i++)
    {
        objs[i] = FindChild<T>(names[i]);

        if (objs[i] == null)
            Debug.Log($"바인드에 실패했습니다. : {names[i]}");
    }
}

 

넘겨준 enum Type으로 Enum.GetNames 메서드를 사용해 string[]을 만들고

Dictionary의 값인 UnityEngine.Object[] 을 names의 길이만큼 만들고 Dictionary에 추가한다.

이 때, 컴포넌트의 Type을 Key로 사용한다.

 

당장은 objs가 비어있지만 참조형이기 때문에 이후에 반복문으로 objs를 추가해도 정상적으로 담겨진다.

 

반복문을 돌며 FindChild<T> 메서드를 사용해 names[i] 이름의 오브젝트에서 T 컴포넌트를 가져와 objs[i]에 할당한다.

 

private T FindChild<T>(string name) where T : UnityEngine.Object
{
    foreach (T child in transform.GetComponentsInChildren<T>())
    {
        if (child.name == name)
            return child;
    }

    return null;
}

 

FindChild 메서드는 현재 UI 오브젝트의 모든 하위 오브젝트를 검사해

T 컴포넌트를 가진 자식 오브젝트의 이름과 매개변수로 넘겨준 name을 비교, 일치하면 T 컴포넌트를 반환한다.

 

지금까지가 사용할 컴포넌트를 캐싱하는 내용이다.

 

그럼 이제 캐싱한 컴포넌트를 사용해야 하기 때문에

Get 메서드를 작성한다.

protected T Get<T>(int index) where T : UnityEngine.Object
{
    // To Do - index 번째의 T 컴포넌트를 가져온다.
}

 

Get 메서드는 T 컴포넌트를 반환해주는 메서드이고 매개변수로 int를 가진다.

int index를 사용하는 이유는 바인드할 때

Enum에 들어있는 순서대로 Dictionary 값인 UnityEngine.Object[]에도 순서대로 들어갔기 때문에

찾고자 하는 오브젝트(enum)의 index만 넘겨주면 찾아올 수 있기 때문이다.

 

protected T Get<T>(int index) where T : UnityEngine.Object
{
    UnityEngine.Object[] objs;

    if(_dict.TryGetValue(typeof(T), out objs) == false)
    {
        Debug.Log("컴포넌트를 가져오는데 실패했습니다.");
        return null;
    }

    return objs[index] as T;
}

private void Start()
{
    Bind<Button>(typeof(Buttons));
    Button button = Get<Button>((int)Buttons.Linked);
}

 

Get 메서드에서 Dictionary에 typeof(T) Key를 가진 값이 없으면 키를 잘못 전달 한 것이므로 return null;

제대로 값이 있다면 index 번째 UnityEngine.Object를 T로 캐스팅해 return 한다.

 

사용하는 방법은 Start 메서드에 적혀있듯 원하는 컴포넌트를 <T>로 전달,

int 매개변수로 원하는 오브젝트 enum값을 (int)로 변환하여 호출한다.

 

이것으로 Get 하는 부분까지 다 작성하였다.

오늘은 여기까지만 하고 내일은 현재까지 작성된 코드들을 정리하고 상속 그리고 공통으로 쓰일 메서드를 공용 클래스로 빼는 작업을 해야겠다.

'1일 1 구현' 카테고리의 다른 글

[1일 1 구현] PoolManager - 2  (0) 2024.05.18
[1일 1 구현] PoolManager - 1  (0) 2024.05.16
[1일 1 구현] 코드 정리 및 공통 클래스  (0) 2024.05.15
[1일 1 구현] UI Manager  (0) 2024.05.13
[1일 1구현] 시작  (0) 2024.05.12