팝업 시스템

[RequireComponent(typeof(IMyObjectPool))]
    public class PopupManager : MonoBehaviour
    {
        [Header("Event")]
        [SerializeField] private PopupEventChannel _popupEventChannel;

        [Header("Data")]
        [SerializeField] private PopupCollection _popupCollection;

        private ListStack<IPopup> _openPopupStack = new();
        private IMyObjectPool _pool;

        private void Awake() => _pool = transform.GetComponent<IMyObjectPool>();
        private void OnEnable() => RegisterEvent();
        private void OnDisable() => UnRegisterEvent();
        private void Start() => InitializePool();

        private void RegisterEvent()
        {
            _popupEventChannel.OnPopupOpen += HandlePopupOpen;
            _popupEventChannel.OnPopupClose += HandlePopupClose;
            _popupEventChannel.OnAllPopupClose += HandleAllPopupClose;
            _popupEventChannel.OnRecentPopupClose += HandleRecentPopupClose;
            _popupEventChannel.OnPopupBackButtonClose += HandleBackButton;
        }
        private void UnRegisterEvent()
        {
            _popupEventChannel.OnPopupOpen -= HandlePopupOpen;
            _popupEventChannel.OnPopupClose -= HandlePopupClose;
            _popupEventChannel.OnAllPopupClose -= HandleAllPopupClose;
            _popupEventChannel.OnRecentPopupClose -= HandleRecentPopupClose;
            _popupEventChannel.OnPopupBackButtonClose -= HandleBackButton;
        }
        private void InitializePool() => _popupCollection.GetAllList().ForEach(type => _pool.SetPool(type.PopupPrefab, type.PopupPrefab.name));

        // 팝업 열기 요청 처리
        private void HandlePopupOpen(PopupType type)
        {
            IPopup popup = ContainPopup(type);
            // 현재 열려있는 팝업 닫기 && 닫기 방지가 아닌 경우
            if (popup != null)
            {
                if(!type.PreventPopupClose) popup.PopupClose();
            }
            else
            {
                if (type.IsToggleButton == true) CloseAllPopups();       // 토글 팝업이면 기존창 전부 닫기

                var popupObject = _pool.Get(type.PopupPrefab, type.PopupPrefab.name);
                if (popupObject == null) return;

                popup = popupObject.GetComponent<IPopup>();
                popup.Setup(type);

                // 팝업 스택에 추가
                _openPopupStack.Push(popup);
                popupObject.transform.SetAsLastSibling();

                RectTransform popupRect = popupObject.GetComponent<RectTransform>();
                popupRect.SetFullStretch();
            }
        }

        // 뒤로가기 버튼 처리
        private void HandleBackButton()
        {
            if(GetPopupCount() > 0)
            {
                IPopup topPopup = PeekPopup();
                if (topPopup != null && topPopup.PopupType.IsBackButtonClosable)
                {
                    ClosePopup(topPopup);
                    _openPopupStack.Pop();
                }
            }
            else
            {
                _popupEventChannel.PopupStackEmpty();                       // 닫을 팝업이 없을 경우 이벤트 발생 (종료 팝업 대응)
            }
        }

        // 최상단 팝업 닫기
        private void HandleRecentPopupClose()
        {
            IPopup topPopup = _openPopupStack.Pop();
            if (topPopup != null)
            {
                ClosePopup(topPopup);
            }
        }

        // 팝업 닫기
        private void HandlePopupClose(IPopup popup)
        {
            _openPopupStack.RemoveAt(popup);
            ClosePopup(popup);
        }

        private void HandleAllPopupClose() => CloseAllPopups();

        private void CloseAllPopups()
        {
            _openPopupStack.Clear();
            _pool.ClearAll();
        }

        private void ClosePopup(IPopup popup)
        {
            if (popup is MonoBehaviour popupObject)
            {
                var type = popup.PopupType;
                _pool.Release(popupObject.gameObject, type.PopupPrefab.name);
                
                if (type.IsGarbageCollect) GC.Collect();                // 팝업 닫을 때 GC
            }
        }

        private int GetPopupCount() => _openPopupStack.Count;
        private IPopup PeekPopup() => _openPopupStack.Count > 0 ? _openPopupStack.Peek() : null;
        private IPopup ContainPopup(PopupType type) => _openPopupStack.ToList().FirstOrDefault(x=>x.PopupType == type);
    }

이벤트 채널

[CreateAssetMenu(menuName = "Jaewook/Library/UI/Popup/PopupEventChannel", fileName = "PopupEventChannel")]
public class PopupEventChannel : ScriptableObject
{
    public event Action<PopupType> OnPopupOpen;
    public event Action<IPopup> OnPopupClose;
    public event Action OnAllPopupClose;
    public event Action OnRecentPopupClose;
    public event Action OnPopupBackButtonClose;
    public event Action OnPopupStackEmpty;

    public void RequestPopupOpen(PopupType type) => OnPopupOpen?.Invoke(type);
    public void RequestPopupClose(IPopup popup) => OnPopupClose?.Invoke(popup);
    public void AllPopupClose() => OnAllPopupClose?.Invoke();
    public void RecentPopupClose() => OnRecentPopupClose?.Invoke();
    public void PopupBackButtonClose() => OnPopupBackButtonClose?.Invoke();
    public void PopupStackEmpty() => OnPopupStackEmpty?.Invoke();
}

팝업 타입

[CreateAssetMenu(menuName = "Jaewook/Library/UI/Popup/PopupType", fileName = "PopupType")]
public class PopupType : ScriptableObject
{
    public string StringKey;
    public string Description;

    [Tooltip("팝업 버튼을 다시 눌러서 닫기 방지")] public bool PreventPopupClose = false;
    [Tooltip("백버튼 닫기 적용 여부")] public bool IsBackButtonClosable = false;
    [Tooltip("토글 타입 여부")] public bool IsToggleButton = false;
    [Tooltip("Dim 영역 생성 여부")] public bool IsDimActive = true;
    [Tooltip("Dim 영역 닫기 적용 여부")] public bool IsDimClosable = true;
    [Tooltip("정지 팝업 여부")] public bool IsPausePopup = false;
    [Tooltip("GC 여부")] public bool IsGarbageCollect = false;
    public GameObject PopupPrefab;
}

팝업 프리팹 컨트롤러