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 받는 것을 지가 다 받아 방해할 수 있기 때문이다.
'공부 > 인프런 - Rookiss' 카테고리의 다른 글
Part 3-8-1. Scene : Scene Manager (0) | 2023.08.26 |
---|---|
Part 3-7-5. UI : 인벤토리 실습 (0) | 2023.08.25 |
Part 3-7-3. UI : UI 자동화 (0) | 2023.08.24 |
Part 3-7-2. UI : Button Event, IsPointerOverGameObject() (0) | 2023.08.23 |
Part 3-7-1. UI : 기초, Rect Transform (앵커) (0) | 2023.08.23 |