공부/인프런 - Rookiss

Part 3-7-4. UI : UI Manager, Blocker

셩잇님 2023. 8. 25. 12:30
반응형

 

 

UI

UI 자동화를 위한 클래스 구조

 

📜Managers 👉 📜UIManager 를 싱글톤으로 관리

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

 

📜PlayerController 👉 📜UIManager의 UI 캔버스 프리팹 생성해주는 ShowPopupUI(팝업 UI 경우) 함수를 호출시켜 UI 띄우기

    void Start()
  {
      Managers.Input.MouseAction -= OnMouseClicked;
      Managers.Input.MouseAction += OnMouseClicked;

      // 임시로 생성 테스트를 진행한다 👻
      Managers.UI.ShowPopupUI<UI_Button>();
  }

 

📜UIManager는 게임 씬 상에서 생길 여러가지 *UI 캔버스 프리팹* 들의 생성과 삭제를 관리하는 스크립트로 사용할 것이다. 팝업 UI 캔버스들은 렌더링 순서 또한 관리되어야 한다. sort order

 

캔버스 UI 종류


두가지로 구분되어 진다.

1. 고정UI (씬단위로 항상 화면 고정 = 체력바, 마나바, 경험치바 등)

대표 조상 📜UI_Scene - 📜UI_Inven

 

2. 팝업UI (사용자가 클릭해야 활성화, 가장 나중에 띄운 팝업부터 비활되도록 Stack 으로 관리한다)

대표 조상 📜UI_PopUp - 📜UI_Button

 


 

UI_Base

public abstract void Init();

 

📜UI_PopUp

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

    public virtual void ClosePopupUI()  // 팝업이니까 고정 캔버스(Scene)과 다르게 닫는게 필요
    {
        Managers.UI.ClosePopupUI(this);
    }
}

 

UI 캔버스 세팅 (오브젝트에 Canvas 컴포넌트 sorting order 세팅) 👉 Init

  • 📜UIManager 의 SetCanvas 호출
  • 팝업 UI니까 캔버스들을 sorting 해야 하므로 true 전달 (밑에 📜UIManager 참고)

 

 

UI 닫기 (캔버스 프리팹 파괴) 👉 ClosePopupUI

  • 팝업이니까 고정 캔버스(Scene)과 다르게 닫는게 필요
  • 📜UIManager 의 ClosePopupUI 호출

 

 

📜UI_Scene

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

 

UI캔버스 세팅 (오브젝트에 Canvas 컴포넌트 sorting order 세팅) 👉 Init
📜UIManager 의 SetCanvas 호출

  • 고정 UI니까 sorting 할 필요가 없으므로 false 전달 (밑에 📜UIManager 참고) 고정 UI는 그냥 sort order 값이 가장 최소값인 0 으로 고정시킬 것. (가장 먼저 그려져 밑에서 그려지게)

 

 


 

 

UI Manager

SetCanvas 👉 go 오브젝트의 캔버스 컴포넌트 가져와(GetOrAddComponent를 통해 없다면 붙여서라도 가져옴) sort order값 세팅
Show~ 👉 캔버스 UI 프리팹 생성
Close~ 👉 캔버스 UI 오브젝트 파괴

 

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

public class UIManager
{
    int _order = 10; // 현재까지 최근에 사용한 오더

    Stack<UI_Popup> _popupStack = new Stack<UI_Popup>(); // 오브젝트 말고 컴포넌트를 담음. 팝업 캔버스 UI 들을 담는다.
    UI_Scene _sceneUI = null; // 현재의 고정 캔버스 UI
    public GameObject Root
    {
        get
        {
			      GameObject root = GameObject.Find("@UI_Root");
			      if (root == null)
				        root = new GameObject { name = "@UI_Root" };
                    return root;
		    }
    }

 

1. 고정 UI

  • sort order 값이 0 으로 고정. (가장 먼저 그려져 밑에서 그려지게)
  • 스택으로 관리될 필요 없음. 가장 바탕이 되는 UI이므로 하나만 있으니!

 

 

2. 팝업 UI

  • sort order을 편의상 고정 UI와 겹치지 않도록 10부터 시작하게 할 것이다. 팝업 캔버스 UI들은 10, 11, 12, 13,… 렌더링 순서를 부여받음. 늦게 생성된 것일 수록 늦은 순서! (가장 위에 그려지도록)
  • 여러개가 생길 수 있으며, 가장 나중에 생성된 팝업부터 먼저 닫혀야 하므로 생성된 팝업 캔버스 UI 오브젝트들을 스택으로 관리할 것.

 

 

3. @UI_Root라는 이름의 오브젝트를 없다면 만들어서라도 리턴해주는 프로퍼티 Root

  • 이게 필요한 이유는, Hierarchy 상의 오브젝트들도 마치 폴더 안에 있는것처럼 관련 있는 것들끼리 종류별로 이름을 구분한 빈 오브젝트의 자식으로 넣어 정리할 것이기 때문이다. UI 오브젝트들은 이 @UI_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 // soring 요청 X 라는 소리는 팝업이 아닌 일반 고정 UI
        {
            canvas.sortingOrder = 0;
        }
    }

 

오브젝트의 캔버스 세팅 

 

canvas에 Canvas 컴포넌트 가져옴(없으면 붙여서라도)

캔버스의 RenderMode를 ScreenSpaceOverlay 모드로 세팅

overrideSorting을 통해 혹시라도 중첩캔버스라 자식 캔버스가 있더라도 부모 캔버스가 어떤 값을 가지던 나는 내 오더값을 가지려 할때 true canvas.overrideSorting = true;

팝업 UI의 경우 canvas.sortingOrder를 _order로 세팅하고 _order 증가시키기

고정 UI의 경우 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/Scene/{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)) // 이름을 안받았다면 T로 ㄱㄱ
            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;
    }

 

캔버스 프리팹 생성하기


1. 고정 UI 캔버스 프리팹 생성 ShowSceneUI

T는 📜UI_Scene 자식들만 가능

프리팹을 Instantiate으로 생성하고 _sceneUI에 바인딩 후 이를 리턴

Root프로퍼티를 통해 @UI_Root의 자식으로 넣어줌

 

2. 팝업 UI 캔버스 프리팹 생성 ShowPopupUI

T는 📜UI_PopUp 자식들만 가능

프리팹을 Instantiate으로 생성하고 popup에 바인딩 후 스택에 추가. 그리고 이를 리턴

Root프로퍼티를 통해 @UI_Root의 자식으로 넣어줌

 

_order++처리를 ShowPopupUI 에서 안하고 SetCanvas에서 해준 이유는, 캔버스 프리팹을 통해 생성되는 경우(ShowPopupUI 사용해서) 말고! 이미 게임 시작전부터 static 하게 존재하고 있던, 프리팹으로부터 생성된게 아닌 팝업 UI일 경우도 있을 수 있기 때문이다. 그런 경우엔 ShowPopupUI를 거치지 않기 때문에 sort order 관리가 안될 것이다. 그래서 SetCanvas에서 해준 것!

 

    public void ClosePopupUI(UI_Popup popup) // 안전 차원
    {
		    if (_popupStack.Count == 0) // 비어있는 스택이라면 삭제 불가
			      return;

        if (_popupStack.Peek() != popup)
        {
            Debug.Log("Close Popup Failed!"); // 스택의 가장 위에있는 Peek() 것만 삭제할 수 잇기 때문에 popup이 Peek()가 아니면 삭제 못함
            return;
        }

        ClosePopupUI();
    }

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

        UI_Popup popup = _popupStack.Pop();
        Managers.Resource.Destroy(popup.gameObject);
        popup = null;
        _order--; // order 줄이기
    }

    public void CloseAllPopupUI()
    {
        while (_popupStack.Count > 0)
            ClosePopupUI();
    }
}

 

팝업은 Close 시킬 함수가 필요하다.

 


 

팝업 외의 드래그는 안되도록 막아주는 Blocker

 

 

UI_Button은 팝업 캔버스 UI이다. 따라서 이게 팝업으로 생성되면 그 아래 깔리는 팝업들과 고정 UI위에 생성된다. 근데 이 UI_Button 팝업 캔버스 UI 와 관련된 마우스 이벤트 클릭, 드래그 이벤트 같은 것들은 UI_Button 에서만 받을 수 있어야 하는데, 밑에 깔려있는 다른 UI들에서도 마우스, 드래그 등 이벤트가 발생할 수 있다.

 

 따라서 UI_Button 밑에 깔려 있는 UI 들의 이벤트를 막기 위해서 UI_Button의 투명한 이미지이며 크게 덮어서 밑에 깔린 다른 UI들도 덮어버릴 수 있는 Blocker를 만든다. Blocker 이미지가 대신 Rarycast 를 받아UI_Button 밑에 깔려있는 UI들은 이벤트를 받지 못하도록!! Rarcast를 받지 못하도록!! 덮어버리는 것이다.

 

 이렇게 해주면 UI_Button 팝업 UI 사용시 밑에 깔려있는 UI들에는 이벤트가 발생하지 않을 것이다. Blocker가 대신 다 받아버려서 방해하기 때문이다. 팝업 UI가 뜨면 그 밑에 깔린 UI들은 마우스 이벤트 등에 반응하지 않도록 해준 것이다. 따라서 당연히 Blocker는 Raycast Target이 체크되어 있어야 한다.

 Hierarchy상 아래에 있을 수록 가장 나중에 그려지고 가장 위에 그려지므로 Raycast를 먼저 받을 수 있다. ⭐ 따라서 Blocker를 UI_Button 의 "가장 선두에 있는 자식"으로 순서를 설정해주어야 한다. 이 Blocker가 밑에 있으면 UI_Button의 자식들보다 나중에 그려져 이벤트를 받아야할 UI_Button의 자식들의 Raycast 받는 것을 지가 다 받아 방해할 수 있기 때문이다.

 

 

 

반응형