UI 는 어떻게 보면 배정 업무 중 가장 쉬워보일 수 있습니다. 단순히, UI의 컴포넌트와 함수를 연결하고 예쁘게 배치하는것이 끝일 수 있기 때문입니다. 하지만 여기서 더 나아가서 UI 자동화 작업을 진행해봄으로써 불필요한 UI 연결 시간을 단축해 보았습니다.

일반적으로 유니티에서의 기초적인 UI 관리는 위의 사진과 같이 컴포넌트 창에서 버튼과 같은 특정 컴포넌트와 스크립트의 함수를 연결해 주는 작업으로 진행됩니다. 지금처럼 간단한 캐쥬얼 게임의 경우에는 무리 없이 작업할 수 있지만, 더 큰 프로젝트 및 더 많은 인원들과 협업하게 된다면 위와 같은 방법은 시간도 오래걸리고 안전하지 않을 수 있습니다. 이를 위해 UI와 스크립트를 자동으로 연결하는 시스템을 구성했습니다.

상숙 구조는 아래와 같습니다
UI_Base -> UI_Popup -> UI_SettingPopup
ㄴ> UI_Scene -> UI_ShowBUIutton
ㄴ> UI_StartScene
protected void Bind<T>(Type type) where T : UnityEngine.Object
{
//타입 이름 배열
string[] names = Enum.GetNames(type);
UnityEngine.Object[] objects = new UnityEngine.Object[name.Length];
this.objects.Add(typeof(T), objects);
for (int i = 0; i < names.Length; i++)
{
// 컴포넌트 찾기
objects[i] = FindChild<T>(gameObject, names[i], true);
if (objects[i] == null)
Debug.Log($"Failed to bind{names[i]}");
}
}
// 원하는 컴포넌트를 딕셔너리에서 추출
protected T Get<T>(int idx) where T : UnityEngine.Object
{
UnityEngine.Object[] obj = null;
if (objects.TryGetValue(typeof(T), out obj) == false)
return null;
return obj[idx] as T;
}
public class UI_Scene : UI_Base
{
public override void Init()
{
UIManager.instance.SetCanvas(gameObject, false,canvasLayer);
}
}
public class UI_Popup : UI_Base
{
public override void Init()
{
UIManager.instance.SetCanvas(gameObject, true, canvasLayer);
}
public virtual void ClosePopUI()
{
UIManager.instance.ClosePopupUI(this);
}
protected void SetPivot(Vector3 pos, RectTransform pivot)
{
pivot.position += pos;
}
}
public class UI_StartScene : UI_Scene
{
enum Texts
{
TitleText
}
enum Buttons
{
NomalModeButton,
HardModeButton,
QuitButton,
}
public override void Init()
{
base.Init();
Bind<Button>(typeof(Buttons));
Bind<Text>(typeof(Texts));
Get<Button>((int)Buttons.NomalModeButton).onClick.AddListener(OnNomalButtonClick);
Get<Button>((int)Buttons.HardModeButton).onClick.AddListener(OnHardButtonClick);
Get<Button>((int)Buttons.QuitButton).onClick.AddListener(OnQuitButtonClick);
}
protected void OnNomalButtonClick()
{
SceneManager.LoadScene("Game");
}
protected void OnHardButtonClick()
{
SceneManager.LoadScene("Game");
}
protected void OnQuitButtonClick()
{
Application.Quit();
}
}

그러면 위와 같이 이름이 같은 오브젝트를 찾아 원하는 "Button" 컴포넌트가 있는지 찾고 이를 저장했다가, Get일 시도 할 시 원하는 값이 존재한다면 반환해줍니다.
public GameObject ShowPopupUI(string name = null)
{
Time.timeScale = 0f;
GameObject prefab = Resources.Load<GameObject>($"Prefabs/UI/Popup/{name}");
GameObject ui = Instantiate(prefab);
if (ui == null)
return null;
UI_Popup popup = ui.GetComponent<UI_Popup>();
_popupStack.Push(popup);
ui.transform.SetParent(Root.transform, false);
return ui;
}
// 오버라이딩
public void ClosePopupUI()
{
if (_popupStack.Count == 0) return;
UI_Popup popup = _popupStack.Pop();
Destroy(popup.gameObject);
popup = null;
_order--;
Time.timeScale = 1.0f;
}
- 지금 작업하는 카드 뒤집기 게임의 파생형 프로젝트의 경우 UI를 거의 사용하지 않습니다. 때문에 위와 같이 직접 연결하지 않고 스크립트로 조작하는것이 오히려 일을 키우는 것 처럼 보이기도 합니다. 하지만 이런 구조를 작성해두면 간단하게 상속받아 추가적인 형태의 UI를 생성하는 것도 쉽고, 프로젝트를 관리하는것도 쉬워집니다.
- 이런 구조는 비단, 현재 프로젝트에서 뿐만 아니라 많은 다른 프로젝트에서 기본적으로 사용 할 수 있을 것이라 생각되기에, 따로 패키지 형태로 추출해놓는것도 좋아보입니다.