ui 생성 로직 변경

This commit is contained in:
NTG 2025-08-21 16:25:27 +09:00
parent 0dc138ea2f
commit ac49d77573
52 changed files with 967 additions and 1333 deletions

View File

@ -134,4 +134,5 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 41f0ee0aabb2f954d918caa8d484f646, type: 3}
m_Name:
m_EditorClassIdentifier:
<UiType>k__BackingField: 4
_enableBlockImage: 1

View File

@ -319,5 +319,6 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 828648aab79941544bf8ceb7b25b586c, type: 3}
m_Name:
m_EditorClassIdentifier:
<UiType>k__BackingField: 4
_enableBlockImage: 0
_messageText: {fileID: 1263817835881307751}

View File

@ -422,6 +422,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: eccb2d58803b65f4e82f22153315d3c6, type: 3}
m_Name:
m_EditorClassIdentifier:
<UiType>k__BackingField: 2
_enableBlockImage: 0
_filledImage: {fileID: 1182510989530764005}
_textLabel: {fileID: 5874059589008679693}

View File

@ -651,7 +651,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 6952779389930089995, guid: 4f2bf029cb06b084ba41defc8fc76731, type: 3}
propertyPath: m_Name
value: Hud
value: RestaurantHud
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects:
@ -663,8 +663,30 @@ PrefabInstance:
- targetCorrespondingSourceObject: {fileID: 8967231042952671610, guid: 4f2bf029cb06b084ba41defc8fc76731, type: 3}
insertIndex: -1
addedObject: {fileID: 8229589654595845064}
m_AddedComponents: []
m_AddedComponents:
- targetCorrespondingSourceObject: {fileID: 6952779389930089995, guid: 4f2bf029cb06b084ba41defc8fc76731, type: 3}
insertIndex: -1
addedObject: {fileID: 3263769952751147662}
m_SourcePrefab: {fileID: 100100000, guid: 4f2bf029cb06b084ba41defc8fc76731, type: 3}
--- !u!1 &3080325846008693413 stripped
GameObject:
m_CorrespondingSourceObject: {fileID: 6952779389930089995, guid: 4f2bf029cb06b084ba41defc8fc76731, type: 3}
m_PrefabInstance: {fileID: 5387070474184109230}
m_PrefabAsset: {fileID: 0}
--- !u!114 &3263769952751147662
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3080325846008693413}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 7521841876f5b054aa4a0b0081ff8425, type: 3}
m_Name:
m_EditorClassIdentifier:
<UiType>k__BackingField: 1
_enableBlockImage: 0
--- !u!224 &3940853162783645140 stripped
RectTransform:
m_CorrespondingSourceObject: {fileID: 8967231042952671610, guid: 4f2bf029cb06b084ba41defc8fc76731, type: 3}

View File

@ -811,7 +811,9 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 7b3eb65dc42aeb84eba2397a9603e94d, type: 3}
m_Name:
m_EditorClassIdentifier:
<UiType>k__BackingField: 3
_enableBlockImage: 1
InputActionMaps: 3
_uiActionsInputBinding: {fileID: 11400000, guid: 99d3d87bd43df65488e757c43a308f36, type: 2}
_messageLabel: {fileID: 3495127426411772216}
_messageLabelLocalizeStringEvent: {fileID: 7334955628972040157}

View File

@ -6338,6 +6338,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 46c8396c996c804449b383960b44e812, type: 3}
m_Name:
m_EditorClassIdentifier:
<UiType>k__BackingField: 3
_enableBlockImage: 0
InputActionMaps: 3
_uiActionsInputBinding: {fileID: 11400000, guid: 8073fcaf56fc7c34e996d0d47044f146, type: 2}
@ -6362,6 +6363,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 80dee5e1862248aab26236036049e5fc, type: 3}
m_Name:
m_EditorClassIdentifier:
_holdCompleteTime: 0
--- !u!1001 &4530765275021007961
PrefabInstance:
m_ObjectHideFlags: 0

Binary file not shown.

BIN
Assets/_DDD/_Addressables/So/GameData/UiData.asset (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: dd182535820ec034b9d5a0315f93fa26
guid: d6d7638e05d740944a77a01f60b331c4
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000

Binary file not shown.

View File

@ -10,7 +10,7 @@ GameObject:
m_Component:
- component: {fileID: 4347279445921954555}
m_Layer: 5
m_Name: PopupUis
m_Name: PopupUiRoot
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
@ -38,6 +38,115 @@ RectTransform:
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!1 &1111901944139047714
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 6076800063500988294}
m_Layer: 5
m_Name: HudRoot
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &6076800063500988294
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1111901944139047714}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 1
m_Children:
- {fileID: 7916164406893547064}
m_Father: {fileID: 5760169274063006291}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!1 &4130064528218324885
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 6174235207617529232}
m_Layer: 5
m_Name: CommonUiRoot
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &6174235207617529232
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4130064528218324885}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 1
m_Children:
- {fileID: 4191602006120035298}
- {fileID: 4862733150375458244}
m_Father: {fileID: 5760169274063006291}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!1 &4851448928365776117
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3090446084811068791}
m_Layer: 5
m_Name: InteractionUiRoot
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &3090446084811068791
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4851448928365776117}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 1
m_Children:
- {fileID: 496411955364970508}
m_Father: {fileID: 5760169274063006291}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!1 &6838253471355869082
GameObject:
m_ObjectHideFlags: 0
@ -73,11 +182,10 @@ RectTransform:
m_LocalScale: {x: 0, y: 0, z: 0}
m_ConstrainProportionsScale: 1
m_Children:
- {fileID: 7916164406893547064}
- {fileID: 496411955364970508}
- {fileID: 6076800063500988294}
- {fileID: 3090446084811068791}
- {fileID: 4347279445921954555}
- {fileID: 4191602006120035298}
- {fileID: 4862733150375458244}
- {fileID: 6174235207617529232}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
@ -219,13 +327,10 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
_persistent: 1
_popupUiState:
m_AssetGUID: dd182535820ec034b9d5a0315f93fa26
m_SubObjectName:
m_SubObjectType:
m_SubObjectGUID:
m_EditorAssetChanged: 1
_hudRoot: {fileID: 6076800063500988294}
_interactionUiRoot: {fileID: 3090446084811068791}
_popupUiRoot: {fileID: 4347279445921954555}
_commonUiRoot: {fileID: 6174235207617529232}
--- !u!1001 &291049386471777514
PrefabInstance:
m_ObjectHideFlags: 0
@ -274,7 +379,7 @@ PrefabInstance:
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 5760169274063006291}
m_TransformParent: {fileID: 6076800063500988294}
m_Modifications:
- target: {fileID: 1353387648420519096, guid: 86a58b93c36851e4787861c23023b094, type: 3}
propertyPath: m_AnchorMax.y
@ -302,7 +407,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 3080325846008693413, guid: 86a58b93c36851e4787861c23023b094, type: 3}
propertyPath: m_Name
value: Hud
value: RestaurantHud
objectReference: {fileID: 0}
- target: {fileID: 4664972416674662382, guid: 86a58b93c36851e4787861c23023b094, type: 3}
propertyPath: m_Pivot.x
@ -354,15 +459,15 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 4664972416674662382, guid: 86a58b93c36851e4787861c23023b094, type: 3}
propertyPath: m_LocalRotation.x
value: 0
value: -0
objectReference: {fileID: 0}
- target: {fileID: 4664972416674662382, guid: 86a58b93c36851e4787861c23023b094, type: 3}
propertyPath: m_LocalRotation.y
value: 0
value: -0
objectReference: {fileID: 0}
- target: {fileID: 4664972416674662382, guid: 86a58b93c36851e4787861c23023b094, type: 3}
propertyPath: m_LocalRotation.z
value: 0
value: -0
objectReference: {fileID: 0}
- target: {fileID: 4664972416674662382, guid: 86a58b93c36851e4787861c23023b094, type: 3}
propertyPath: m_AnchoredPosition.x
@ -1811,7 +1916,7 @@ PrefabInstance:
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 5760169274063006291}
m_TransformParent: {fileID: 6174235207617529232}
m_Modifications:
- target: {fileID: 1926278333613504521, guid: f84d9014b084dbf46b1c4d44fe5b63c3, type: 3}
propertyPath: m_Name
@ -1867,15 +1972,15 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 8122602890985372994, guid: f84d9014b084dbf46b1c4d44fe5b63c3, type: 3}
propertyPath: m_LocalRotation.x
value: 0
value: -0
objectReference: {fileID: 0}
- target: {fileID: 8122602890985372994, guid: f84d9014b084dbf46b1c4d44fe5b63c3, type: 3}
propertyPath: m_LocalRotation.y
value: 0
value: -0
objectReference: {fileID: 0}
- target: {fileID: 8122602890985372994, guid: f84d9014b084dbf46b1c4d44fe5b63c3, type: 3}
propertyPath: m_LocalRotation.z
value: 0
value: -0
objectReference: {fileID: 0}
- target: {fileID: 8122602890985372994, guid: f84d9014b084dbf46b1c4d44fe5b63c3, type: 3}
propertyPath: m_AnchoredPosition.x
@ -2079,7 +2184,7 @@ PrefabInstance:
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 5760169274063006291}
m_TransformParent: {fileID: 6174235207617529232}
m_Modifications:
- target: {fileID: 2726783183655360567, guid: 085fe4e2983c9ed4780abfb56bf5c322, type: 3}
propertyPath: m_Pivot.x
@ -2131,15 +2236,15 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 2726783183655360567, guid: 085fe4e2983c9ed4780abfb56bf5c322, type: 3}
propertyPath: m_LocalRotation.x
value: 0
value: -0
objectReference: {fileID: 0}
- target: {fileID: 2726783183655360567, guid: 085fe4e2983c9ed4780abfb56bf5c322, type: 3}
propertyPath: m_LocalRotation.y
value: 0
value: -0
objectReference: {fileID: 0}
- target: {fileID: 2726783183655360567, guid: 085fe4e2983c9ed4780abfb56bf5c322, type: 3}
propertyPath: m_LocalRotation.z
value: 0
value: -0
objectReference: {fileID: 0}
- target: {fileID: 2726783183655360567, guid: 085fe4e2983c9ed4780abfb56bf5c322, type: 3}
propertyPath: m_AnchoredPosition.x
@ -2181,7 +2286,7 @@ PrefabInstance:
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 5760169274063006291}
m_TransformParent: {fileID: 3090446084811068791}
m_Modifications:
- target: {fileID: 507999590019405259, guid: 67b28d928cd16794eba49dade35d395d, type: 3}
propertyPath: m_Name
@ -2313,15 +2418,15 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 7858233056384553088, guid: 67b28d928cd16794eba49dade35d395d, type: 3}
propertyPath: m_LocalRotation.x
value: 0
value: -0
objectReference: {fileID: 0}
- target: {fileID: 7858233056384553088, guid: 67b28d928cd16794eba49dade35d395d, type: 3}
propertyPath: m_LocalRotation.y
value: 0
value: -0
objectReference: {fileID: 0}
- target: {fileID: 7858233056384553088, guid: 67b28d928cd16794eba49dade35d395d, type: 3}
propertyPath: m_LocalRotation.z
value: 0
value: -0
objectReference: {fileID: 0}
- target: {fileID: 7858233056384553088, guid: 67b28d928cd16794eba49dade35d395d, type: 3}
propertyPath: m_AnchoredPosition.x

Binary file not shown.

View File

@ -8,8 +8,10 @@ namespace DDD
public class GameData : ScriptSingleton<GameData>
{
[SerializeField] private AssetReference _gameLocalizationData;
[SerializeField] private AssetReference _uiData;
public GameLocalizationData LocalizationData { get; private set; }
public UiData UiData { get; private set; }
private bool _isLoaded;
@ -21,10 +23,16 @@ public async Task LoadData()
}
var gameLocalizationDataHandle = _gameLocalizationData.LoadAssetAsync<GameLocalizationData>();
var popupUiDataHandle = _uiData.LoadAssetAsync<UiData>();
await gameLocalizationDataHandle.Task;
await popupUiDataHandle.Task;
LocalizationData = gameLocalizationDataHandle.Result;
UiData = popupUiDataHandle.Result;
Debug.Assert(LocalizationData != null, "GameLocalizationData is null");
Debug.Assert(UiData != null, "UiData is null");
_isLoaded = true;
}

View File

@ -0,0 +1,12 @@
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;
namespace DDD
{
[CreateAssetMenu(fileName = "UiData", menuName = "GameData/UiData")]
public class UiData : SerializedScriptableObject
{
public Dictionary<GameFlowState, List<BaseUi>> FlowToUiMapping = new();
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a0663996283946b0a51684c1047c24a1
timeCreated: 1755751458

View File

@ -28,7 +28,7 @@ public void PreInit()
public Task Init()
{
return Task.CompletedTask; ;
return Task.CompletedTask;
}
public async void PostInit()

View File

@ -1,17 +1,14 @@
using UnityEngine;
using UnityEngine.AddressableAssets;
namespace DDD
{
public class GameState : ScriptSingleton<GameState>
{
[SerializeField] private AssetReference _gameLevelState;
public GameLevelState LevelState { get; private set; }
public UiState UiState { get; private set; }
private void OnEnable()
{
LevelState = CreateInstance<GameLevelState>();
UiState = CreateInstance<UiState>();
}
}
}

View File

@ -2,14 +2,24 @@
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using DDD.MVVM;
namespace DDD
{
public enum UiType
{
None = 0,
Hud,
Interaction,
Popup,
Common
}
public abstract class BaseUi : MonoBehaviour
{
[field: SerializeField] public UiType UiType { get; set; }
[SerializeField] protected bool _enableBlockImage;
protected CanvasGroup _canvasGroup;
@ -27,7 +37,6 @@ protected virtual void Awake()
_blockImage = transform.Find(CommonConstants.BlockImage)?.gameObject;
_bindingContext = new BindingContext();
SetupAutoBindings();
SetupBindings();
}
@ -38,7 +47,6 @@ protected virtual void OnEnable()
protected virtual void Start()
{
TryRegister();
ClosePanel();
}
@ -58,8 +66,20 @@ protected virtual void OnDestroy()
_bindingContext?.Dispose();
}
protected virtual void TryRegister() { }
protected virtual void TryUnregister() { }
public virtual void CreateInitialize()
{
TryRegister();
}
protected virtual void TryRegister()
{
UiManager.Instance.UiState.RegisterUI(this);
}
protected virtual void TryUnregister()
{
UiManager.Instance.UiState.UnregisterUI(this);
}
// BaseUi 메서드들을 직접 구현
public virtual void OpenPanel()
@ -93,74 +113,6 @@ public virtual void SetUiInteractable(bool active)
public bool IsOpenPanel() => _panel && _panel.activeInHierarchy;
/// <summary>
/// Attribute 기반 자동 바인딩 설정
/// </summary>
private void SetupAutoBindings()
{
var fields = GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public)
.Where(f => f.GetCustomAttribute<BindToAttribute>() != null);
foreach (var field in fields)
{
var bindAttribute = field.GetCustomAttribute<BindToAttribute>();
SetupBinding(field, bindAttribute);
}
// 컬렉션 바인딩 설정
var collectionFields = GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public)
.Where(f => f.GetCustomAttribute<BindCollectionAttribute>() != null);
foreach (var field in collectionFields)
{
var bindAttribute = field.GetCustomAttribute<BindCollectionAttribute>();
SetupCollectionBinding(field, bindAttribute);
}
}
/// <summary>
/// 개별 필드의 바인딩 설정
/// </summary>
private void SetupBinding(FieldInfo field, BindToAttribute bindAttribute)
{
var target = field.GetValue(this);
IValueConverter converter = null;
if (bindAttribute.ConverterType != null)
{
converter = Activator.CreateInstance(bindAttribute.ConverterType) as IValueConverter;
}
// UI 컴포넌트 타입별 바인딩 타겟 생성
IBindingTarget bindingTarget = target switch
{
Text text => new TextBindingTarget(text, bindAttribute.PropertyPath),
Image image => new ImageBindingTarget(image, bindAttribute.PropertyPath),
GameObject go => new ActiveBindingTarget(go, bindAttribute.PropertyPath),
Slider slider => new SliderBindingTarget(slider, bindAttribute.PropertyPath),
_ => null
};
if (bindingTarget != null)
{
_bindingContext.Bind(bindAttribute.PropertyPath, bindingTarget, converter);
}
}
/// <summary>
/// 컬렉션 바인딩 설정
/// </summary>
private void SetupCollectionBinding(FieldInfo field, BindCollectionAttribute bindAttribute)
{
var target = field.GetValue(this);
if (target is Transform parent)
{
// 컬렉션 바인딩 로직 (필요시 확장)
Debug.Log($"Collection binding for {bindAttribute.PropertyPath} is set up on {parent.name}");
}
}
/// <summary>
/// 추가 바인딩 설정 - 하위 클래스에서 구현
/// </summary>
@ -181,30 +133,5 @@ protected virtual void HandleCustomPropertyChanged(string propertyName)
{
// 하위 클래스에서 구현
}
// 수동 바인딩 헬퍼 메서드들
protected void BindText(Text text, string propertyPath, IValueConverter converter = null)
{
var target = new TextBindingTarget(text, propertyPath);
_bindingContext?.Bind(propertyPath, target, converter);
}
protected void BindImage(Image image, string propertyPath, IValueConverter converter = null)
{
var target = new ImageBindingTarget(image, propertyPath);
_bindingContext?.Bind(propertyPath, target, converter);
}
protected void BindActive(GameObject gameObject, string propertyPath, IValueConverter converter = null)
{
var target = new ActiveBindingTarget(gameObject, propertyPath);
_bindingContext?.Bind(propertyPath, target, converter);
}
protected void BindSlider(Slider slider, string propertyPath, IValueConverter converter = null)
{
var target = new SliderBindingTarget(slider, propertyPath);
_bindingContext?.Bind(propertyPath, target, converter);
}
}
}

View File

@ -1,6 +1,5 @@
using System.Threading.Tasks;
using DG.Tweening;
using UnityEngine;
namespace DDD
{
@ -15,6 +14,8 @@ protected override void Awake()
protected override void TryRegister()
{
base.TryRegister();
EventBus.Register<FadeInEvent>(this);
EventBus.Register<FadeOutEvent>(this);
}

View File

@ -1,7 +1,7 @@
using System.Linq;
using UnityEngine;
namespace DDD.MVVM
namespace DDD
{
/// <summary>
/// 불린 값을 반전시키는 컨버터

View File

@ -1,4 +1,4 @@
namespace DDD.MVVM
namespace DDD
{
/// <summary>
/// 값 변환기 인터페이스

View File

@ -1,5 +1,4 @@
using System.Linq;
using DDD.MVVM;
using UnityEngine;
namespace DDD
@ -11,41 +10,31 @@ namespace DDD
public class RestaurantManagementViewModel : SimpleViewModel, IEventHandler<TodayMenuRemovedEvent>
{
// 홀드 진행 상태 관리
[SerializeField] private float _holdCompleteTime = 1f;
private bool _isHolding;
private float _elapsedTime;
private float _holdCompleteTime = 1f;
private float _holdProgress;
public float HoldProgress
{
get => _holdProgress;
private set
{
if (SetField(ref _holdProgress, value))
{
OnPropertyChanged(nameof(NormalizedHoldProgress));
}
}
}
/// <summary>
/// 홀드 진행률을 0~1 범위로 변환한 값
/// </summary>
public float NormalizedHoldProgress => _holdCompleteTime <= 0f ? 0f : Mathf.Clamp01(_holdProgress / _holdCompleteTime);
// 탭 상태 관리
private SectionButtonType _currentSection = SectionButtonType.Menu;
private InventoryCategoryType _currentCategory = InventoryCategoryType.Food;
/// <summary>
/// 현재 홀드 상태
/// </summary>
public bool IsHolding
{
get => _isHolding;
private set => SetField(ref _isHolding, value);
}
/// <summary>
/// 홀드 진행 시간 (0.0 ~ 1.0)
/// </summary>
public float HoldProgress
{
get => _elapsedTime;
private set => SetField(ref _elapsedTime, value);
}
/// <summary>
/// 홀드 완료에 필요한 시간
/// </summary>
public float HoldCompleteTime
{
get => _holdCompleteTime;
set => SetField(ref _holdCompleteTime, value);
}
/// <summary>
/// 현재 선택된 섹션
/// </summary>
@ -67,13 +56,7 @@ public InventoryCategoryType CurrentCategory
/// <summary>
/// 배치 완료 가능 여부 (체크리스트 완료 상태)
/// </summary>
public bool CanCompleteBatch =>
RestaurantState.Instance.ManagementState.GetChecklistStates().All(state => state);
/// <summary>
/// 홀드 진행률을 0~1 범위로 변환한 값
/// </summary>
public float NormalizedHoldProgress => HoldCompleteTime <= 0f ? 1f : Mathf.Clamp01(HoldProgress / HoldCompleteTime);
public bool CanCompleteBatch => RestaurantState.Instance.ManagementState.GetChecklistStates().All(state => state);
public override void Initialize()
{
@ -103,9 +86,9 @@ private void UnregisterEvents()
/// </summary>
public void UpdateHoldProgress()
{
if (!IsHolding) return;
if (_isHolding == false) return;
if (HoldCompleteTime <= 0f)
if (_holdCompleteTime <= 0f)
{
ProcessCompleteBatch();
return;
@ -114,13 +97,10 @@ public void UpdateHoldProgress()
var deltaTime = Time.deltaTime;
HoldProgress += deltaTime;
if (HoldProgress >= HoldCompleteTime)
if (HoldProgress >= _holdCompleteTime)
{
ProcessCompleteBatch();
}
// UI 업데이트를 위한 정규화된 진행률 알림
OnPropertyChanged(nameof(NormalizedHoldProgress));
}
/// <summary>
@ -128,9 +108,8 @@ public void UpdateHoldProgress()
/// </summary>
public void StartHold()
{
IsHolding = true;
_isHolding = true;
HoldProgress = 0f;
OnPropertyChanged(nameof(NormalizedHoldProgress));
}
/// <summary>
@ -143,9 +122,8 @@ public void CancelHold()
private void ResetHoldState()
{
IsHolding = false;
_isHolding = false;
HoldProgress = 0f;
OnPropertyChanged(nameof(NormalizedHoldProgress));
}
/// <summary>

View File

@ -1,4 +1,4 @@
namespace DDD.MVVM
namespace DDD
{
/// <summary>
/// 서비스 계층의 기본 인터페이스

View File

@ -2,7 +2,7 @@
using System.Linq;
using UnityEngine;
namespace DDD.MVVM
namespace DDD
{
/// <summary>
/// 인벤토리 관련 비즈니스 로직을 담당하는 서비스

View File

@ -1,114 +0,0 @@
using System;
namespace DDD.MVVM
{
/// <summary>
/// UI 요소를 ViewModel 속성에 바인딩하기 위한 Attribute
/// Inspector에서 바인딩 정보를 시각적으로 확인할 수 있도록 지원
/// </summary>
[System.AttributeUsage(System.AttributeTargets.Field)]
public class BindToAttribute : System.Attribute
{
/// <summary>
/// 바인딩할 ViewModel 속성의 경로 (nameof 사용 권장)
/// </summary>
public string PropertyPath { get; }
/// <summary>
/// 값 변환기 타입 (선택사항)
/// </summary>
public System.Type ConverterType { get; }
/// <summary>
/// 바인딩 Attribute 생성자
/// </summary>
/// <param name="propertyPath">바인딩할 속성 경로 (nameof 사용 권장)</param>
/// <param name="converterType">값 변환기 타입 (선택사항)</param>
public BindToAttribute(string propertyPath, System.Type converterType = null)
{
PropertyPath = propertyPath;
ConverterType = converterType;
}
}
// /// <summary>
// /// 타입 안전한 바인딩 Attribute (제네릭 버전)
// /// 특정 ViewModel 타입에 대한 바인딩을 명시적으로 지정
// /// </summary>
// /// <typeparam name="TViewModel">바인딩할 ViewModel 타입</typeparam>
// [System.AttributeUsage(System.AttributeTargets.Field)]
// public class BindToAttribute<TViewModel> : System.Attribute where TViewModel : class
// {
// /// <summary>
// /// 바인딩할 ViewModel 속성의 경로
// /// </summary>
// public string PropertyPath { get; }
//
// /// <summary>
// /// 값 변환기 타입 (선택사항)
// /// </summary>
// public System.Type ConverterType { get; }
//
// /// <summary>
// /// 타입 안전한 바인딩 Attribute 생성자
// /// </summary>
// /// <param name="propertyPath">바인딩할 속성 경로</param>
// /// <param name="converterType">값 변환기 타입 (선택사항)</param>
// public BindToAttribute(string propertyPath, System.Type converterType = null)
// {
// PropertyPath = propertyPath;
// ConverterType = converterType;
// }
// }
/// <summary>
/// 컬렉션 바인딩을 위한 Attribute
/// 동적으로 생성되는 UI 요소들을 컬렉션에 바인딩
/// </summary>
[System.AttributeUsage(System.AttributeTargets.Field)]
public class BindCollectionAttribute : System.Attribute
{
/// <summary>
/// 바인딩할 컬렉션 속성의 경로
/// </summary>
public string PropertyPath { get; }
/// <summary>
/// 아이템 프리팹의 필드명 또는 속성명
/// </summary>
public string ItemPrefabReference { get; }
/// <summary>
/// 컬렉션 바인딩 Attribute 생성자
/// </summary>
/// <param name="propertyPath">바인딩할 컬렉션 속성 경로</param>
/// <param name="itemPrefabReference">아이템 프리팹 참조</param>
public BindCollectionAttribute(string propertyPath, string itemPrefabReference = null)
{
PropertyPath = propertyPath;
ItemPrefabReference = itemPrefabReference;
}
}
/// <summary>
/// 커맨드 바인딩을 위한 Attribute
/// 버튼 클릭 등의 이벤트를 ViewModel 메서드에 바인딩
/// </summary>
[System.AttributeUsage(System.AttributeTargets.Field)]
public class BindCommandAttribute : System.Attribute
{
/// <summary>
/// 바인딩할 ViewModel 메서드 이름
/// </summary>
public string MethodName { get; }
/// <summary>
/// 커맨드 바인딩 Attribute 생성자
/// </summary>
/// <param name="methodName">바인딩할 메서드 이름</param>
public BindCommandAttribute(string methodName)
{
MethodName = methodName;
}
}
}

View File

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 279b3238907a3564f842594af646eab7

View File

@ -2,113 +2,12 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace DDD.MVVM
namespace DDD
{
/// <summary>
/// 바인딩 타겟 인터페이스
/// UI 요소와 ViewModel 속성을 연결하는 역할
/// </summary>
public interface IBindingTarget
{
/// <summary>
/// 바인딩된 속성의 경로
/// </summary>
string PropertyPath { get; }
/// <summary>
/// UI 요소의 값을 업데이트
/// </summary>
/// <param name="value">새로운 값</param>
void UpdateValue(object value);
}
/// <summary>
/// Text 컴포넌트에 대한 바인딩 타겟
/// </summary>
public class TextBindingTarget : IBindingTarget
{
private readonly Text _text;
public string PropertyPath { get; }
public TextBindingTarget(Text text, string propertyPath)
{
_text = text;
PropertyPath = propertyPath;
}
public void UpdateValue(object value)
{
if (_text != null)
_text.text = value?.ToString() ?? string.Empty;
}
}
/// <summary>
/// Image 컴포넌트에 대한 바인딩 타겟
/// </summary>
public class ImageBindingTarget : IBindingTarget
{
private readonly Image _image;
public string PropertyPath { get; }
public ImageBindingTarget(Image image, string propertyPath)
{
_image = image;
PropertyPath = propertyPath;
}
public void UpdateValue(object value)
{
if (_image != null && value is Sprite sprite)
_image.sprite = sprite;
}
}
/// <summary>
/// GameObject의 활성화 상태에 대한 바인딩 타겟
/// </summary>
public class ActiveBindingTarget : IBindingTarget
{
private readonly GameObject _gameObject;
public string PropertyPath { get; }
public ActiveBindingTarget(GameObject gameObject, string propertyPath)
{
_gameObject = gameObject;
PropertyPath = propertyPath;
}
public void UpdateValue(object value)
{
if (_gameObject != null)
_gameObject.SetActive(value is bool active && active);
}
}
/// <summary>
/// Slider 컴포넌트에 대한 바인딩 타겟
/// </summary>
public class SliderBindingTarget : IBindingTarget
{
private readonly Slider _slider;
public string PropertyPath { get; }
public SliderBindingTarget(Slider slider, string propertyPath)
{
_slider = slider;
PropertyPath = propertyPath;
}
public void UpdateValue(object value)
{
if (_slider != null && value is float floatValue)
_slider.value = floatValue;
}
}
/// <summary>
/// 바인딩 컨텍스트 - ViewModel과 View 간의 데이터 바인딩을 관리
/// </summary>

View File

@ -0,0 +1,39 @@
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace DDD
{
public static class BindingHelper
{
public static void BindText(BindingContext context, TextMeshProUGUI text, string propertyPath, IValueConverter converter = null)
{
var target = new TextBindingTarget(text, propertyPath);
context?.Bind(propertyPath, target, converter);
}
public static void BindImage(BindingContext context, Image image, string propertyPath, IValueConverter converter = null)
{
var target = new ImageBindingTarget(image, propertyPath);
context?.Bind(propertyPath, target, converter);
}
public static void BindImageFilled(BindingContext context, Image image, string propertyPath, IValueConverter converter = null)
{
var target = new ImageFilledBindingTarget(image, propertyPath);
context?.Bind(propertyPath, target, converter);
}
public static void BindActive(BindingContext context, GameObject gameObject, string propertyPath, IValueConverter converter = null)
{
var target = new ActiveBindingTarget(gameObject, propertyPath);
context?.Bind(propertyPath, target, converter);
}
public static void BindSlider(BindingContext context, Slider slider, string propertyPath, IValueConverter converter = null)
{
var target = new SliderBindingTarget(slider, propertyPath);
context?.Bind(propertyPath, target, converter);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a76b583779de40c0b4b3a922c4efb82d
timeCreated: 1755747839

View File

@ -0,0 +1,118 @@
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace DDD
{
public interface IBindingTarget
{
/// <summary>
/// 바인딩된 속성의 경로
/// </summary>
string PropertyPath { get; }
/// <summary>
/// UI 요소의 값을 업데이트
/// </summary>
/// <param name="value">새로운 값</param>
void UpdateValue(object value);
}
public class TextBindingTarget : IBindingTarget
{
private readonly TextMeshProUGUI _text;
public string PropertyPath { get; }
public TextBindingTarget(TextMeshProUGUI text, string propertyPath)
{
_text = text;
PropertyPath = propertyPath;
}
public void UpdateValue(object value)
{
if (_text != null)
{
_text.text = value?.ToString() ?? string.Empty;
}
}
}
public class ImageBindingTarget : IBindingTarget
{
private readonly Image _image;
public string PropertyPath { get; }
public ImageBindingTarget(Image image, string propertyPath)
{
_image = image;
PropertyPath = propertyPath;
}
public void UpdateValue(object value)
{
if (_image != null && value is Sprite sprite)
{
_image.sprite = sprite;
}
}
}
public class ImageFilledBindingTarget : IBindingTarget
{
private readonly Image _image;
public string PropertyPath { get; }
public ImageFilledBindingTarget(Image image, string propertyPath)
{
_image = image;
PropertyPath = propertyPath;
}
public void UpdateValue(object value)
{
if (_image != null && value is float floatValue)
{
_image.fillAmount = Mathf.Clamp01(floatValue); // 0-1 범위로 제한
}
}
}
public class ActiveBindingTarget : IBindingTarget
{
private readonly GameObject _gameObject;
public string PropertyPath { get; }
public ActiveBindingTarget(GameObject go, string propertyPath)
{
_gameObject = go;
PropertyPath = propertyPath;
}
public void UpdateValue(object value)
{
if (_gameObject != null)
{
_gameObject.SetActive(value is true);
}
}
}
public class SliderBindingTarget : IBindingTarget
{
private readonly Slider _slider;
public string PropertyPath { get; }
public SliderBindingTarget(Slider slider, string propertyPath)
{
_slider = slider;
PropertyPath = propertyPath;
}
public void UpdateValue(object value)
{
if (_slider != null && value is float floatValue)
_slider.value = floatValue;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cd44087c256644b9815bfdc23ac4b29f
timeCreated: 1755749119

View File

@ -1,24 +0,0 @@
namespace DDD.MVVM
{
/// <summary>
/// 입력 처리 단계를 나타내는 열거형
/// 매직 스트링을 제거하고 타입 안전성을 제공
/// </summary>
public enum InputPhaseType
{
/// <summary>
/// 입력이 시작됨
/// </summary>
Started,
/// <summary>
/// 입력이 수행됨
/// </summary>
Performed,
/// <summary>
/// 입력이 취소됨
/// </summary>
Canceled
}
}

View File

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 5c9b66b101f99e1458e01b9e0653935f

View File

@ -10,8 +10,11 @@ public abstract class SimpleViewModel : MonoBehaviour, INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void Awake() { }
protected virtual void OnEnable() { }
protected virtual void Start() { }
protected virtual void OnDisable() { }
protected virtual void OnDestroy() { }
public virtual void Initialize() { }
public virtual void Cleanup() { }
@ -35,6 +38,7 @@ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
@ -64,24 +68,11 @@ protected void EndUpdate()
if (_pendingNotifications.Count > 0)
{
foreach (var prop in _pendingNotifications)
{
OnPropertyChanged(prop);
}
_pendingNotifications.Clear();
}
}
/// <summary>
/// PropertyChanged 이벤트 발생 (배치 업데이트 고려)
/// </summary>
protected virtual void OnPropertyChangedInternal([CallerMemberName] string propertyName = null)
{
if (_isUpdating)
{
_pendingNotifications.Add(propertyName);
}
else
{
OnPropertyChanged(propertyName);
}
}
}
}

View File

@ -1,9 +1,8 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using UnityEngine;
namespace DDD.MVVM
namespace DDD
{
/// <summary>
/// 인벤토리 UI의 ViewModel
@ -12,8 +11,7 @@ namespace DDD.MVVM
public class InventoryViewModel : SimpleViewModel, IEventHandler<InventoryChangedEvent>,
IEventHandler<TodayMenuAddedEvent>, IEventHandler<TodayMenuRemovedEvent>
{
[Header("Services")]
[SerializeField] private InventoryService _inventoryService;
private InventoryService _inventoryService;
// Private fields for properties
private InventoryCategoryType _currentCategory = InventoryCategoryType.Food;
@ -33,8 +31,6 @@ public InventoryCategoryType CurrentCategory
if (SetField(ref _currentCategory, value))
{
UpdateVisibleItems();
// 연관된 계산된 속성들도 알림
OnPropertyChanged(nameof(CategoryDisplayText));
}
}
}
@ -81,21 +77,6 @@ public ItemViewModel SelectedItem
set => SetField(ref _selectedItem, value);
}
// Computed Properties (계산된 속성들)
/// <summary>
/// 카테고리 표시 텍스트 (한국어)
/// </summary>
public string CategoryDisplayText => CurrentCategory switch
{
InventoryCategoryType.Food => "음식",
InventoryCategoryType.Drink => "음료",
InventoryCategoryType.Ingredient => "재료",
InventoryCategoryType.Cookware => "조리도구",
InventoryCategoryType.Special => "특수",
_ => "전체"
};
/// <summary>
/// 보이는 아이템들 중 실제 보유한 아이템이 있는지 확인
/// </summary>

View File

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 36adabeb3767cf64684116798ff0ef30
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 76b62bf64be94ab4bb1e4b610da29fa4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,210 +0,0 @@
using System;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.UI;
namespace DDD.MVVM
{
/// <summary>
/// 자동 바인딩을 지원하는 View 기본 클래스
/// Attribute를 통해 설정된 바인딩을 자동으로 처리
/// </summary>
/// <typeparam name="TViewModel">바인딩할 ViewModel 타입</typeparam>
public abstract class AutoBindView<TViewModel> : MonoBehaviour where TViewModel : SimpleViewModel
{
[SerializeField] protected TViewModel _viewModel;
protected BindingContext _bindingContext;
/// <summary>
/// ViewModel 인스턴스
/// </summary>
public TViewModel ViewModel => _viewModel;
protected virtual void Awake()
{
if (_viewModel == null)
_viewModel = GetComponent<TViewModel>();
_bindingContext = new BindingContext();
SetupAutoBindings();
}
protected virtual void OnEnable()
{
if (_viewModel != null && _bindingContext != null)
{
_bindingContext.SetDataContext(_viewModel);
_viewModel.PropertyChanged += OnViewModelPropertyChanged;
}
}
protected virtual void OnDisable()
{
if (_viewModel != null)
{
_viewModel.PropertyChanged -= OnViewModelPropertyChanged;
}
}
protected virtual void OnDestroy()
{
_bindingContext?.Dispose();
}
/// <summary>
/// Attribute 기반 자동 바인딩 설정
/// </summary>
private void SetupAutoBindings()
{
var fields = GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public)
.Where(f => f.GetCustomAttribute<BindToAttribute>() != null);
foreach (var field in fields)
{
var bindAttribute = field.GetCustomAttribute<BindToAttribute>();
SetupBinding(field, bindAttribute);
}
// 컬렉션 바인딩 설정
var collectionFields = GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public)
.Where(f => f.GetCustomAttribute<BindCollectionAttribute>() != null);
foreach (var field in collectionFields)
{
var bindAttribute = field.GetCustomAttribute<BindCollectionAttribute>();
SetupCollectionBinding(field, bindAttribute);
}
}
/// <summary>
/// 개별 필드의 바인딩 설정
/// </summary>
/// <param name="field">바인딩할 필드</param>
/// <param name="bindAttribute">바인딩 Attribute</param>
private void SetupBinding(FieldInfo field, BindToAttribute bindAttribute)
{
var target = field.GetValue(this);
IValueConverter converter = null;
if (bindAttribute.ConverterType != null)
{
converter = Activator.CreateInstance(bindAttribute.ConverterType) as IValueConverter;
}
// UI 컴포넌트 타입별 바인딩 타겟 생성
IBindingTarget bindingTarget = target switch
{
Text text => new TextBindingTarget(text, bindAttribute.PropertyPath),
Image image => new ImageBindingTarget(image, bindAttribute.PropertyPath),
GameObject gameObject => new ActiveBindingTarget(gameObject, bindAttribute.PropertyPath),
Slider slider => new SliderBindingTarget(slider, bindAttribute.PropertyPath),
_ => null
};
if (bindingTarget != null)
{
_bindingContext.Bind(bindAttribute.PropertyPath, bindingTarget, converter);
}
}
/// <summary>
/// 컬렉션 바인딩 설정
/// </summary>
/// <param name="field">바인딩할 필드</param>
/// <param name="bindAttribute">바인딩 Attribute</param>
private void SetupCollectionBinding(FieldInfo field, BindCollectionAttribute bindAttribute)
{
var target = field.GetValue(this);
if (target is Transform parent)
{
// 컬렉션 바인딩은 별도 구현이 필요한 복잡한 기능으로
// 현재는 기본 구조만 제공
Debug.Log($"Collection binding for {bindAttribute.PropertyPath} is set up on {parent.name}");
}
}
/// <summary>
/// ViewModel 속성 변경 이벤트 핸들러
/// 추가적인 커스텀 로직이 필요한 경우 오버라이드
/// </summary>
/// <param name="sender">이벤트 발신자</param>
/// <param name="e">속성 변경 정보</param>
protected virtual void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
// 자동 바인딩으로 처리되지 않는 특별한 속성들의 커스텀 처리
HandleCustomPropertyChanged(e.PropertyName);
}
/// <summary>
/// 커스텀 속성 변경 처리 (하위 클래스에서 오버라이드)
/// </summary>
/// <param name="propertyName">변경된 속성 이름</param>
protected virtual void HandleCustomPropertyChanged(string propertyName)
{
// 하위 클래스에서 구현
}
/// <summary>
/// 수동 바인딩 헬퍼 메서드들
/// Attribute 사용이 어려운 경우 코드로 바인딩 설정
/// </summary>
protected void BindText(Text text, string propertyPath, IValueConverter converter = null)
{
var target = new TextBindingTarget(text, propertyPath);
_bindingContext?.Bind(propertyPath, target, converter);
}
protected void BindImage(Image image, string propertyPath, IValueConverter converter = null)
{
var target = new ImageBindingTarget(image, propertyPath);
_bindingContext?.Bind(propertyPath, target, converter);
}
protected void BindActive(GameObject gameObject, string propertyPath, IValueConverter converter = null)
{
var target = new ActiveBindingTarget(gameObject, propertyPath);
_bindingContext?.Bind(propertyPath, target, converter);
}
protected void BindSlider(Slider slider, string propertyPath, IValueConverter converter = null)
{
var target = new SliderBindingTarget(slider, propertyPath);
_bindingContext?.Bind(propertyPath, target, converter);
}
/// <summary>
/// ViewModel 메서드 호출 헬퍼
/// UI 이벤트에서 ViewModel 메서드를 쉽게 호출
/// </summary>
/// <param name="methodName">호출할 메서드 이름</param>
/// <param name="parameters">메서드 매개변수</param>
protected void InvokeViewModelMethod(string methodName, params object[] parameters)
{
if (_viewModel == null) return;
var method = _viewModel.GetType().GetMethod(methodName);
method?.Invoke(_viewModel, parameters);
}
/// <summary>
/// ViewModel 속성 직접 설정 헬퍼
/// </summary>
/// <param name="propertyName">속성 이름</param>
/// <param name="value">설정할 값</param>
protected void SetViewModelProperty(string propertyName, object value)
{
if (_viewModel == null) return;
var property = _viewModel.GetType().GetProperty(propertyName);
if (property != null && property.CanWrite)
{
property.SetValue(_viewModel, value);
}
}
}
}

View File

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 738101122cf3fb74e99b244165797ab8

View File

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: abb1dd67b48daeb4f968a2641cf7b4a3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -5,7 +5,7 @@ namespace DDD
{
public abstract class BasePopupUi : BaseUi
{
public bool IsTopPopup => UiManager.Instance.PopupUiState.IsTopPopup(this);
public bool IsTopPopup => UiManager.Instance.UiState.IsTopPopup(this);
public InputActionMaps InputActionMaps;
protected override void Awake()
@ -29,10 +29,10 @@ protected override void Update()
if (IsOpenPanel() == false) return;
var currentSelectedGameObject = EventSystem.current.currentSelectedGameObject;
if (currentSelectedGameObject == null || currentSelectedGameObject.activeInHierarchy == false)
if (!currentSelectedGameObject || currentSelectedGameObject.activeInHierarchy == false)
{
var initialSelected = GetInitialSelected();
if (initialSelected != null)
if (initialSelected)
{
EventSystem.current.SetSelectedGameObject(initialSelected);
}
@ -43,14 +43,14 @@ protected override void TryRegister()
{
base.TryRegister();
UiManager.Instance.PopupUiState?.RegisterPopupUI(this);
UiManager.Instance.UiState.RegisterPopupUI(this);
}
protected override void TryUnregister()
{
base.TryUnregister();
UiManager.Instance?.PopupUiState?.UnregisterPopupUI(this);
UiManager.Instance?.UiState?.UnregisterPopupUI(this);
}
public virtual void Open(OpenPopupUiEvent evt)

View File

@ -6,15 +6,25 @@
namespace DDD
{
[CreateAssetMenu(fileName = "PopupUiState", menuName = "GameUi/PopupUiState")]
public class PopupUiState : SerializedScriptableObject, IEventHandler<OpenPopupUiEvent>, IEventHandler<ClosePopupUiEvent>
public class UiState : SerializedScriptableObject, IEventHandler<OpenPopupUiEvent>, IEventHandler<ClosePopupUiEvent>
{
public Dictionary<GameFlowState, List<BasePopupUi>> FlowToPopupUiMapping = new();
private readonly Dictionary<Type, BaseUi> _uis = new();
[Title("디버그")]
[ReadOnly, ShowInInspector] private readonly Dictionary<Type, BasePopupUi> _popupUis = new();
[ReadOnly, ShowInInspector] private readonly Stack<BasePopupUi> _popupUiStack = new();
[ReadOnly, SerializeField] private InputActionMaps _previousActionMap = InputActionMaps.None;
private readonly Dictionary<Type, BasePopupUi> _popupUis = new();
private readonly Stack<BasePopupUi> _popupUiStack = new();
private InputActionMaps _previousActionMap = InputActionMaps.None;
private UiData _uiData => GameData.Instance.UiData;
public void OnEnable()
{
EventBus.Register<OpenPopupUiEvent>(this);
EventBus.Register<ClosePopupUiEvent>(this);
_uis.Clear();
_popupUis.Clear();
_popupUiStack.Clear();
}
private void OnDisable()
{
@ -22,13 +32,19 @@ private void OnDisable()
EventBus.Unregister<ClosePopupUiEvent>(this);
}
public void Initialize()
public void RegisterUI(BaseUi ui)
{
EventBus.Register<OpenPopupUiEvent>(this);
EventBus.Register<ClosePopupUiEvent>(this);
var type = ui.GetType();
_uis.TryAdd(type, ui);
}
_popupUis.Clear();
_popupUiStack.Clear();
public void UnregisterUI(BaseUi ui)
{
var type = ui.GetType();
if (_uis.TryGetValue(type, out var registered) && registered == ui)
{
_uis.Remove(type);
}
}
public void RegisterPopupUI(BasePopupUi ui)
@ -86,50 +102,25 @@ public void Invoke(ClosePopupUiEvent evt)
}
}
public void CreatePopup(BasePopupUi popup, Transform parent)
{
if (_popupUis.TryGetValue(popup.GetType(), out var registered) && registered == popup) return;
var instance = Instantiate(popup, parent);
instance.name = popup.name;
}
public void DestroyPopup(BasePopupUi popup)
{
if (_popupUis.TryGetValue(popup.GetType(), out var registered) == false || registered != popup) return;
Destroy(popup.gameObject);
}
public List<BasePopupUi> GetMatchingPopupUis(GameFlowState flowState)
{
return FlowToPopupUiMapping
.Where(keyValuePair => (keyValuePair.Key & flowState) != 0)
.SelectMany(keyValuePair => keyValuePair.Value)
.ToList();
}
public bool HasMatchingPopupUis(GameFlowState flowState)
{
return FlowToPopupUiMapping.Any(keyValuePair => (keyValuePair.Key & flowState) != 0);
return _uiData.FlowToUiMapping.Any(keyValuePair => (keyValuePair.Key & flowState) != 0);
}
public void CreateMatchingPopupUis(GameFlowState flowState, Transform parent)
public void CreateUi(BaseUi ui, Transform parent)
{
var matchingPopupUis = GetMatchingPopupUis(flowState);
foreach (var popupUi in matchingPopupUis)
{
CreatePopup(popupUi, parent);
}
if (_uis.TryGetValue(ui.GetType(), out var registered) && registered == ui) return;
var instance = Instantiate(ui, parent);
instance.name = ui.name;
instance.CreateInitialize();
}
public void DestroyMatchingPopupUis(GameFlowState flowState)
public void DestroyUi(BaseUi ui)
{
var matchingPopupUis = GetMatchingPopupUis(flowState);
foreach (var popupUi in matchingPopupUis)
{
DestroyPopup(popupUi);
}
if (_uis.TryGetValue(ui.GetType(), out var registered) && registered == ui) return;
Destroy(ui.gameObject);
}
public bool IsTopPopup(BasePopupUi popup)

View File

@ -2,14 +2,9 @@
using UnityEngine.UI;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
using DDD.MVVM;
namespace DDD
{
/// <summary>
/// MVVM 패턴을 적용한 새로운 레스토랑 관리 UI
/// 기존 RestaurantManagementUi의 기능을 ViewModel과 분리하여 구현
/// </summary>
[RequireComponent(typeof(RestaurantManagementViewModel))]
public class RestaurantManagementUi : PopupUi<RestaurantUiActions, RestaurantManagementViewModel>
{
@ -26,26 +21,25 @@ public class RestaurantManagementUi : PopupUi<RestaurantUiActions, RestaurantMan
[SerializeField] private TabGroupUi _cookwareCategoryTabs;
[Header("Hold Progress UI")]
[SerializeField, BindTo(nameof(RestaurantManagementViewModel.NormalizedHoldProgress))]
private Image _completeBatchFilledImage;
protected override void Awake()
{
base.Awake();
SetupViewModelEvents();
}
[SerializeField] private Image _completeBatchFilledImage;
protected override void Update()
{
base.Update();
if (_viewModel != null && _viewModel.IsHolding)
if (_viewModel)
{
_viewModel.UpdateHoldProgress();
}
}
protected override void TryRegister()
{
base.TryRegister();
SetupViewModelEvents();
}
public override void Open(OpenPopupUiEvent evt)
{
base.Open(evt);
@ -73,8 +67,7 @@ protected override GameObject GetInitialSelected()
protected override void SetupBindings()
{
// Attribute 기반 자동 바인딩이 처리됨
// 추가적인 수동 바인딩이 필요한 경우 여기에 구현
BindingHelper.BindImageFilled(_bindingContext, _completeBatchFilledImage, nameof(RestaurantManagementViewModel.NormalizedHoldProgress));
}
protected override void HandleCustomPropertyChanged(string propertyName)
@ -260,7 +253,7 @@ protected override bool OnInputCanceled(RestaurantUiActions actionEnum, InputAct
{
var isHandled = base.OnInputCanceled(actionEnum, context);
if (isHandled && _viewModel != null)
if (isHandled && _viewModel)
{
switch (actionEnum)
{

View File

@ -0,0 +1,7 @@
namespace DDD
{
public class RestaurantHud : BaseUi
{
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7521841876f5b054aa4a0b0081ff8425

View File

@ -1,390 +1,402 @@
using System;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
namespace DDD
{
public enum ButtonState
{
Normal,
Highlighted,
Pressed,
Selected,
Disabled
}
public class UiButton : MonoBehaviour, IInteractableUi, IPointerEnterHandler, IPointerExitHandler,
IPointerDownHandler, IPointerUpHandler, ISelectHandler, IDeselectHandler, ISubmitHandler
{
[Header("Button Components")]
[SerializeField] private Button _button;
[SerializeField] private Selectable _selectable;
[Header("State Synchronization")]
[SerializeField] private bool _synchronizeStates = true;
[SerializeField] private bool _handleKeyboardInput = true;
[SerializeField] private bool _handleGamepadInput = true;
[Header("Visual Feedback")]
[SerializeField] private Animator _animator;
[SerializeField] private Image _targetGraphic;
[Header("Toggle Functionality")]
[SerializeField] private bool _useToggle = false;
// State tracking
private bool _isPressed;
private bool _isHighlighted;
private bool _isSelected;
private bool _wasSelectedByKeyboard;
private bool _isToggled = false;
// Events
public event Action OnClicked;
public event Action OnStateChanged;
// Animation parameter hashes (if using Animator)
private readonly int _normalHash = Animator.StringToHash("Normal");
private readonly int _highlightedHash = Animator.StringToHash("Highlighted");
private readonly int _pressedHash = Animator.StringToHash("Pressed");
private readonly int _selectedHash = Animator.StringToHash("Selected");
private readonly int _disabledHash = Animator.StringToHash("Disabled");
private void Awake()
{
InitializeComponents();
}
private void OnEnable()
{
if (_button != null)
{
_button.onClick.AddListener(HandleButtonClick);
}
UpdateVisualState();
}
private void OnDisable()
{
if (_button != null)
{
_button.onClick.RemoveListener(HandleButtonClick);
}
}
private void Update()
{
HandleInputUpdate();
}
private void InitializeComponents()
{
// Get Button component if not assigned
if (_button == null)
{
_button = GetComponent<Button>();
}
// Get Selectable component (Button inherits from Selectable)
if (_selectable == null)
{
_selectable = _button;
}
// Get target graphic from button if not assigned
if (_targetGraphic == null && _button != null)
{
_targetGraphic = _button.targetGraphic as Image;
}
// Get Animator if not assigned
if (_animator == null)
{
_animator = GetComponent<Animator>();
}
}
private void HandleInputUpdate()
{
if (!_handleKeyboardInput && !_handleGamepadInput) return;
if (_selectable == null || !_selectable.interactable) return;
// Handle keyboard/gamepad input when this button is selected
if (EventSystem.current != null && EventSystem.current.currentSelectedGameObject == gameObject)
{
//HandleSelectedInput();
}
}
private void HandleSelectedInput()
{
var keyboard = Keyboard.current;
var gamepad = Gamepad.current;
// Handle keyboard input
if (_handleKeyboardInput && keyboard != null)
{
if (keyboard.enterKey.wasPressedThisFrame || keyboard.spaceKey.wasPressedThisFrame)
{
HandleButtonClick();
}
}
// Handle gamepad input
if (_handleGamepadInput && gamepad != null)
{
if (gamepad.buttonSouth.wasPressedThisFrame) // A button on Xbox controller
{
HandleButtonClick();
}
}
}
private void HandleButtonClick()
{
if (_selectable != null && !_selectable.interactable) return;
if (_useToggle)
{
_isToggled = !_isToggled;
if (_isToggled)
{
// When toggled on, maintain pressed state and make non-interactable
_isPressed = true;
_selectable.interactable = false;
}
else
{
// When toggled off, restore normal behavior
_isPressed = false;
_selectable.interactable = true;
}
UpdateVisualState();
}
OnClicked?.Invoke();
}
private void UpdateVisualState()
{
if (!_synchronizeStates) return;
var currentState = GetCurrentState();
print(currentState);
ApplyVisualState(currentState);
OnStateChanged?.Invoke();
}
private ButtonState GetCurrentState()
{
if (_selectable == null || (!_selectable.interactable && !_isToggled))
return ButtonState.Disabled;
// Toggle mode: when toggled on, maintain pressed state even if not interactable
if (_useToggle && _isToggled)
return ButtonState.Pressed;
if (_isPressed)
return ButtonState.Pressed;
if (_isSelected)
return ButtonState.Selected;
if (_isHighlighted)
return ButtonState.Highlighted;
return ButtonState.Normal;
}
private void ApplyVisualState(ButtonState state)
{
// Apply animator state if available
if (_animator != null && _animator.runtimeAnimatorController != null)
{
ApplyAnimatorState(state);
}
// Apply color tint if using Button's color block
if (_button != null && _targetGraphic != null)
{
ApplyColorState(state);
}
}
private void ApplyAnimatorState(ButtonState state)
{
switch (state)
{
case ButtonState.Normal:
_animator.SetTrigger(_normalHash);
break;
case ButtonState.Highlighted:
_animator.SetTrigger(_highlightedHash);
break;
case ButtonState.Pressed:
_animator.SetTrigger(_pressedHash);
break;
case ButtonState.Selected:
_animator.SetTrigger(_selectedHash);
break;
case ButtonState.Disabled:
_animator.SetTrigger(_disabledHash);
break;
}
}
private void ApplyColorState(ButtonState state)
{
var colors = _button.colors;
Color targetColor;
switch (state)
{
case ButtonState.Normal:
targetColor = colors.normalColor;
break;
case ButtonState.Highlighted:
targetColor = colors.highlightedColor;
break;
case ButtonState.Pressed:
targetColor = colors.pressedColor;
break;
case ButtonState.Selected:
targetColor = colors.selectedColor;
break;
case ButtonState.Disabled:
targetColor = colors.disabledColor;
break;
default:
targetColor = colors.normalColor;
break;
}
_targetGraphic.color = targetColor;
}
// IInteractableUi implementation
public void OnInteract()
{
if (_selectable != null && _selectable.interactable)
{
// This method is called for programmatic interaction
HandleButtonClick();
}
}
// Pointer event handlers
public void OnPointerEnter(PointerEventData eventData)
{
_isHighlighted = true;
UpdateVisualState();
}
public void OnPointerExit(PointerEventData eventData)
{
_isHighlighted = false;
UpdateVisualState();
}
public void OnPointerDown(PointerEventData eventData)
{
if (eventData.button == PointerEventData.InputButton.Left)
{
if (_isSelected)
{
_isPressed = true;
UpdateVisualState();
return;
}
_isSelected = true;
UpdateVisualState();
}
}
public void OnPointerUp(PointerEventData eventData)
{
if (eventData.button == PointerEventData.InputButton.Left)
{
// Don't reset pressed state in toggle mode when toggled on
if (_isPressed && !(_useToggle && _isToggled))
{
_isPressed = false;
}
_isSelected = false;
UpdateVisualState();
}
}
// Selection event handlers (for keyboard/gamepad navigation)
public void OnSelect(BaseEventData eventData)
{
_isSelected = true;
UpdateVisualState();
}
public void OnDeselect(BaseEventData eventData)
{
_isSelected = false;
UpdateVisualState();
}
// Submit handler (for keyboard/gamepad activation)
public void OnSubmit(BaseEventData eventData)
{
HandleButtonClick();
}
// Public API
public bool IsInteractable => _selectable != null && _selectable.interactable;
public void SetInteractable(bool interactable)
{
if (_selectable != null)
{
_selectable.interactable = interactable;
UpdateVisualState();
}
}
public void ForceUpdateState()
{
UpdateVisualState();
}
// Toggle functionality API
public bool UseToggle
{
get => _useToggle;
set => _useToggle = value;
}
public bool IsToggled => _isToggled;
public void SetToggleState(bool toggled)
{
if (!_useToggle) return;
_isToggled = toggled;
if (_isToggled)
{
_isPressed = true;
if (_selectable != null)
_selectable.interactable = false;
}
else
{
_isPressed = false;
if (_selectable != null)
_selectable.interactable = true;
}
UpdateVisualState();
}
}
}
// using System;
// using UnityEngine;
// using UnityEngine.UI;
// using UnityEngine.EventSystems;
// using UnityEngine.InputSystem;
//
// namespace DDD
// {
// public enum ButtonState
// {
// Normal,
// Highlighted,
// Pressed,
// Selected,
// Disabled
// }
//
// public enum ButtonType
// {
// None = 0,
// Toggle
// }
//
// // TODO : ButtonType == None
// // normal, selected(마우스 pointerEnter, eventsystem selected), pressed (마우스 pointerDown, 키보드 외부 입력처리), disabled
// // Highlighted가 사실상 selected로 통합, 실제로 마우스가 가리키는 오브젝트가 eventsystem의 selected가 됨
// // ButtonType == Toggle
// // normal, highlighted, selected(계속 눌려있는 상태, 추후에 ToggleGroup 클래스에서 관리 - 다른 토글이 눌리기 전까지 풀리지 않음), pressed(눌리면 selected 고정), disabled
//
// public class UiButton : MonoBehaviour, IInteractableUi, IPointerEnterHandler, IPointerExitHandler,
// IPointerDownHandler, IPointerUpHandler, ISelectHandler, IDeselectHandler, ISubmitHandler
// {
// [Header("Button Components")]
// [SerializeField] private Button _button;
// [SerializeField] private Selectable _selectable;
//
// [Header("State Synchronization")]
// [SerializeField] private bool _synchronizeStates = true;
// [SerializeField] private bool _handleKeyboardInput = true;
// [SerializeField] private bool _handleGamepadInput = true;
//
// [Header("Visual Feedback")]
// [SerializeField] private Animator _animator;
// [SerializeField] private Image _targetGraphic;
//
// [Header("Toggle Functionality")]
// [SerializeField] private ButtonType _buttonType;
//
// // State tracking
// private bool _isPressed;
// private bool _isHighlighted;
// private bool _isSelected;
// private bool _wasSelectedByKeyboard;
// private bool _isToggled = false;
//
// // Events
// public event Action OnClicked;
// public event Action OnStateChanged;
//
// // Animation parameter hashes (if using Animator)
// private readonly int _normalHash = Animator.StringToHash("Normal");
// private readonly int _highlightedHash = Animator.StringToHash("Highlighted");
// private readonly int _pressedHash = Animator.StringToHash("Pressed");
// private readonly int _selectedHash = Animator.StringToHash("Selected");
// private readonly int _disabledHash = Animator.StringToHash("Disabled");
//
// private void Awake()
// {
// InitializeComponents();
// }
//
// private void OnEnable()
// {
// if (_button != null)
// {
// _button.onClick.AddListener(HandleButtonClick);
// }
//
// UpdateVisualState();
// }
//
// private void OnDisable()
// {
// if (_button != null)
// {
// _button.onClick.RemoveListener(HandleButtonClick);
// }
// }
//
// private void Update()
// {
// HandleInputUpdate();
// }
//
// private void InitializeComponents()
// {
// // Get Button component if not assigned
// if (_button == null)
// {
// _button = GetComponent<Button>();
// }
//
// // Get Selectable component (Button inherits from Selectable)
// if (_selectable == null)
// {
// _selectable = _button;
// }
//
// // Get target graphic from button if not assigned
// if (_targetGraphic == null && _button != null)
// {
// _targetGraphic = _button.targetGraphic as Image;
// }
//
// // Get Animator if not assigned
// if (_animator == null)
// {
// _animator = GetComponent<Animator>();
// }
// }
//
// private void HandleInputUpdate()
// {
// if (!_handleKeyboardInput && !_handleGamepadInput) return;
// if (_selectable == null || !_selectable.interactable) return;
//
// // Handle keyboard/gamepad input when this button is selected
// if (EventSystem.current != null && EventSystem.current.currentSelectedGameObject == gameObject)
// {
// //HandleSelectedInput();
// }
// }
//
// private void HandleSelectedInput()
// {
// var keyboard = Keyboard.current;
// var gamepad = Gamepad.current;
//
// // Handle keyboard input
// if (_handleKeyboardInput && keyboard != null)
// {
// if (keyboard.enterKey.wasPressedThisFrame || keyboard.spaceKey.wasPressedThisFrame)
// {
// HandleButtonClick();
// }
// }
//
// // Handle gamepad input
// if (_handleGamepadInput && gamepad != null)
// {
// if (gamepad.buttonSouth.wasPressedThisFrame) // A button on Xbox controller
// {
// HandleButtonClick();
// }
// }
// }
//
// private void HandleButtonClick()
// {
// if (_selectable != null && !_selectable.interactable) return;
//
// if (_buttonType == ButtonType.Toggle)
// {
// _isToggled = !_isToggled;
//
// if (_isToggled)
// {
// // When toggled on, maintain pressed state and make non-interactable
// _isPressed = true;
// _selectable.interactable = false;
// }
// else
// {
// // When toggled off, restore normal behavior
// _isPressed = false;
// _selectable.interactable = true;
// }
//
// UpdateVisualState();
// }
//
// OnClicked?.Invoke();
// }
//
// private void UpdateVisualState()
// {
// if (!_synchronizeStates) return;
//
// var currentState = GetCurrentState();
// print(currentState);
// ApplyVisualState(currentState);
// OnStateChanged?.Invoke();
// }
//
// private ButtonState GetCurrentState()
// {
// if (_selectable == null || (!_selectable.interactable && !_isToggled))
// return ButtonState.Disabled;
//
// // Toggle mode: when toggled on, maintain pressed state even if not interactable
// if (_useToggle && _isToggled)
// return ButtonState.Pressed;
//
// if (_isPressed)
// return ButtonState.Pressed;
//
// if (_isSelected)
// return ButtonState.Selected;
//
// if (_isHighlighted)
// return ButtonState.Highlighted;
//
// return ButtonState.Normal;
// }
//
// private void ApplyVisualState(ButtonState state)
// {
// // Apply animator state if available
// if (_animator != null && _animator.runtimeAnimatorController != null)
// {
// ApplyAnimatorState(state);
// }
//
// // Apply color tint if using Button's color block
// if (_button != null && _targetGraphic != null)
// {
// ApplyColorState(state);
// }
// }
//
// private void ApplyAnimatorState(ButtonState state)
// {
// switch (state)
// {
// case ButtonState.Normal:
// _animator.SetTrigger(_normalHash);
// break;
// case ButtonState.Highlighted:
// _animator.SetTrigger(_highlightedHash);
// break;
// case ButtonState.Pressed:
// _animator.SetTrigger(_pressedHash);
// break;
// case ButtonState.Selected:
// _animator.SetTrigger(_selectedHash);
// break;
// case ButtonState.Disabled:
// _animator.SetTrigger(_disabledHash);
// break;
// }
// }
//
// private void ApplyColorState(ButtonState state)
// {
// var colors = _button.colors;
// Color targetColor;
//
// switch (state)
// {
// case ButtonState.Normal:
// targetColor = colors.normalColor;
// break;
// case ButtonState.Highlighted:
// targetColor = colors.highlightedColor;
// break;
// case ButtonState.Pressed:
// targetColor = colors.pressedColor;
// break;
// case ButtonState.Selected:
// targetColor = colors.selectedColor;
// break;
// case ButtonState.Disabled:
// targetColor = colors.disabledColor;
// break;
// default:
// targetColor = colors.normalColor;
// break;
// }
//
// _targetGraphic.color = targetColor;
// }
//
// // IInteractableUi implementation
// public void OnInteract()
// {
// if (_selectable != null && _selectable.interactable)
// {
// // This method is called for programmatic interaction
// HandleButtonClick();
// }
// }
//
// // Pointer event handlers
// public void OnPointerEnter(PointerEventData eventData)
// {
// _isHighlighted = true;
// UpdateVisualState();
// }
//
// public void OnPointerExit(PointerEventData eventData)
// {
// _isHighlighted = false;
// UpdateVisualState();
// }
//
// public void OnPointerDown(PointerEventData eventData)
// {
// if (eventData.button == PointerEventData.InputButton.Left)
// {
// if (_isSelected)
// {
// _isPressed = true;
// UpdateVisualState();
// return;
// }
// _isSelected = true;
// UpdateVisualState();
// }
// }
//
// public void OnPointerUp(PointerEventData eventData)
// {
// if (eventData.button == PointerEventData.InputButton.Left)
// {
// // Don't reset pressed state in toggle mode when toggled on
// if (_isPressed && !(_useToggle && _isToggled))
// {
// _isPressed = false;
// }
// _isSelected = false;
// UpdateVisualState();
// }
// }
//
// // Selection event handlers (for keyboard/gamepad navigation)
// public void OnSelect(BaseEventData eventData)
// {
// _isSelected = true;
// UpdateVisualState();
// }
//
// public void OnDeselect(BaseEventData eventData)
// {
// _isSelected = false;
// UpdateVisualState();
// }
//
// // Submit handler (for keyboard/gamepad activation)
// public void OnSubmit(BaseEventData eventData)
// {
// HandleButtonClick();
// }
//
// // Public API
// public bool IsInteractable => _selectable != null && _selectable.interactable;
//
// public void SetInteractable(bool interactable)
// {
// if (_selectable != null)
// {
// _selectable.interactable = interactable;
// UpdateVisualState();
// }
// }
//
// public void ForceUpdateState()
// {
// UpdateVisualState();
// }
//
// // Toggle functionality API
// public bool UseToggle
// {
// get => _useToggle;
// set => _useToggle = value;
// }
//
// public bool IsToggled => _isToggled;
//
// public void SetToggleState(bool toggled)
// {
// if (!_useToggle) return;
//
// _isToggled = toggled;
//
// if (_isToggled)
// {
// _isPressed = true;
// if (_selectable != null)
// _selectable.interactable = false;
// }
// else
// {
// _isPressed = false;
// if (_selectable != null)
// _selectable.interactable = true;
// }
//
// UpdateVisualState();
// }
// }
// }

View File

@ -1,133 +0,0 @@
using UnityEngine;
using UnityEngine.UI;
namespace DDD
{
/// <summary>
/// Simple test script to verify UiButton functionality.
/// Attach this to a GameObject with UiButton component to test.
/// </summary>
public class UiButtonTest : MonoBehaviour
{
[SerializeField] private UiButton _uiButton;
[SerializeField] private Text _statusText;
private int _clickCount = 0;
private void Start()
{
// Get UiButton if not assigned
if (_uiButton == null)
{
_uiButton = GetComponent<UiButton>();
}
if (_uiButton != null)
{
// Subscribe to events
_uiButton.OnClicked += HandleButtonClicked;
_uiButton.OnStateChanged += HandleStateChanged;
Debug.Log("[DEBUG_LOG] UiButton test initialized");
}
else
{
Debug.LogError("[DEBUG_LOG] UiButton component not found!");
}
UpdateStatusText();
}
private void OnDestroy()
{
if (_uiButton != null)
{
_uiButton.OnClicked -= HandleButtonClicked;
_uiButton.OnStateChanged -= HandleStateChanged;
}
}
private void HandleButtonClicked()
{
_clickCount++;
Debug.Log($"[DEBUG_LOG] UiButton clicked! Count: {_clickCount}");
UpdateStatusText();
}
private void HandleStateChanged()
{
Debug.Log($"[DEBUG_LOG] UiButton state changed. Interactable: {_uiButton.IsInteractable}");
}
private void UpdateStatusText()
{
if (_statusText != null)
{
_statusText.text = $"Clicks: {_clickCount}\nInteractable: {(_uiButton?.IsInteractable ?? false)}";
}
}
// Test methods that can be called from inspector or other scripts
[ContextMenu("Toggle Interactable")]
public void ToggleInteractable()
{
if (_uiButton != null)
{
_uiButton.SetInteractable(!_uiButton.IsInteractable);
Debug.Log($"[DEBUG_LOG] Button interactable set to: {_uiButton.IsInteractable}");
UpdateStatusText();
}
}
[ContextMenu("Force Update State")]
public void ForceUpdateState()
{
if (_uiButton != null)
{
_uiButton.ForceUpdateState();
Debug.Log("[DEBUG_LOG] Button state forcefully updated");
}
}
[ContextMenu("Reset Click Count")]
public void ResetClickCount()
{
_clickCount = 0;
Debug.Log("[DEBUG_LOG] Click count reset");
UpdateStatusText();
}
[ContextMenu("Toggle Use Toggle Mode")]
public void ToggleUseToggleMode()
{
if (_uiButton != null)
{
_uiButton.UseToggle = !_uiButton.UseToggle;
Debug.Log($"[DEBUG_LOG] Toggle mode set to: {_uiButton.UseToggle}");
UpdateStatusText();
}
}
[ContextMenu("Set Toggle State On")]
public void SetToggleStateOn()
{
if (_uiButton != null)
{
_uiButton.SetToggleState(true);
Debug.Log($"[DEBUG_LOG] Toggle state set to ON. Toggled: {_uiButton.IsToggled}");
UpdateStatusText();
}
}
[ContextMenu("Set Toggle State Off")]
public void SetToggleStateOff()
{
if (_uiButton != null)
{
_uiButton.SetToggleState(false);
Debug.Log($"[DEBUG_LOG] Toggle state set to OFF. Toggled: {_uiButton.IsToggled}");
UpdateStatusText();
}
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: cc3aec81f35244d39191739b3bd208d2
timeCreated: 1755681789

View File

@ -1,15 +1,18 @@
using System;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;
namespace DDD
{
public class UiManager : Singleton<UiManager>, IManager, IGameFlowHandler
{
[SerializeField] private AssetReference _popupUiState;
[SerializeField] private Transform _hudRoot;
[SerializeField] private Transform _interactionUiRoot;
[SerializeField] private Transform _popupUiRoot;
[SerializeField] private Transform _commonUiRoot;
public PopupUiState PopupUiState { get; private set; }
public UiData UiData => GameData.Instance.UiData;
public UiState UiState => GameState.Instance.UiState;
private void OnDestroy()
{
@ -18,17 +21,28 @@ private void OnDestroy()
public void PreInit()
{
ClearAll();
GameFlowManager.Instance.FlowHandlers.Add(this);
}
foreach (Transform child in _popupUiRoot)
public Task Init()
{
Destroy(child.gameObject);
var flowToUiMapping = UiData.FlowToUiMapping;
foreach (var flowToUis in flowToUiMapping)
{
if (flowToUis.Key == GameFlowState.All)
{
foreach (var ui in flowToUis.Value)
{
var uiType = ui.UiType;
var root = GetUiRoot(uiType);
UiState.CreateUi(ui, root);
}
}
}
public async Task Init()
{
await LoadData();
return Task.CompletedTask;
}
public void PostInit()
@ -36,27 +50,62 @@ public void PostInit()
}
private void ClearObjects(Transform root)
{
foreach (Transform child in root)
{
Destroy(child.gameObject);
}
}
private void ClearAll()
{
ClearObjects(_hudRoot);
ClearObjects(_interactionUiRoot);
ClearObjects(_popupUiRoot);
ClearObjects(_commonUiRoot);
}
public Task OnReadyNewFlow(GameFlowState newFlowState)
{
PopupUiState.CreateMatchingPopupUis(newFlowState, _popupUiRoot);
var flowToUiMapping = UiData.FlowToUiMapping;
foreach (var flowToUis in flowToUiMapping)
{
if ((flowToUis.Key & newFlowState) != 0)
{
foreach (var ui in flowToUis.Value)
{
var uiType = ui.UiType;
var root = GetUiRoot(uiType);
UiState.CreateUi(ui, root);
}
}
else
{
foreach (var ui in flowToUis.Value)
{
UiState.DestroyUi(ui);
}
}
}
return Task.CompletedTask;
}
public Task OnExitCurrentFlow(GameFlowState exitingFlowState)
{
PopupUiState.DestroyMatchingPopupUis(exitingFlowState);
return Task.CompletedTask;
}
private async Task LoadData()
public Transform GetUiRoot(UiType uiType)
{
var handle = _popupUiState.LoadAssetAsync<PopupUiState>();
await handle.Task;
PopupUiState = handle.Result;
Debug.Assert(PopupUiState != null, "PopupUiState is null");
PopupUiState.Initialize();
return uiType switch
{
UiType.Hud => _hudRoot,
UiType.Interaction => _interactionUiRoot,
UiType.Popup => _popupUiRoot,
UiType.Common => _commonUiRoot,
_ => throw new Exception("UiType 설정 오류")
};
}
}
}