전반적인 팝업 관리 시스템
모듈화하여 언제든 쉽게 불러다 사용하는 것이 목적
Event Channel를 통한 이벤트 관리
Object Pool을 사용해 팝업 관리
[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;
}