
달팽이 키우기 리팩토링 목차
Unity Refactoring - 달팽이키우기 UIManager 설계요약
UI요소의 On/Off를 UIManager에서 일괄적으로 관리할 수 있도록 했으며, 활성화된 패널번호를 스택에 넣어서 Stack.Pieek()을 이용해 맨 위부터 순차적으로 꺼질 수 있도록 했다.
지난번에 정리하면서 번뜩였던 부분을 토대로 UI부분을 정리해봤다. 가장 먼저 정리한 부분은 Btn과 Popup 부분인데, ViewStack을 만들어서, 버튼을 눌러 팝업을 열면 열려있는 팝업을 ViewStack에서 관리할 수 있도록 하고 싶었다 (그래서, 백스페이스 등을 눌렀을 때. 순서대로 하나씩 닫을 수 있도록)
UIManager의 작동방식은 다음과 같은데.
- 먼저, 정의된 SelfManageButton : Button과 UIPanels : Monobehaviour 클래스를 씬 내부의 버튼들과 팝업창들에 컴포넌트로 추가해준다.
- 그 다음, FindAllT<T> 를 이용해 해당 클래스를 전부 긁어준다. 이때 제네릭 한정자 (where T : ) 부분을 class로 해두어야 범용성있게 긁어올 대상을 정할 수 있다.
- 그리고 긁어온 SelfManageButton과 UIPanels들을 LinkBtnPnl을 이용해 SerializableDictionary<SelfManageButton, UIPanels>에 하나씩 짝지어서 넣어준다. (SerializableDictionary는 인스펙터에 내용물을 보여주는 Dictionary로써 나머지는 Dictionary와 동일하다) (이 때, 짝지어진 순서에 따라 각 UIPanels들은 자신의 index번호를 갖게 된다.) 1
- 짝을 지어주는 순간에 버튼에 타겟이 되는 패널을 여는 액션을 넣어준다.

아래의 소스코드를 보면 알 수 있지만, On/Off를 하는 액션 자체는 Dictionary를 참조하지 않기때문에 (패널의 Index만 가지고 FintAllT<>를 통해 가져온 pnlList에 접근한다) Dictionary가 필요해보이지 않을 수 있지만, 내가 Dictionay를 사용한 이유는 다음과 같다.
먼저 Dictionary는 특성상 중복된 키를 가질 수 없기 때문에, 한 버튼이 > 두개의 패널을 여는 일을 자연스럽게 막을 수 있다. (또한 값을 차지하는 패널은 중복될 수 있으므로, 한 패널이 두개 이상의 버튼으로부터 열리는 것은 가능하다 = 숏컷 등 의외로 필요한 상황이 있다.) 즉, UI버튼을 만들 때 자연스럽게 규격외 상황을 걸러주는 프레임으로써 유용하다고 생각했고.
두번째로는 SerializableDictionary를 사용한다면, 굳이 Debug를 걸고 Break Point를 찍어서 런타임에 값을 확인하지 않아도, 자연스럽게 시각적으로 어떤 버튼들과 패널들이 짝지어졌는지 확인할 수 있기 때문에 생산성을 높이는데에도 도움이 된다고 생각했다.
특히 게임 시작단에서 값을 버리고 새롭게 값을 초기화 하는 등, inspector상에서의 수정이 인게임 데이터에 직접적으로 영향을 주지 않는 항목들이라면, 무조건 숨기는 것 보단, 인스펙터에 노출해두어 시각적으로 확인할 수 있도록 하는게 작업속도 향상에 도움이 된다고 생각한다.
UIManager.cs ↓
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Linq;
public class UIManager : MonoBehaviour
{
ActionManager actionManager;
[SerializeField]
Canvas mainCanvas;
[SerializeField]
List<UIPanels> uiPanels = null;
[SerializeField]
List<SelfManageButton> btnList = null;
[SerializeField]
SerializableDictionary<SelfManageButton, UIPanels> btnDict = new SerializableDictionary<SelfManageButton, UIPanels>();
private void Awake()
{
mainCanvas = GameObject.Find("MainCanvas").GetComponent<Canvas>();
actionManager = this.GetComponent<ActionManager>();
actionManager.RegistKeyAction(KeyCode.Escape, HideAllPanel);
//버튼과 패널을 찾아오는 부분
foreach(UIPanels pnl in ComponentUtility.FindAllT<UIPanels>(mainCanvas.transform))
RegistPanel(pnl);
uiPanels.ForEach(x => x.LinkManager(this));
btnList = ComponentUtility.FindAllT<SelfManageButton>(mainCanvas.transform);
//찾은 패널을 여기에서 짝지어짐
//******* Stage *********//
ComponentUtility.LinkBtnPnl("talk", btnDict, uiPanels, btnList);
//******* TopPanel *********//
ComponentUtility.LinkBtnPnl("option", btnDict, uiPanels, btnList);
ComponentUtility.LinkBtnPnl("credits", btnDict, uiPanels, btnList);
ComponentUtility.LinkBtnPnl("developplan", btnDict, uiPanels, btnList);
ComponentUtility.LinkBtnPnl("book", btnDict, uiPanels, btnList);
ComponentUtility.LinkBtnPnl("stamina", btnDict, uiPanels, btnList);
ComponentUtility.LinkBtnPnl("shop", btnDict, uiPanels, btnList);
//******** Bottom *********//
ComponentUtility.LinkBtnPnl("food", btnDict, uiPanels, btnList);
ComponentUtility.LinkBtnPnl("play", btnDict, uiPanels, btnList);
actionManager.initFlag[nameof(UIManager)] = true;
}
public void RegistPanel(UIPanels uiPanel)
{
uiPanels.Add(uiPanel);
uiPanel.LinkManager(this);
uiPanel.SetIndex(uiPanels.IndexOf(uiPanel));
}
[SerializeField]
Stack<int> indexStack = new Stack<int>();
public void ShowPanel(int index, List<UIPanels.textFactor> factor = null)
{
if (index < 0 || index >= uiPanels.Count)
return;
foreach (UIPanels pnl in ComponentUtility.FindAllT<UIPanels>(uiPanels[index].transform))
HidePanel(pnl);
uiPanels[index].gameObject.SetActive(true);
uiPanels[index].Init(factor);
indexStack.Push(index);
}
public void HidePanel(UIPanels pnl)
=> HidePanel(pnl.thisIndex);
public void HidePanel(int index){
if (index < 0 || index >= uiPanels.Count)
return;
if (indexStack.Count <= 0 || index != indexStack.Peek())
return;
uiPanels[index].gameObject.SetActive(false);
indexStack.Pop();
}
public void HideAllPanel(){
foreach (UIPanels panel in uiPanels)
panel.gameObject.SetActive(false);
indexStack.Clear();
}
}
'개발 조각글' 카테고리의 다른 글
Unity - 유닛테스트를 염두한 함수 설계 (0) | 2022.08.08 |
---|---|
Unity Refactoring - 달팽이키우기 클래스간 관계 (0) | 2022.08.04 |
Python - xlsx to csv (0) | 2022.07.29 |
Unity Refactoring - 달팽이키우기 구조 재설계 (0) | 2022.07.29 |
Unity Refactoring - 달팽이 키우기 리팩토링 준비 (0) | 2022.07.28 |