본문 바로가기
1일 1 구현

[1일 1 구현] UI Manager

by LemongO 2024. 5. 13.

Rookiss 님의 UIManager를 기반으로 작성

📌 구현 이유

  • 팀 프로젝트 Null Reference Exception을 만들며 구성했던 UI 구조가 마구잡이로 섞임
    • Rookiss 식으로 만들면 생성/제거 이 두 기능밖에 안 하지만
      Active(true/false) 기능까지 넣어서 이도저도 아니게 돼버림
    • Rookiss 식 + [serializefield] 어트리뷰트를 사용하는 드래그 드롭 방식도 씀
  • 유저 피드백 중 ESC 키로 이전 UI 또는 Popup 끄기 키를 넣어달라는 요청 해결 못함
  • 기왕 따라 쓸 거라면 100% 이해를 하는 게 맞다고 생각
  • 내가 원하는 기능으로 커스텀할 수 있도록

 

📌 구현할 기능

  • UIManager.cs
  • UIBase.cs
  • 팝업 UI (모바일게임의 이벤트 UI, 알림 팝업 등)
  • 연결 UI ([오버워치] 메인메뉴 - 영웅 - 영웅선택 - 스킨) : Esc키로 이전 팝업으로 돌아가기

 

UIManager 기본 구조

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UIManager : MonoBehaviour
{
    public static UIManager Instance;    

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }            
        else
            Destroy(gameObject);
    }

    public void ShowPopupUI()
    {
        // To Do - 팝업 UI 열기        
    }

    public void ClosePopupUI()
    {
        // To Do - 팝업 UI 닫기
    }

    public void ShowLinkedUI()
    {
        // To Do - 연속 UI 띄워주기, 마지막 UI Active(false)
    }

    public void UndoLinkedUI()
    {
        // To Do - 연속 UI 뒤로가기
    }
}

 

 

나중에 구조가 바뀌긴 하겠지만,

일단 MonoBehaviour를 상속받고 가장 단순한 방법으로 싱글톤을 구현한 UIManager이다.

 

주어질 기능은 크게 4가지 인데

  • ShowPopupUI : Popup UI 열기
  • ClosePopupUI : Popup UI 닫기
  • ShowLinkedUI : Linked UI 띄우기 및 마지막 Linked UI 비활성화
  • UndoLinkedUI : 마지막 Linked UI 비활성화 및 이전 LinkedUI 띄우기

나는 여기서 크게 두 가지 기능을 하는 UI를 생각하고 만들 예정이다.

 

Popup : 알림등 열기/닫기 기능의 UI

Linked : 메뉴 - 메뉴 - 메뉴 와 같은 많은 정보를 담고있는 계층 구조의 UI

 

 

좀더 명확하게 설명하자면

Popup은 1회성으로 한 번 보여주고 마는 그런 간단한 알림류의 UI 이다

주로하는 FPS 게임 기준으로 [파티초대, 친구요청, 이벤트알림, 게임종료, 시즌변경]
정도일 것 같다.

 

그리고 Linked는 오버워치를 기준으로 보면

영웅 메뉴를 선택하면 다양한 정보를 담은 새로운 UI가 화면에 나타나는데

계속해서 타고 타고 들어가다가 ESC를 누르면 다시 이전 UI로 돌아오는 구조이다.

 

그리고 Popup과 Linked를 나눈 다른 이유는

Popup은 Close 시 제거를, Linked는 Undo 시 비활성화를 할 목적이기 때문이다.

Popup은 나타나는 정보가 단순하고 활성화 빈도가 높지 않지만

Linked는 나타나는 정보가 복잡하고 활성화 빈도가 높기 때문이다.

 

실제로 팀 프로젝트의 Module UI도 그랬고 오버워치의 [진척도] 메뉴도 그렇고

처음 UI를 띄울 땐 어느정도의 로딩 시간이 필요했다.

(물론 우리 프로젝트는 로딩을 따로 안 만들어서 아주 잠깐 끊기는 현상이 발생했지만...)

 

Popup

private Stack<UI_Popup> _popupStack = new Stack<UI_Popup>();
private int _order = 5;

public T ShowPopupUI<T>(string path) where T : UI_Popup
{
    if (string.IsNullOrEmpty(path))
        path = typeof(T).Name;

    GameObject prefab = Resources.Load<GameObject>($"Prefabs/UI/Popup/{path}");

    if(prefab == null)
    {
        Debug.Log("경로가 올바르지 않습니다.");
        return null;
    }

    T popup = Instantiate(prefab).GetOrAddComponent<T>();
    _popupStack.Push(popup);

    return popup;
}

public void ClosePopupUI(UI_Popup popup)
{
    if (_popupStack.Count == 0)
        return;

    if (_popupStack.Peek() != popup)
    {
        Debug.LogWarning("팝업이 일치하지 않습니다.");
        return;
    }

    UI_Popup stackPopup = _popupStack.Pop();
    Destroy(stackPopup.gameObject);
    _order--;
}

간단하게 만들어 본 PopupUI용 메서드다.

 

ShowPopupUI

따로 path를 지정해주지 않는다면 클래스 이름이 곧 UI 자체를 의미하고

프리팹을 찾아 생성 및 T에 할당, Stack에 등록 후 T를 return 한다.

 

ClosePopupUI

닫고자 하는 popup을 매개변수로 넘겨준 후 일치하지 않으면 return

Stack 에서 꺼내와 가장 마지막의 popup을 삭제한다.

 

 

Linked

private List<UI_Linked> _linkList = new List<UI_Linked>();

public T ShowLinkedUI<T>(string path = null) where T : UI_Linked
{
    if(string.IsNullOrEmpty(path))
        path = typeof(T).Name;

    GameObject prefab = Resources.Load<GameObject>($"Prefabs/UI/Linked/{path}");

    if (prefab == null)
    {
        Debug.Log("경로가 올바르지 않습니다.");
        return null;
    }

    if (_linkList.Count > 0)
        _linkList[_linkList.Count - 1].gameObject.SetActive(false);

    T linked = Instantiate(prefab).GetOrAddComponent<T>();
    _linkList.Add(linked);

    return linked;
}

public void UndoLinkedUI()
{
    if (_linkList.Count == 0)
        return;        

    UI_Linked lastUI = _linkList[_linkList.Count - 1];
    _linkList.RemoveAt(_linkList.Count - 1);
    Destroy(lastUI.gameObject);

    if (_linkList.Count > 0)
        _linkList[_linkList.Count - 1].gameObject.SetActive(true);
}

역시나 간단하게 만든 LinkedUI용 메서드다.

 

ShowLinkedUI

Popup과 비슷한 흐름이지만 List를 사용해 관리를 한다.

 

CloseLinkedUI

마지막 UI를 꺼낸 뒤 리스트 마지막은 삭제 후 마지막 UI 오브젝트는 비활성화

 

확인결과 잘 되기는 하지만 아직까지 여러 문제들이 남아있다.

 

  • Linked는 활성화/비활성화 이지만 현재 코드에선 Destroy 되는 점
  • 비활성화 시키더라도 나중에 꺼낼 땐 현재 코드에서는 Instantiate 되는 점

ObjectPooling을 활용하지 않으면 해결되지 않을 것 같은 문제들로 보인다.

오늘은 여기까지만 하고 내일은 UI_Base를 구현해보도록 해야겠다.

'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 Base  (0) 2024.05.13
[1일 1구현] 시작  (0) 2024.05.12