강의, 책/[Unity] C#과 유니티로 만드는 MMORPG 게임 개발 시리즈

Section 7. UI - UI Manager

hye3193 2024. 1. 30. 20:47

UI를 팝업 UI와 고정형 UI로 구분하여 생각하기

UI > Popup > UI_Popup.cs, UI > Scene > UI_Scene.cs

스크립트를 각각 만들어주고, 이 베이스 스크립트들은 MonoBehaviour 대신 UI_Base를 상속받는다

- 팝업과 씬 스크립트를 새로 만들 때 위 베이스 스크립트를 상속 받아서 사용

- Popup, Scene 폴더 내 스크립트들은 이제 UI_Base 대신 UI_Popup, UI_Scene 을 상속받으면 됨

 

UIManager _ui = new UIManager();
public static UIManager UI { get { return Instance._ui; } }

UIManager 스크립트를 만들어준 뒤, Manager.cs에 연결시켜준다

 

public class UIManager
{
    int _order = 10;
    
    Stack<UI_Popup> _popupStack = new Stack<UI_Popup>();
    UI_Scene _sceneUI = null;
    
    public GameObject Root
    {
        get
        {
            GameObject root = GameObject.Find("@UI_Root");
            if (root == null)
                root = new GameObject { name = "@UI_Root" };
            return root;
        }
    }
    
    public void SetCanvas(GameObject go, bool sort = true)
    {
        Canvas canvas = Util.GetOrAddComponent<Canvas>(go);
        canvas.renderMode = RenderMode.ScreenSpaceOverlay;
        canvas.overrideSorting = true;
        
        if (sort)
        {
            canvas.sortingOrder = _order;
            _order++;
        }
        else
        {
            canvas.sortingOrder = 0;
        }
    }
    
    public T ShowSceneUI<T>(string name = null) where T : UI_Scene
    {
        if (string.IsNullOrEmpty(name))
            name = typeof(T).Name;

        GameObject go = Managers.Resource.Instantiate($"UI/Popup/{name}");
        T sceneUI = Util.GetOrAddComponent<T>(go);
        _sceneUI = sceneUI;

        go.transform.SetParent(Root.transform);

        return sceneUI;
    }
    
    public T ShowPopupUI<T>(string name = null) where T : UI_Popup
    {
        if (string.IsNullOrEmpty(name))
            name = typeof(T).Name;
        
        GameObject go = Managers.Resource.Instantiate($"UI/Popup/{name}");
        T popup = Util.GetOrAddComponent<T>(go);
        _popupStack.Push(popup);
        
        go.transform.SetParent(Root.transform);
        
        return popup;
    }
    
    public void ClosePopupUI(UI_Popup popup)
    {
        if (_popupStack.Count == 0)
            return;
        
        if (_popupStack.Peek() != popup)
        {
            Debug.Log("Close Popup Failed!");
            return;
        }
    }
    
    public void ClosePopupUI()
    {
        if (_popupStack.Count == 0)
            return;
        
        UI_Popup popup = _popupStack.Pop();
        Managers.Resource.Destory(popup.gameObject);
        popup = null;
        _order--;
    }
    
    public void CloseAllPopupUI()
    {
        while (_popupStack.Count > 0)
            ClosePopupUI();
    }
}

UIManager 스크립트에서 팝업을 여닫는 함수들을 만들어준다

* ClosePopupUI 함수는 버전을 두 가지로 만들어, 닫을 팝업의 이름을 명시해주는(안정성 높은) 버전도 사용할 수 있게 한다

* Stack 사용할 때는 count가 0인지 체크하는 습관을 들일 것

* canvas.overrideSorting: 부모의 sort order에 상관없이 자신만의 sort order를 가진다

UI_Button ui = Managers.UI.ShowPopupUI<UI_Button>();
Managers.UI.ClosePopupUI(ui);

위와 같이 사용하면 된다(안정성 높은 버전)

* 만일 스크립트명과 팝업프리팹명이 다르다면 ShowPopupUI 함수에 프리팹명을 인자로 넘겨주어야 한다

 

public class UI_Scene : UI_Base
{
    public virtual void Init()
    {
        Managers.UI.SetCanvas(gameObject, false);
    }
}

UI_Popup, UI_Scene 스크립트에 위와 같이 Init 함수를 만들어준다(popup은 두번째 인자(sort)가 true)

 

private void Start()
{
    Init();
}

public override void Init()
{
    base.Init();
    
    // 기존 Start 함수에 들어가 있던 코드
}

그리고 UI_Button 스크립트(UI_Popup을 상속받는 스크립트)에 위와 같이 Init 함수를 override 해서 그 안에서 base(부모)의 Init 함수도 호출해준다

 

마지막으로 UI_Popup 스크립트에서 close popup 함수를 추가해준다

public class UI_Popup : UI_Base
{
    public virtual void Init()
    {
        Managers.UI.SetCanvas(gameObject, true);
    }
    
    public virtual void ClosePopupUI()
    {
        Managers.UI.ClosePopupUI(this);
    }
}

내부에서 close popup ui 함수를 좀 더 쉽게 호출하기 위함이다

 

 

+ UI 팝업 생성 시 뒤에 있는 부분이 클릭되는 것을 방지하기 위해 블로커를 만들어둔다

색상을 알파 0으로 맞춰두고 모든 공간을 차지하도록 사이즈를 키워주면 된다

아래에 위치할수록 늦게 랜더링 되기 때문에, Blocker는 맨 위에 올라가야 UI_Button 내 다른 것들을 누를 때 방해받지 않을 수 있다