Ui 시스템 구조 강화

This commit is contained in:
NTG_Lenovo 2025-07-22 16:46:37 +09:00
parent 4d9fd78c3a
commit aae35a3563
9 changed files with 219 additions and 226 deletions

View File

@ -4,9 +4,15 @@ namespace DDD
{
public abstract class BaseUi : MonoBehaviour
{
protected GameObject _panel;
public virtual bool IsBlockingTime => false;
public virtual bool IsOpen => gameObject.activeSelf;
protected virtual void Awake()
{
_panel = transform.Find(CommonConstants.Panel).gameObject;
}
protected virtual void Start()
{
TryRegister();
@ -20,7 +26,7 @@ protected virtual void OnDestroy()
protected virtual void TryRegister() { }
protected virtual void TryUnregister() { }
public virtual void Open() => gameObject.SetActive(true);
public virtual void Close() => gameObject.SetActive(false);
public virtual void Open() => _panel.SetActive(true);
public virtual void Close() => _panel.SetActive(false);
}
}

View File

@ -14,20 +14,27 @@ public class GlobalMessageUi : BaseUi, IEventHandler<ShowGlobalMessageEvent>
private readonly Queue<ShowGlobalMessageEvent> _messageQueue = new();
private bool _isDisplayingMessage = false;
private void Awake()
protected override void Awake()
{
base.Awake();
_canvasGroup = GetComponent<CanvasGroup>();
_messageText = GetComponentInChildren<TextMeshProUGUI>();
_canvasGroup.alpha = 0;
_messageText.text = null;
}
protected override void TryRegister()
{
base.TryRegister();
EventBus.Register(this);
}
protected override void OnDestroy()
protected override void TryUnregister()
{
base.OnDestroy();
base.TryUnregister();
EventBus.Unregister(this);
_fadeTween?.Kill();

View File

@ -1,7 +1,78 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
namespace DDD
{
public class PopupUi : BaseUi
{
protected UiInputBindingSo _uiInputBindingSo;
protected readonly List<(InputAction action, Action<InputAction.CallbackContext> handler)> _registeredHandlers = new();
protected override async void TryRegister()
{
base.TryRegister();
UiManager.Instance.RegisterPopupUI(this);
// So의 이름을 통일 : TestUi_UiInputBindingSo
string addressableKey = $"{GetType().Name}_{DataConstants.UiInputBindingSo}";
_uiInputBindingSo = await AssetManager.LoadAsset<UiInputBindingSo>(addressableKey);
Debug.Assert(_uiInputBindingSo != null, "_uiInputBindingSo != null");
foreach (var binding in _uiInputBindingSo.Bindings)
{
if (binding.InputAction == null) continue;
var action = binding.InputAction.action;
if (action == null) continue;
var handler = new Action<InputAction.CallbackContext>(ctx =>
{
if (UiManager.Instance.IsTopPopup(this))
{
OnInputPerformed(binding.ActionName, ctx);
}
});
action.Enable();
action.performed += handler;
_registeredHandlers.Add((action, handler));
}
}
protected override void TryUnregister()
{
base.TryUnregister();
UiManager.Instance.UnregisterPopupUI(this);
foreach (var (action, handler) in _registeredHandlers)
{
if (action != null)
{
action.performed -= handler;
action.Disable();
}
}
_registeredHandlers.Clear();
}
public override void Open()
{
base.Open();
transform.SetAsLastSibling();
if (UiManager.Instance.IsTopPopup(this))
{
InputManager.Instance.SwitchCurrentActionMap(_uiInputBindingSo.InputActionMaps);
}
}
protected virtual void OnInputPerformed(string actionName, InputAction.CallbackContext ctx) { }
public InputActionMaps GetInputActionMaps() => _uiInputBindingSo.InputActionMaps;
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace DDD
@ -7,6 +8,8 @@ namespace DDD
public class UiManager : Singleton<UiManager>, IManager, IEventHandler<OpenPopupUiEvent>, IEventHandler<ClosePopupUiEvent>
{
private readonly Dictionary<Type, PopupUi> _popupUIs = new();
private readonly Stack<PopupUi> _popupStack = new();
private InputActionMaps _previousActionMap = InputActionMaps.None;
private readonly object _uiPauseRequester = new();
@ -51,14 +54,18 @@ public void Invoke(OpenPopupUiEvent evt)
{
if (_popupUIs.TryGetValue(evt.UiType, out var popup))
{
popup.Open();
if (popup.IsBlockingTime)
if (!popup.IsOpen)
{
var timeScaleChangeEvent = GameEvents.RequestTimeScaleChangeEvent;
timeScaleChangeEvent.Requester = popup;
timeScaleChangeEvent.NewTimeScale = 0f;
EventBus.Broadcast(timeScaleChangeEvent);
popup.Open();
PushPopup(popup);
if (popup.IsBlockingTime)
{
var timeScaleChangeEvent = GameEvents.RequestTimeScaleChangeEvent;
timeScaleChangeEvent.Requester = popup;
timeScaleChangeEvent.NewTimeScale = 0f;
EventBus.Broadcast(timeScaleChangeEvent);
}
}
}
}
@ -67,16 +74,65 @@ public void Invoke(ClosePopupUiEvent evt)
{
if (_popupUIs.TryGetValue(evt.UiType, out var popup))
{
popup.Close();
if (popup.IsBlockingTime)
if (popup.IsOpen)
{
var timeScaleChangeEvent = GameEvents.RequestTimeScaleChangeEvent;
timeScaleChangeEvent.Requester = popup;
timeScaleChangeEvent.NewTimeScale = 1f;
EventBus.Broadcast(timeScaleChangeEvent);
popup.Close();
PopPopup(popup);
if (popup.IsBlockingTime)
{
var timeScaleChangeEvent = GameEvents.RequestTimeScaleChangeEvent;
timeScaleChangeEvent.Requester = popup;
timeScaleChangeEvent.NewTimeScale = 1f;
EventBus.Broadcast(timeScaleChangeEvent);
}
}
}
}
public bool IsTopPopup(PopupUi popup)
{
return _popupStack.Count > 0 && _popupStack.Peek() == popup;
}
public void PushPopup(PopupUi popup)
{
if (_popupStack.Contains(popup)) return;
if (_popupStack.Count == 0)
{
_previousActionMap = InputManager.Instance.GetCurrentActionMap();
}
_popupStack.Push(popup);
}
public void PopPopup(PopupUi popup)
{
if (_popupStack.Count == 0) return;
if (_popupStack.Peek() == popup)
{
_popupStack.Pop();
}
else
{
var temp = _popupStack.Reverse().Where(p => p != popup).Reverse().ToList();
_popupStack.Clear();
foreach (var p in temp)
{
_popupStack.Push(p);
}
}
if (_popupStack.TryPeek(out var topPopup) && topPopup.IsOpen)
{
InputManager.Instance.SwitchCurrentActionMap(topPopup.GetInputActionMaps());
}
else
{
InputManager.Instance.SwitchCurrentActionMap(_previousActionMap);
}
}
}
}

View File

@ -1,50 +1,44 @@
using System;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.InputSystem;
namespace DDD
{
public static class InputActionMapExtensions
{
public static string ToName(this InputActionMaps map)
{
return map.ToString();
}
}
public enum InputActionMaps
{
None = 0,
Ui = 1,
Restaurant = 2,
RestaurantUi = 3
}
[Flags]
public enum RestaurantActions
{
None = 0,
Move = 1<<0,
Dash = 1<<1,
Interact = 1<<2
}
public class InputManager : Singleton<InputManager>
public class InputManager : Singleton<InputManager>, IManager
{
private PlayerInput _currentPlayerInput;
protected override void OnAwake()
public void PreInit()
{
base.OnAwake();
_currentPlayerInput = GetComponent<PlayerInput>();
}
// public void ChangeScene(SceneType sceneType)
// {
// switch (sceneType)
// {
// case SceneType.Title:
// SwitchCurrentActionMap(InputActionMaps.Ui);
// break;
// case SceneType.Restaurant:
// SwitchCurrentActionMap(InputActionMaps.Restaurant);
// break;
// default:
// throw new System.Exception("Invalid scene name");
// }
// }
public Task Init()
{
return Task.CompletedTask;
}
public void PostInit()
{
}
private bool IsNullCurrentPlayerInput()
{
@ -54,193 +48,25 @@ private bool IsNullCurrentPlayerInput()
return true;
}
public InputAction GetAction(InputActionMaps actionMapName, string actionName)
{
if (IsNullCurrentPlayerInput()) return null;
var actionMap = _currentPlayerInput.actions.FindActionMap(actionMapName.ToString(), true);
if (actionMap == null)
{
Debug.LogError($"Action Map '{actionMapName}' not found!");
return null;
}
var action = actionMap.FindAction(actionName, true);
if (action == null)
{
Debug.LogError($"Action '{actionName}' not found in Action Map '{actionMapName}'!");
}
return action;
}
public string GetBoundKey(InputActionMaps actionMapName, string actionName)
{
if (IsNullCurrentPlayerInput()) return null;
var actionMap = _currentPlayerInput.actions.FindActionMap(actionMapName.ToString(), true);
if (actionMap == null)
{
Debug.LogError($"Action Map '{actionMapName}' not found!");
return null;
}
var action = actionMap.FindAction(actionName, true);
if (action == null)
{
Debug.LogError($"Action '{actionName}' not found in Action Map '{actionMapName}'!");
return null;
}
// 첫 번째 바인딩에서 키 이름 가져오기
foreach (var binding in action.bindings)
{
if (!string.IsNullOrEmpty(binding.path))
{
// 키 이름만 추출
var key = InputControlPath.ToHumanReadableString(binding.path,
InputControlPath.HumanReadableStringOptions.OmitDevice);
return key;
}
}
Debug.LogWarning($"No bindings found for action '{actionName}' in Action Map '{actionMapName}'.");
return null;
}
public string GetBoundKey(InputAction inputAction)
{
if (IsNullCurrentPlayerInput()) return null;
if (inputAction == null)
{
Debug.LogError($"Action not found'!");
return null;
}
// 첫 번째 바인딩에서 키 이름 가져오기
foreach (var binding in inputAction.bindings)
{
if (!string.IsNullOrEmpty(binding.path))
{
// 키 이름만 추출
var key = InputControlPath.ToHumanReadableString(binding.path,
InputControlPath.HumanReadableStringOptions.OmitDevice);
return key;
}
}
Debug.LogWarning($"No bindings found for action '{inputAction}'");
return null;
}
public bool IsCurrentActionMap(InputActionMaps inputActionMaps)
{
if (IsNullCurrentPlayerInput()) return false;
return _currentPlayerInput.currentActionMap.ToString() == inputActionMaps.ToString();
}
public void SwitchCurrentActionMap(string inputActionMaps)
{
if (IsNullCurrentPlayerInput()) return;
_currentPlayerInput.SwitchCurrentActionMap(inputActionMaps);
}
public void SwitchCurrentActionMap(InputActionMaps inputActionMaps)
{
if (IsNullCurrentPlayerInput()) return;
if (IsNullCurrentPlayerInput() || inputActionMaps == InputActionMaps.None) return;
_currentPlayerInput.SwitchCurrentActionMap(inputActionMaps.ToString());
_currentPlayerInput.SwitchCurrentActionMap(inputActionMaps.ToName());
}
public InputActionMap GetCurrentInputActionMap()
public InputActionMaps GetCurrentActionMap()
{
if (IsNullCurrentPlayerInput()) return null;
if (IsNullCurrentPlayerInput()) return InputActionMaps.None;
return _currentPlayerInput.currentActionMap;
}
public void EnableCurrentPlayerInput()
{
if (!_currentPlayerInput) return;
_currentPlayerInput.enabled = true;
}
public void DisableCurrentPlayerInput()
{
if (IsNullCurrentPlayerInput()) return;
_currentPlayerInput.enabled = false;
}
public void DisableAllActionMaps()
{
if (IsNullCurrentPlayerInput()) return;
foreach (var element in _currentPlayerInput.actions.actionMaps)
string mapName = _currentPlayerInput.currentActionMap.name;
if (Enum.TryParse(mapName, out InputActionMaps parsedMap))
{
element.Disable();
}
}
public void DisableAllActionsExcept(string exceptActionName)
{
if (IsNullCurrentPlayerInput()) return;
var exceptAction = _currentPlayerInput.currentActionMap.FindAction(exceptActionName);
foreach (var action in _currentPlayerInput.currentActionMap.actions)
{
if (action != exceptAction)
{
action.Disable();
}
else
{
action.Enable();
}
}
}
public void EnableAllActionsMaps()
{
if (IsNullCurrentPlayerInput()) return;
foreach (var action in _currentPlayerInput.actions)
{
action.Enable();
}
}
public void EnableAction(string actionName)
{
if (IsNullCurrentPlayerInput()) return;
var action = _currentPlayerInput.currentActionMap.FindAction(actionName);
if (action == null)
{
Debug.Log($"현재 Action Map인 {_currentPlayerInput.currentActionMap}에는 {actionName} Action이 존재하지 않습니다");
return;
return parsedMap;
}
action.Enable();
}
public void DisableAction(string actionName)
{
if (IsNullCurrentPlayerInput()) return;
var action = _currentPlayerInput.currentActionMap.FindAction(actionName);
if (action == null)
{
Debug.Log($"현재 Action Map인 {_currentPlayerInput.currentActionMap}에는 {actionName} Action이 존재하지 않습니다");
return;
}
action.Disable();
Debug.LogError($"[InputManager] 알 수 없는 ActionMap 이름: {mapName}");
return InputActionMaps.None;
}
}
}

View File

@ -0,0 +1,12 @@
using System;
using UnityEngine.InputSystem;
namespace DDD
{
[Serializable]
public class UiActionBinding
{
public string ActionName;
public InputActionReference InputAction;
}
}

View File

@ -0,0 +1,12 @@
using System.Collections.Generic;
using UnityEngine;
namespace DDD
{
[CreateAssetMenu(fileName = "UiInputBindingSo", menuName = "Ui/UiInputBindingSo")]
public class UiInputBindingSo : ScriptableObject
{
public InputActionMaps InputActionMaps;
public List<UiActionBinding> Bindings = new();
}
}

View File

@ -70,6 +70,7 @@ public async Task OnReadyNewFlow(GameFlowState newFlowState)
var playerHandle = createRestaurantPlayerSoJob.OnReadyNewFlow(newFlowState);
var propHandle = createEnvironmentSoJob.OnReadyNewFlow(newFlowState);
// Combine handles and return it
InputManager.Instance.SwitchCurrentActionMap(InputActionMaps.Restaurant);
await Task.WhenAll(playerHandle, propHandle);
}
}

View File

@ -6,6 +6,7 @@ public static class CommonConstants
public const string RestaurantPlayer = "RestaurantPlayer";
public const string BaseRestaurantEnvironment = "BaseRestaurantEnvironment";
public const string Clone = "(Clone)";
public const string Panel = "Panel";
}
public static class DataConstants
@ -14,6 +15,7 @@ public static class DataConstants
public const string FoodDataSo = "FoodDataSo";
public const string EnvironmentDataSo = "EnvironmentDataSo";
public const string RestaurantPlayerDataSo = "RestaurantPlayerDataSo";
public const string UiInputBindingSo = "UiInputBindingSo";
public const string AtlasLabel = "Atlas";
public const string BasePropSpriteMaterial = "BasePropSpriteMaterial";