[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 |