From e0e0f3564ffa85416c1974a2b17d873a6080158e Mon Sep 17 00:00:00 2001 From: Jeonghyeon Ha Date: Wed, 20 Aug 2025 12:59:29 +0900 Subject: [PATCH 01/36] =?UTF-8?q?=EC=9D=B8=ED=84=B0=EB=9E=99=EC=85=98=20?= =?UTF-8?q?=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EA=B5=AC=EC=A1=B0?= =?UTF-8?q?=EC=B2=B4=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit InteractionExecutionParameters, InteractionDisplayParamaters --- .../_DDD/_Scripts/GameEvent/IInteractable.cs | 36 ++++++++++++++++--- .../RestaurantInteractionComponent.cs | 25 +++++++++---- 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/Assets/_DDD/_Scripts/GameEvent/IInteractable.cs b/Assets/_DDD/_Scripts/GameEvent/IInteractable.cs index 49c62717e..52c19a0e1 100644 --- a/Assets/_DDD/_Scripts/GameEvent/IInteractable.cs +++ b/Assets/_DDD/_Scripts/GameEvent/IInteractable.cs @@ -4,14 +4,42 @@ namespace DDD { + /* TODO : BitFlag 제거해야할듯. + 인터랙션 개수가 엄청나게 많아질것으로 예상됨. + 혹은 인터랙션 타입을 여기서 추가하면 안 되고 여기는 몇가지 정적인 타입만 두고 RestaurantInteraction 같은 식으로 확장해서 사용해야함. + 확장성을 생각하면 RestaurantInteractionSolver에서 RestaurantInteractionType을 리졸브해서 또 다른 Solver로 포워딩하는 식으로. + 편하게 하려면 그냥 여기서 비트연산자 없애고 인터랙션타입을 무한정 늘리기. + */ [Flags] - public enum InteractionType : uint + public enum InteractionType : ulong { None = 0u, RestaurantManagementUi = 1u << 0, OpenRestaurant = 1u << 1, All = 0xFFFFFFFFu } + + [System.Serializable] + public struct InteractionExecutionParameters + { + [SerializeField] private float _holdTime; + public float HoldTime => _holdTime; + public InteractionExecutionParameters(float holdTime = 1f) + { + _holdTime = holdTime; + } + } + + [System.Serializable] + public struct InteractionDisplayParameters + { + [SerializeField] private string _messageKey; + public string MessageKey => _messageKey; + public InteractionDisplayParameters(string messageKey = "") + { + _messageKey = messageKey; + } + } public interface IInteractable { @@ -21,10 +49,8 @@ public interface IInteractable InteractionType GetInteractionType(); GameObject GetInteractableGameObject(); void InitializeInteraction(InteractionType interactionType); - // TODO : Struct InteractionExecutionParameters 등으로 정리 - float GetRequiredHoldTime(); - // TODO : Struct InteractionDisplayParameters 등으로 정리 - string GetInteractionMessageKey(); + InteractionExecutionParameters GetExecutionParameters(); + InteractionDisplayParameters GetDisplayParameters(); Vector3[] GetInteractionPoints(); } diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionComponent.cs b/Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionComponent.cs index 766b67be8..fcd7860d7 100644 --- a/Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionComponent.cs +++ b/Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionComponent.cs @@ -2,12 +2,12 @@ namespace DDD { + public class RestaurantInteractionComponent : MonoBehaviour, IInteractable { - [SerializeField] private InteractionType _interactionType = InteractionType.None; - [SerializeField] private float _holdTime = 1f; - [SerializeField] private string _interactionMessageKey = ""; - + [SerializeField] protected InteractionType _interactionType = InteractionType.None; + [SerializeField] protected InteractionExecutionParameters _executionParameters = new InteractionExecutionParameters(1f); + [SerializeField] protected InteractionDisplayParameters _displayParameters = new InteractionDisplayParameters(""); [SerializeField] protected GameFlowState _interactionAvailableFlows; [SerializeField] private Transform[] _aiInteractionPoints; @@ -49,21 +49,32 @@ public void InitializeInteraction(InteractionType interactionType) _interactionType = interactionType; } + // 새로운 스트럭트 기반 메서드들 + public InteractionExecutionParameters GetExecutionParameters() + { + return _executionParameters; + } + + public InteractionDisplayParameters GetDisplayParameters() + { + return _displayParameters; + } + + // 하위 호환성을 위한 기존 메서드들 public float GetRequiredHoldTime() { - return _holdTime; + return _executionParameters.HoldTime; } public string GetInteractionMessageKey() { - return _interactionMessageKey; + return _displayParameters.MessageKey; } public Vector3[] GetInteractionPoints() { if (_aiInteractionPoints == null || _aiInteractionPoints.Length == 0) { - // AI 상호작용 포인트가 설정되지 않은 경우 오브젝트의 위치를 기본값으로 반환 return new Vector3[] { transform.position }; } From 75bc5e52cc2fa92149995ec6ef7809ce7d10df2c Mon Sep 17 00:00:00 2001 From: Jeonghyeon Ha Date: Wed, 20 Aug 2025 12:59:43 +0900 Subject: [PATCH 02/36] =?UTF-8?q?=EC=9D=B8=ED=84=B0=EB=9E=99=EC=85=98=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/_DDD/_Scripts/RestaurantEvent/Cosmetic.meta | 3 +++ .../RestaurantEvent/{ => Cosmetic}/InteractableHighlight.cs | 0 .../{ => Cosmetic}/InteractableHighlight.cs.meta | 0 3 files changed, 3 insertions(+) create mode 100644 Assets/_DDD/_Scripts/RestaurantEvent/Cosmetic.meta rename Assets/_DDD/_Scripts/RestaurantEvent/{ => Cosmetic}/InteractableHighlight.cs (100%) rename Assets/_DDD/_Scripts/RestaurantEvent/{ => Cosmetic}/InteractableHighlight.cs.meta (100%) diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/Cosmetic.meta b/Assets/_DDD/_Scripts/RestaurantEvent/Cosmetic.meta new file mode 100644 index 000000000..7a4acd006 --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantEvent/Cosmetic.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 59f8f35926714893bb8d4192e527fa48 +timeCreated: 1755655771 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/InteractableHighlight.cs b/Assets/_DDD/_Scripts/RestaurantEvent/Cosmetic/InteractableHighlight.cs similarity index 100% rename from Assets/_DDD/_Scripts/RestaurantEvent/InteractableHighlight.cs rename to Assets/_DDD/_Scripts/RestaurantEvent/Cosmetic/InteractableHighlight.cs diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/InteractableHighlight.cs.meta b/Assets/_DDD/_Scripts/RestaurantEvent/Cosmetic/InteractableHighlight.cs.meta similarity index 100% rename from Assets/_DDD/_Scripts/RestaurantEvent/InteractableHighlight.cs.meta rename to Assets/_DDD/_Scripts/RestaurantEvent/Cosmetic/InteractableHighlight.cs.meta From c7a4aee67da093c2d6d8b0c48326bcc0f4cbd5e0 Mon Sep 17 00:00:00 2001 From: Jeonghyeon Ha Date: Wed, 20 Aug 2025 14:07:33 +0900 Subject: [PATCH 03/36] =?UTF-8?q?WIP=20:=20=EC=9D=B8=ED=84=B0=EB=9E=99?= =?UTF-8?q?=EC=85=98=20=EC=84=9C=EB=B8=8C=EC=8B=9C=EC=8A=A4=ED=85=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_DDD/_Scripts/GameEvent/IInteractable.cs | 10 +-- .../GameEvent/InteractionSubsystem.cs | 4 + .../GameEvent/InteractionSubsystem.cs.meta | 3 + .../Core/RestaurantCharacterInteraction.cs | 2 +- .../Player/RestaurantPlayerInteraction.cs | 4 +- .../RestaurantEnvironment/Interactions.meta | 3 + .../RestaurantOrderInteraction.cs | 76 +++++++++++++++++++ .../RestaurantOrderInteraction.cs.meta | 3 + .../RestaurantInteractionEvents.cs | 3 +- .../Solvers/RestaurantOrderSolver.cs | 35 +++++++++ .../Solvers/RestaurantOrderSolver.cs.meta | 3 + 11 files changed, 135 insertions(+), 11 deletions(-) create mode 100644 Assets/_DDD/_Scripts/GameEvent/InteractionSubsystem.cs create mode 100644 Assets/_DDD/_Scripts/GameEvent/InteractionSubsystem.cs.meta create mode 100644 Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions.meta create mode 100644 Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteraction.cs create mode 100644 Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteraction.cs.meta create mode 100644 Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrderSolver.cs create mode 100644 Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrderSolver.cs.meta diff --git a/Assets/_DDD/_Scripts/GameEvent/IInteractable.cs b/Assets/_DDD/_Scripts/GameEvent/IInteractable.cs index 52c19a0e1..c52bd47fd 100644 --- a/Assets/_DDD/_Scripts/GameEvent/IInteractable.cs +++ b/Assets/_DDD/_Scripts/GameEvent/IInteractable.cs @@ -4,18 +4,14 @@ namespace DDD { - /* TODO : BitFlag 제거해야할듯. - 인터랙션 개수가 엄청나게 많아질것으로 예상됨. - 혹은 인터랙션 타입을 여기서 추가하면 안 되고 여기는 몇가지 정적인 타입만 두고 RestaurantInteraction 같은 식으로 확장해서 사용해야함. - 확장성을 생각하면 RestaurantInteractionSolver에서 RestaurantInteractionType을 리졸브해서 또 다른 Solver로 포워딩하는 식으로. - 편하게 하려면 그냥 여기서 비트연산자 없애고 인터랙션타입을 무한정 늘리기. - */ [Flags] - public enum InteractionType : ulong + public enum InteractionType : uint { None = 0u, RestaurantManagementUi = 1u << 0, OpenRestaurant = 1u << 1, + RestaurantOrder = 1u << 2, + RestaurantMeal = 1u << 3, All = 0xFFFFFFFFu } diff --git a/Assets/_DDD/_Scripts/GameEvent/InteractionSubsystem.cs b/Assets/_DDD/_Scripts/GameEvent/InteractionSubsystem.cs new file mode 100644 index 000000000..8ca5eefc8 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameEvent/InteractionSubsystem.cs @@ -0,0 +1,4 @@ +namespace DDD +{ + +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameEvent/InteractionSubsystem.cs.meta b/Assets/_DDD/_Scripts/GameEvent/InteractionSubsystem.cs.meta new file mode 100644 index 000000000..1bea8095d --- /dev/null +++ b/Assets/_DDD/_Scripts/GameEvent/InteractionSubsystem.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 2e8dca10c7c74466bd1df632b7d111c4 +timeCreated: 1755665098 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantCharacter/Core/RestaurantCharacterInteraction.cs b/Assets/_DDD/_Scripts/RestaurantCharacter/Core/RestaurantCharacterInteraction.cs index 2ca1f4021..c4745fafd 100644 --- a/Assets/_DDD/_Scripts/RestaurantCharacter/Core/RestaurantCharacterInteraction.cs +++ b/Assets/_DDD/_Scripts/RestaurantCharacter/Core/RestaurantCharacterInteraction.cs @@ -44,7 +44,7 @@ protected virtual void Update() _interactHeldTime += Time.deltaTime; - float requiredHoldTime = _interactingTarget.GetRequiredHoldTime(); + float requiredHoldTime = _interactingTarget.GetExecutionParameters().HoldTime; float ratio = Mathf.Clamp01(_interactHeldTime / requiredHoldTime); if (_interactHeldTime >= requiredHoldTime) diff --git a/Assets/_DDD/_Scripts/RestaurantCharacter/Player/RestaurantPlayerInteraction.cs b/Assets/_DDD/_Scripts/RestaurantCharacter/Player/RestaurantPlayerInteraction.cs index 8f648cef9..a86e19447 100644 --- a/Assets/_DDD/_Scripts/RestaurantCharacter/Player/RestaurantPlayerInteraction.cs +++ b/Assets/_DDD/_Scripts/RestaurantCharacter/Player/RestaurantPlayerInteraction.cs @@ -49,7 +49,7 @@ private void OnInteractPerformed(InputAction.CallbackContext context) { if (_nearestInteractable == null || CanInteractTo(_nearestInteractable) == false) return; - float requiredHoldTime = _nearestInteractable.GetRequiredHoldTime(); + float requiredHoldTime = _nearestInteractable.GetExecutionParameters().HoldTime; if (requiredHoldTime <= 0f) { @@ -98,7 +98,7 @@ private void BroadcastShowUi(IInteractable interactable, bool canInteract, float { var evt = GameEvents.ShowInteractionUiEvent; evt.CanInteract = canInteract; - evt.TextKey = interactable.GetInteractionMessageKey(); + evt.TextKey = interactable.GetDisplayParameters().MessageKey; evt.HoldProgress = ratio; EventBus.Broadcast(evt); } diff --git a/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions.meta b/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions.meta new file mode 100644 index 000000000..52a559f1d --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 30a19025059e4338b29bf140644dd5aa +timeCreated: 1755655803 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteraction.cs b/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteraction.cs new file mode 100644 index 000000000..8c3ca91c1 --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteraction.cs @@ -0,0 +1,76 @@ +using System; +using UnityEngine; + +namespace DDD +{ + public interface IInteractionSubsystemObject where T : Enum + { + T GetInteractionSubsystemType(); + } + public interface IInteractionSubsystemSolver where T : Enum + { + bool ExecuteInteractionSubsystem(IInteractor interactor, IInteractable interactable, ScriptableObject payloadSo = null); + bool CanExecuteInteractionSubsystem(IInteractor interactor = null, IInteractable interactable = null, + ScriptableObject payloadSo = null); + } + + public interface IInteractionSubsystemSubject where T : Enum + { + bool CanSolveInteractionType(T interactionSubsystemType); + bool CanInteractTo(IInteractionSubsystemObject interactableSubsystemObject, + ScriptableObject payloadSo = null); + } + + [Flags] + public enum RestaurantOrderInteractionType : uint + { + // None = 0u, + WaitCustomer = 1u << 0, + // WaitOrder = 1u << 1, + // WaitServe = 1u << 2, + // All = 0xFFFFFFFFu + } + public class RestaurantOrderInteraction : RestaurantInteractionComponent, IInteractionSubsystemObject + { + [SerializeField] protected RestaurantOrderInteractionType _initialOrderInteractionType = RestaurantOrderInteractionType.WaitCustomer; + private RestaurantOrderInteractionType _currentRestaurantOrderInteractionType; + + // EDITOR + private void Reset() + { + SetInteractionTypeToRestaurantOrder(); + } + private void OnValidate() + { + SetInteractionTypeToRestaurantOrder(); + } + // ~EDITOR + + private void Start() + { + + } + + private void SetInteractionTypeToRestaurantOrder() + { + _interactionType = InteractionType.RestaurantOrder; + } + InteractionType GetInteractionType() + { + return InteractionType.RestaurantOrder; + } + bool CanInteract() + { + return TODO_IMPLEMENT_ME; + } + bool OnInteracted(IInteractor interactor, ScriptableObject payloadSo = null); + void InitializeInteraction(InteractionType interactionType); + InteractionExecutionParameters GetExecutionParameters(); + InteractionDisplayParameters GetDisplayParameters(); + + public RestaurantOrderInteractionType GetInteractionSubsystemType() + { + return _currentRestaurantOrderInteractionType; + } + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteraction.cs.meta b/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteraction.cs.meta new file mode 100644 index 000000000..97b6c0b9a --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteraction.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c0b1e0992510498b8d33d5b6094b8f4b +timeCreated: 1755655843 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionEvents.cs b/Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionEvents.cs index 2f86f12c3..6d9021765 100644 --- a/Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionEvents.cs +++ b/Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionEvents.cs @@ -14,7 +14,8 @@ public static class RestaurantInteractionEventSolvers public static Dictionary TypeToSolver = new() { {InteractionType.RestaurantManagementUi, typeof(RestaurantManagementUiEventSolver)}, - {InteractionType.OpenRestaurant, typeof(RestaurantOpenEventSolver)} + {InteractionType.OpenRestaurant, typeof(RestaurantOpenEventSolver)}, + {InteractionType.RestaurantOrder, typeof(RestaurantOrderSolver)} }; } diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrderSolver.cs b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrderSolver.cs new file mode 100644 index 000000000..653b7e355 --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrderSolver.cs @@ -0,0 +1,35 @@ +using UnityEngine; + +namespace DDD +{ + public class RestaurantOrderSolver : MonoBehaviour, IInteractionSolver, IInteractionSubsystemSolver + { + public bool ExecuteInteraction(IInteractor interactor, IInteractable interactable, ScriptableObject payloadSo = null) + { + return ExecuteInteractionSubsystem(interactor, interactable, payloadSo); + } + + public bool CanExecuteInteraction(IInteractor interactor = null, IInteractable interactable = null, + ScriptableObject payloadSo = null) + { + return CanExecuteInteractionSubsystem(interactor, interactable, payloadSo); + } + + public bool ExecuteInteractionSubsystem(IInteractor interactor, IInteractable interactable, ScriptableObject payloadSo = null) + { + TODO_IMPLEMENT_ME + } + + public bool CanExecuteInteractionSubsystem(IInteractor interactor = null, IInteractable interactable = null, + ScriptableObject payloadSo = null) + { + if (interactable is IInteractionSubsystemObject subsystem) + { + RestaurantOrderInteractionType interactionType = subsystem.GetInteractionSubsystemType(); + // Can I solve this interaction type? + TODO_IMPLEMENT_ME + } + return false; + } + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrderSolver.cs.meta b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrderSolver.cs.meta new file mode 100644 index 000000000..0d072c20a --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrderSolver.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b832b16fa5964817afa836bc86f25f6b +timeCreated: 1755664610 \ No newline at end of file From db0388b1715c0f25e9d0a27fc1bd1712a1560856 Mon Sep 17 00:00:00 2001 From: NTG Date: Wed, 20 Aug 2025 15:21:59 +0900 Subject: [PATCH 04/36] =?UTF-8?q?material=20=EC=9E=90=EB=8F=99=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Environments/Prop/CounterTable/Mat_CounterTable.mat | 2 +- .../_Addressables/Environments/Prop/Vomiting/Mat_Vomiting.mat | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Assets/_DDD/_Addressables/Environments/Prop/CounterTable/Mat_CounterTable.mat b/Assets/_DDD/_Addressables/Environments/Prop/CounterTable/Mat_CounterTable.mat index d4adb2c0f..adb3605ab 100644 --- a/Assets/_DDD/_Addressables/Environments/Prop/CounterTable/Mat_CounterTable.mat +++ b/Assets/_DDD/_Addressables/Environments/Prop/CounterTable/Mat_CounterTable.mat @@ -21,7 +21,7 @@ Material: m_LightmapFlags: 2 m_EnableInstancingVariants: 0 m_DoubleSidedGI: 0 - m_CustomRenderQueue: -1 + m_CustomRenderQueue: 2450 stringTagMap: RenderType: TransparentCutout disabledShaderPasses: diff --git a/Assets/_DDD/_Addressables/Environments/Prop/Vomiting/Mat_Vomiting.mat b/Assets/_DDD/_Addressables/Environments/Prop/Vomiting/Mat_Vomiting.mat index 2fbe1354a..ae909a9e2 100644 --- a/Assets/_DDD/_Addressables/Environments/Prop/Vomiting/Mat_Vomiting.mat +++ b/Assets/_DDD/_Addressables/Environments/Prop/Vomiting/Mat_Vomiting.mat @@ -21,7 +21,7 @@ Material: m_LightmapFlags: 2 m_EnableInstancingVariants: 0 m_DoubleSidedGI: 0 - m_CustomRenderQueue: -1 + m_CustomRenderQueue: 2450 stringTagMap: RenderType: TransparentCutout disabledShaderPasses: From 832c3690733066b5dab2ed564457f729541ff01f Mon Sep 17 00:00:00 2001 From: NTG Date: Wed, 20 Aug 2025 15:22:08 +0900 Subject: [PATCH 05/36] =?UTF-8?q?ui=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/_DDD/_Scripts/GameUi/New.meta | 8 + .../_DDD/_Scripts/GameUi/New/Converters.meta | 8 + .../GameUi/New/Converters/CommonConverters.cs | 223 ++++++++++++ .../New/Converters/CommonConverters.cs.meta | 2 + .../GameUi/New/Converters/IValueConverter.cs | 39 +++ .../New/Converters/IValueConverter.cs.meta | 2 + .../_Scripts/GameUi/New/MIGRATION_GUIDE.md | 321 ++++++++++++++++++ .../GameUi/New/MIGRATION_GUIDE.md.meta | 7 + Assets/_DDD/_Scripts/GameUi/New/README.md | 278 +++++++++++++++ .../_DDD/_Scripts/GameUi/New/README.md.meta | 7 + Assets/_DDD/_Scripts/GameUi/New/Services.meta | 8 + .../_Scripts/GameUi/New/Services/IService.cs | 62 ++++ .../GameUi/New/Services/IService.cs.meta | 2 + .../GameUi/New/Services/InventoryService.cs | 167 +++++++++ .../New/Services/InventoryService.cs.meta | 2 + Assets/_DDD/_Scripts/GameUi/New/Utils.meta | 8 + .../GameUi/New/Utils/BindToAttribute.cs | 114 +++++++ .../GameUi/New/Utils/BindToAttribute.cs.meta | 2 + .../GameUi/New/Utils/BindingContext.cs | 259 ++++++++++++++ .../GameUi/New/Utils/BindingContext.cs.meta | 2 + .../_Scripts/GameUi/New/Utils/InputPhase.cs | 24 ++ .../GameUi/New/Utils/InputPhase.cs.meta | 2 + .../_DDD/_Scripts/GameUi/New/ViewModels.meta | 8 + .../_Scripts/GameUi/New/ViewModels/Base.meta | 8 + .../New/ViewModels/Base/SimpleViewModel.cs | 87 +++++ .../ViewModels/Base/SimpleViewModel.cs.meta | 2 + .../New/ViewModels/InventoryViewModel.cs | 274 +++++++++++++++ .../New/ViewModels/InventoryViewModel.cs.meta | 2 + Assets/_DDD/_Scripts/GameUi/New/Views.meta | 8 + .../_DDD/_Scripts/GameUi/New/Views/Base.meta | 8 + .../GameUi/New/Views/Base/AutoBindView.cs | 210 ++++++++++++ .../New/Views/Base/AutoBindView.cs.meta | 2 + .../New/Views/Base/IntegratedBasePopupUi.cs | 146 ++++++++ .../Views/Base/IntegratedBasePopupUi.cs.meta | 2 + .../GameUi/New/Views/Base/IntegratedBaseUi.cs | 248 ++++++++++++++ .../New/Views/Base/IntegratedBaseUi.cs.meta | 2 + .../_Scripts/GameUi/New/Views/Examples.meta | 8 + .../Views/Examples/IntegratedInventoryView.cs | 221 ++++++++++++ .../Examples/IntegratedInventoryView.cs.meta | 2 + 39 files changed, 2785 insertions(+) create mode 100644 Assets/_DDD/_Scripts/GameUi/New.meta create mode 100644 Assets/_DDD/_Scripts/GameUi/New/Converters.meta create mode 100644 Assets/_DDD/_Scripts/GameUi/New/Converters/CommonConverters.cs create mode 100644 Assets/_DDD/_Scripts/GameUi/New/Converters/CommonConverters.cs.meta create mode 100644 Assets/_DDD/_Scripts/GameUi/New/Converters/IValueConverter.cs create mode 100644 Assets/_DDD/_Scripts/GameUi/New/Converters/IValueConverter.cs.meta create mode 100644 Assets/_DDD/_Scripts/GameUi/New/MIGRATION_GUIDE.md create mode 100644 Assets/_DDD/_Scripts/GameUi/New/MIGRATION_GUIDE.md.meta create mode 100644 Assets/_DDD/_Scripts/GameUi/New/README.md create mode 100644 Assets/_DDD/_Scripts/GameUi/New/README.md.meta create mode 100644 Assets/_DDD/_Scripts/GameUi/New/Services.meta create mode 100644 Assets/_DDD/_Scripts/GameUi/New/Services/IService.cs create mode 100644 Assets/_DDD/_Scripts/GameUi/New/Services/IService.cs.meta create mode 100644 Assets/_DDD/_Scripts/GameUi/New/Services/InventoryService.cs create mode 100644 Assets/_DDD/_Scripts/GameUi/New/Services/InventoryService.cs.meta create mode 100644 Assets/_DDD/_Scripts/GameUi/New/Utils.meta create mode 100644 Assets/_DDD/_Scripts/GameUi/New/Utils/BindToAttribute.cs create mode 100644 Assets/_DDD/_Scripts/GameUi/New/Utils/BindToAttribute.cs.meta create mode 100644 Assets/_DDD/_Scripts/GameUi/New/Utils/BindingContext.cs create mode 100644 Assets/_DDD/_Scripts/GameUi/New/Utils/BindingContext.cs.meta create mode 100644 Assets/_DDD/_Scripts/GameUi/New/Utils/InputPhase.cs create mode 100644 Assets/_DDD/_Scripts/GameUi/New/Utils/InputPhase.cs.meta create mode 100644 Assets/_DDD/_Scripts/GameUi/New/ViewModels.meta create mode 100644 Assets/_DDD/_Scripts/GameUi/New/ViewModels/Base.meta create mode 100644 Assets/_DDD/_Scripts/GameUi/New/ViewModels/Base/SimpleViewModel.cs create mode 100644 Assets/_DDD/_Scripts/GameUi/New/ViewModels/Base/SimpleViewModel.cs.meta create mode 100644 Assets/_DDD/_Scripts/GameUi/New/ViewModels/InventoryViewModel.cs create mode 100644 Assets/_DDD/_Scripts/GameUi/New/ViewModels/InventoryViewModel.cs.meta create mode 100644 Assets/_DDD/_Scripts/GameUi/New/Views.meta create mode 100644 Assets/_DDD/_Scripts/GameUi/New/Views/Base.meta create mode 100644 Assets/_DDD/_Scripts/GameUi/New/Views/Base/AutoBindView.cs create mode 100644 Assets/_DDD/_Scripts/GameUi/New/Views/Base/AutoBindView.cs.meta create mode 100644 Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBasePopupUi.cs create mode 100644 Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBasePopupUi.cs.meta create mode 100644 Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBaseUi.cs create mode 100644 Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBaseUi.cs.meta create mode 100644 Assets/_DDD/_Scripts/GameUi/New/Views/Examples.meta create mode 100644 Assets/_DDD/_Scripts/GameUi/New/Views/Examples/IntegratedInventoryView.cs create mode 100644 Assets/_DDD/_Scripts/GameUi/New/Views/Examples/IntegratedInventoryView.cs.meta diff --git a/Assets/_DDD/_Scripts/GameUi/New.meta b/Assets/_DDD/_Scripts/GameUi/New.meta new file mode 100644 index 000000000..0034041f1 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b710f3e6683e1e649a6d44dc476b5083 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_DDD/_Scripts/GameUi/New/Converters.meta b/Assets/_DDD/_Scripts/GameUi/New/Converters.meta new file mode 100644 index 000000000..1b0a4a586 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/Converters.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 75317739247907e4099f44de6f643a56 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_DDD/_Scripts/GameUi/New/Converters/CommonConverters.cs b/Assets/_DDD/_Scripts/GameUi/New/Converters/CommonConverters.cs new file mode 100644 index 000000000..d15ab1849 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/Converters/CommonConverters.cs @@ -0,0 +1,223 @@ +using System.Linq; +using UnityEngine; + +namespace DDD.MVVM +{ + /// + /// 불린 값을 반전시키는 컨버터 + /// 예: true → false, false → true + /// + public class InvertBoolConverter : IValueConverter + { + public object Convert(object value) + { + return value is bool boolValue ? !boolValue : false; + } + + public object ConvertBack(object value) + { + return Convert(value); // 반전은 양방향 동일 + } + } + + /// + /// 아이템 개수를 텍스트로 변환하는 컨버터 + /// 예: 5 → "아이템 수: 5" + /// + public class ItemCountConverter : IValueConverter + { + public object Convert(object value) + { + if (value is int count) + return $"아이템 수: {count}"; + return "아이템 수: 0"; + } + + public object ConvertBack(object value) + { + if (value is string str && str.StartsWith("아이템 수: ")) + { + var countStr = str.Substring("아이템 수: ".Length); + return int.TryParse(countStr, out var count) ? count : 0; + } + return 0; + } + } + + /// + /// null 또는 빈 값을 불린으로 변환하는 컨버터 + /// 예: null → false, "" → false, "text" → true + /// + public class IsNullOrEmptyConverter : IValueConverter + { + public object Convert(object value) + { + if (value == null) return true; + if (value is string str) return string.IsNullOrEmpty(str); + return false; + } + + public object ConvertBack(object value) + { + return value is bool boolValue && !boolValue ? "" : null; + } + } + + /// + /// 숫자를 백분율 텍스트로 변환하는 컨버터 + /// 예: 0.75f → "75%" + /// + public class PercentageConverter : IValueConverter + { + public object Convert(object value) + { + if (value is float floatValue) + return $"{(floatValue * 100):F0}%"; + if (value is double doubleValue) + return $"{(doubleValue * 100):F0}%"; + return "0%"; + } + + public object ConvertBack(object value) + { + if (value is string str && str.EndsWith("%")) + { + var percentStr = str.Substring(0, str.Length - 1); + if (float.TryParse(percentStr, out var percent)) + return percent / 100f; + } + return 0f; + } + } + + /// + /// 열거형을 문자열로 변환하는 컨버터 + /// + public class EnumToStringConverter : IValueConverter + { + public object Convert(object value) + { + return value?.ToString() ?? string.Empty; + } + + public object ConvertBack(object value) + { + // 역변환은 타입 정보가 필요하므로 기본 구현만 제공 + return value; + } + } + + /// + /// 컬렉션의 개수를 확인하는 컨버터 + /// 예: List(5개) → true, List(0개) → false + /// + public class CollectionHasItemsConverter : IValueConverter + { + public object Convert(object value) + { + if (value is System.Collections.ICollection collection) + return collection.Count > 0; + if (value is System.Collections.IEnumerable enumerable) + return enumerable.Cast().Any(); + return false; + } + + public object ConvertBack(object value) + { + return value; // 역변환 불가 + } + } + + /// + /// 색상 투명도 조절 컨버터 + /// 불린 값에 따라 알파값을 조절 + /// + public class AlphaConverter : IValueConverter + { + public float EnabledAlpha { get; set; } = 1.0f; + public float DisabledAlpha { get; set; } = 0.5f; + + public object Convert(object value) + { + if (value is bool boolValue) + return boolValue ? EnabledAlpha : DisabledAlpha; + return EnabledAlpha; + } + + public object ConvertBack(object value) + { + if (value is float alpha) + return Mathf.Approximately(alpha, EnabledAlpha); + return true; + } + } + + /// + /// InventoryCategoryType을 한국어로 변환하는 컨버터 + /// + public class InventoryCategoryConverter : IValueConverter + { + public object Convert(object value) + { + if (value is InventoryCategoryType category) + { + return category switch + { + InventoryCategoryType.Food => "음식", + InventoryCategoryType.Drink => "음료", + InventoryCategoryType.Ingredient => "재료", + InventoryCategoryType.Cookware => "조리도구", + InventoryCategoryType.Special => "특수", + _ => "전체" + }; + } + return "전체"; + } + + public object ConvertBack(object value) + { + if (value is string str) + { + return str switch + { + "음식" => InventoryCategoryType.Food, + "음료" => InventoryCategoryType.Drink, + "재료" => InventoryCategoryType.Ingredient, + "조리도구" => InventoryCategoryType.Cookware, + "특수" => InventoryCategoryType.Special, + _ => InventoryCategoryType.Food + }; + } + return InventoryCategoryType.Food; + } + } + + /// + /// 가격을 통화 형식으로 변환하는 컨버터 + /// 예: 1000 → "1,000원" + /// + public class CurrencyConverter : IValueConverter + { + public string CurrencySymbol { get; set; } = "원"; + + public object Convert(object value) + { + if (value is int intValue) + return $"{intValue:N0}{CurrencySymbol}"; + if (value is float floatValue) + return $"{floatValue:N0}{CurrencySymbol}"; + return $"0{CurrencySymbol}"; + } + + public object ConvertBack(object value) + { + if (value is string str && str.EndsWith(CurrencySymbol)) + { + var numberStr = str.Substring(0, str.Length - CurrencySymbol.Length).Replace(",", ""); + if (int.TryParse(numberStr, out var number)) + return number; + } + return 0; + } + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/Converters/CommonConverters.cs.meta b/Assets/_DDD/_Scripts/GameUi/New/Converters/CommonConverters.cs.meta new file mode 100644 index 000000000..ff3ec6bd8 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/Converters/CommonConverters.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7c7031b52d02cf3479d48a4cab3ca66e \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/Converters/IValueConverter.cs b/Assets/_DDD/_Scripts/GameUi/New/Converters/IValueConverter.cs new file mode 100644 index 000000000..16bb141ff --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/Converters/IValueConverter.cs @@ -0,0 +1,39 @@ +namespace DDD.MVVM +{ + /// + /// 값 변환기 인터페이스 + /// ViewModel의 데이터를 View에서 표시하기 적합한 형태로 변환 + /// + public interface IValueConverter + { + /// + /// ViewModel 값을 View 표시용으로 변환 + /// + /// 변환할 값 + /// 변환된 값 + object Convert(object value); + + /// + /// View 값을 ViewModel용으로 역변환 (선택적 구현) + /// + /// 역변환할 값 + /// 역변환된 값 + object ConvertBack(object value) + { + return value; // 기본 구현: 그대로 반환 + } + } + + /// + /// 간단한 값 변환기 인터페이스 (단방향 전용) + /// + public interface ISimpleConverter + { + /// + /// ViewModel 값을 View 표시용으로 변환 + /// + /// 변환할 값 + /// 변환된 값 + object Convert(object value); + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/Converters/IValueConverter.cs.meta b/Assets/_DDD/_Scripts/GameUi/New/Converters/IValueConverter.cs.meta new file mode 100644 index 000000000..56100971e --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/Converters/IValueConverter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a0229b137966b684f99c93cb7a488e48 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/MIGRATION_GUIDE.md b/Assets/_DDD/_Scripts/GameUi/New/MIGRATION_GUIDE.md new file mode 100644 index 000000000..5c57932cb --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/MIGRATION_GUIDE.md @@ -0,0 +1,321 @@ +# 기존 UI 시스템을 MVVM으로 마이그레이션 가이드 + +Unity 프로젝트에서 기존 UI 시스템(BaseUi, BasePopupUi, PopupUi)을 MVVM 패턴으로 점진적으로 전환하는 방법을 설명합니다. + +## 호환성 보장 + +### 1. 기존 UI 시스템은 그대로 유지됩니다 +- `BaseUi`, `BasePopupUi`, `PopupUi` 클래스들은 변경되지 않음 +- 기존 UI들은 계속해서 정상 동작 +- `UiManager`와 `PopupUiState`도 기존 방식 그대로 지원 + +### 2. 새로운 Integrated 클래스들 사용 +- `IntegratedBaseUi` : BaseUi + MVVM 기능 통합 +- `IntegratedBasePopupUi` : BasePopupUi + MVVM 기능 통합 +- `IntegratedPopupUi` : PopupUi + MVVM 기능 통합 + +## 마이그레이션 전략 + +### 단계 1: 점진적 전환 계획 수립 + +1. **우선순위 결정** + ``` + 높음: 복잡한 상태 관리가 필요한 UI (InventoryView, ShopView 등) + 중간: 중간 규모의 팝업 UI + 낮음: 간단한 확인/알림 다이얼로그 + ``` + +2. **전환 범위 설정** + - 한 번에 하나의 UI씩 전환 + - 관련된 ViewModel과 Service 함께 구현 + - 철저한 테스트 후 다음 UI 진행 + +### 단계 2: ViewModel 및 Service 구현 + +#### 기존 View 분석 +```csharp +// 기존 InventoryView +public class InventoryView : MonoBehaviour +{ + private InventoryCategoryType _currentCategory; + private List _items; + + public void UpdateCategoryView(InventoryCategoryType category) + { + _currentCategory = category; + // 복잡한 UI 업데이트 로직 + } +} +``` + +#### ViewModel로 분리 +```csharp +// 새로운 InventoryViewModel +public class InventoryViewModel : SimpleViewModel +{ + private InventoryCategoryType _currentCategory; + private List _visibleItems; + + public InventoryCategoryType CurrentCategory + { + get => _currentCategory; + set => SetField(ref _currentCategory, value); + } + + public void SetCategory(InventoryCategoryType category) + { + CurrentCategory = category; + UpdateVisibleItems(); // 비즈니스 로직 + } +} +``` + +#### Service 계층 분리 +```csharp +// 비즈니스 로직을 Service로 분리 +public class InventoryService : IUiService +{ + public IEnumerable FilterItems(InventoryCategoryType category) + { + // 복잡한 필터링 로직 + } +} +``` + +### 단계 3: View를 MVVM으로 전환 + +#### 기존 View 코드 +```csharp +public class InventoryView : MonoBehaviour, IEventHandler +{ + [SerializeField] private Transform _slotParent; + [SerializeField] private Text _categoryLabel; + + // 많은 상태 변수들과 복잡한 로직들... +} +``` + +#### MVVM View로 전환 +```csharp +public class MvvmInventoryView : MvvmBasePopupUi +{ + [SerializeField] private Transform _slotParent; + [SerializeField] private Text _categoryLabel; + + protected override void SetupBindings() + { + BindText(_categoryLabel, nameof(InventoryViewModel.CategoryDisplayText)); + // 간단한 바인딩 설정만 + } + + public void OnCategoryButtonClicked(int categoryIndex) + { + ViewModel.SetCategory((InventoryCategoryType)categoryIndex); + } +} +``` + +### 단계 4: 마이그레이션 체크리스트 + +#### ViewModel 체크리스트 +- [ ] SimpleViewModel 상속 +- [ ] 모든 상태를 속성으로 변환 +- [ ] SetField 사용한 PropertyChanged 알림 +- [ ] 계산된 속성 구현 +- [ ] 비즈니스 로직을 Service로 위임 + +#### View 체크리스트 +- [ ] 적절한 MVVM Base 클래스 상속 +- [ ] SetupBindings() 구현 +- [ ] UI 이벤트를 ViewModel 메서드 호출로 변경 +- [ ] 직접적인 UI 업데이트 코드 제거 +- [ ] HandleCustomPropertyChanged에서 복잡한 UI 처리 + +#### Service 체크리스트 +- [ ] IService 인터페이스 구현 +- [ ] 복잡한 비즈니스 로직 포함 +- [ ] 데이터 접근 로직 캡슐화 +- [ ] 테스트 가능한 구조 + +## 구체적인 마이그레이션 예시 + +### 예시 1: 간단한 팝업 UI + +#### Before (기존) +```csharp +public class SimpleDialogUi : BasePopupUi +{ + [SerializeField] private Text _messageText; + [SerializeField] private Button _confirmButton; + + public void ShowMessage(string message) + { + _messageText.text = message; + _confirmButton.onClick.AddListener(Close); + } +} +``` + +#### After (MVVM) +```csharp +// ViewModel +public class DialogViewModel : SimpleViewModel +{ + private string _message; + + public string Message + { + get => _message; + set => SetField(ref _message, value); + } +} + +// View +public class MvvmSimpleDialogUi : MvvmBasePopupUi +{ + [SerializeField] private Text _messageText; + [SerializeField] private Button _confirmButton; + + protected override void SetupBindings() + { + BindText(_messageText, nameof(DialogViewModel.Message)); + } + + protected override void Awake() + { + base.Awake(); + _confirmButton.onClick.AddListener(() => Close()); + } +} +``` + +### 예시 2: 복잡한 목록 UI + +#### Before (기존 InventoryView) +```csharp +public class InventoryView : MonoBehaviour +{ + private InventoryCategoryType _currentCategory; + private Dictionary _slotLookup = new(); + + public void UpdateCategoryView(InventoryCategoryType category) + { + _currentCategory = category; + + // 복잡한 필터링 로직 + foreach (var slot in _slotLookup.Values) + { + bool shouldShow = MatchesCategory(slot.Model, category); + slot.SetActive(shouldShow); + } + + // 정렬 로직 + // UI 업데이트 로직 + } + + private bool MatchesCategory(ItemViewModel model, InventoryCategoryType category) + { + // 복잡한 카테고리 매칭 로직 + } +} +``` + +#### After (MVVM) +```csharp +// ViewModel (상태 관리) +public class InventoryViewModel : SimpleViewModel +{ + private InventoryCategoryType _currentCategory; + private List _visibleItems; + + public InventoryCategoryType CurrentCategory + { + get => _currentCategory; + set => SetField(ref _currentCategory, value); + } + + public List VisibleItems + { + get => _visibleItems; + private set => SetField(ref _visibleItems, value); + } + + public string CategoryDisplayText => GetCategoryDisplayText(); + + public void SetCategory(InventoryCategoryType category) + { + CurrentCategory = category; + UpdateVisibleItems(); + OnPropertyChanged(nameof(CategoryDisplayText)); + } + + private void UpdateVisibleItems() + { + var filtered = _inventoryService.FilterItems(CurrentCategory, CurrentSortType); + VisibleItems = filtered.ToList(); + } +} + +// Service (비즈니스 로직) +public class InventoryService : IUiService +{ + public IEnumerable FilterItems(InventoryCategoryType category, InventorySortType sortType) + { + // 복잡한 필터링과 정렬 로직을 Service로 이동 + } +} + +// View (UI 바인딩만) +public class MvvmInventoryView : MvvmBasePopupUi +{ + [SerializeField] private Text _categoryLabel; + [SerializeField] private Transform _slotParent; + + protected override void SetupBindings() + { + BindText(_categoryLabel, nameof(InventoryViewModel.CategoryDisplayText)); + } + + protected override void HandleCustomPropertyChanged(string propertyName) + { + if (propertyName == nameof(InventoryViewModel.VisibleItems)) + { + UpdateItemSlots(); // 복잡한 UI 업데이트는 여전히 필요 + } + } +} +``` + +## 마이그레이션 주의사항 + +### 1. 기존 코드와의 의존성 +- 기존 UI를 참조하는 다른 코드들 확인 +- 이벤트 시스템과의 연동 유지 +- ScriptableObject 설정 파일들과의 호환성 + +### 2. 성능 고려사항 +- PropertyChanged 이벤트의 과도한 발생 방지 +- UI 업데이트 배칭 활용 +- 메모리 누수 방지 (이벤트 핸들러 정리) + +### 3. 테스트 전략 +- ViewModel 단위 테스트 작성 +- 기존 UI와 새 UI 동시 테스트 +- 사용자 시나리오 기반 통합 테스트 + +## 마이그레이션 우선순위 추천 + +### 1차 마이그레이션 (높은 우선순위) +- **InventoryView**: 복잡한 상태 관리, 필터링, 정렬 +- **ShopView**: 상품 목록, 카테고리, 구매 로직 +- **MenuView**: 메뉴 관리, 레시피 선택 + +### 2차 마이그레이션 (중간 우선순위) +- **SettingsView**: 다양한 설정 옵션들 +- **StatisticsView**: 데이터 표시와 계산 + +### 3차 마이그레이션 (낮은 우선순위) +- **SimpleDialogUi**: 간단한 확인 다이얼로그들 +- **NotificationUi**: 알림 팝업들 + +이 가이드를 따라 점진적으로 UI를 MVVM으로 전환하면, 기존 시스템의 안정성을 유지하면서도 새로운 아키텍처의 이점을 얻을 수 있습니다. \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/MIGRATION_GUIDE.md.meta b/Assets/_DDD/_Scripts/GameUi/New/MIGRATION_GUIDE.md.meta new file mode 100644 index 000000000..423f0dde4 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/MIGRATION_GUIDE.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4d3c9e8511cb71c42b53aa3543216518 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_DDD/_Scripts/GameUi/New/README.md b/Assets/_DDD/_Scripts/GameUi/New/README.md new file mode 100644 index 000000000..1b09e6a8e --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/README.md @@ -0,0 +1,278 @@ +# Unity MVVM 시스템 가이드 + +Unity에서 MVVM(Model-View-ViewModel) 패턴을 구현한 시스템입니다. Attribute 기반 데이터 바인딩과 `nameof`를 활용한 타입 안전한 구조를 제공합니다. + +## 폴더 구조 + +``` +GameUi/New/ +├── ViewModels/ # ViewModel 클래스들 +│ ├── Base/ # 기본 ViewModel 클래스 +│ │ └── SimpleViewModel.cs +│ └── InventoryViewModel.cs # 예시 ViewModel +├── Views/ # View 클래스들 +│ └── Base/ +│ └── AutoBindView.cs # 자동 바인딩 View 기본 클래스 +├── Services/ # 서비스 계층 클래스들 +│ ├── IService.cs # 서비스 인터페이스 +│ └── InventoryService.cs # 예시 서비스 +├── Utils/ # 유틸리티 클래스들 +│ ├── BindToAttribute.cs # 바인딩 Attribute +│ └── BindingContext.cs # 바인딩 컨텍스트 +└── Converters/ # 값 변환기들 + ├── IValueConverter.cs # 컨버터 인터페이스 + └── CommonConverters.cs # 공통 컨버터들 +``` + +## 핵심 클래스 + +### 1. SimpleViewModel +- ViewModel의 기본 클래스 +- `INotifyPropertyChanged` 구현 +- 속성 변경 알림 자동 처리 +- 배치 업데이트 지원 + +### 2. IntegratedBaseUi +- UI의 기본 클래스 (BaseUi + MVVM 기능 통합) +- Attribute 기반 자동 바인딩 +- ViewModel과 UI 요소 자동 연결 + +### 3. BindToAttribute +- UI 요소를 ViewModel 속성에 바인딩 +- `nameof()` 사용으로 타입 안전성 보장 +- 컨버터 지원 + +## 사용법 + +### 1. ViewModel 생성 + +```csharp +namespace DDD.MVVM +{ + public class MyViewModel : SimpleViewModel + { + private string _title = "기본 제목"; + private int _count = 0; + private bool _isVisible = true; + + public string Title + { + get => _title; + set => SetField(ref _title, value); + } + + public int Count + { + get => _count; + set => SetField(ref _count, value); + } + + public bool IsVisible + { + get => _isVisible; + set => SetField(ref _isVisible, value); + } + + public string CountText => $"개수: {Count}"; + + public void IncrementCount() + { + Count++; + OnPropertyChanged(nameof(CountText)); + } + } +} +``` + +### 2. View 생성 + +```csharp +namespace DDD +{ + public class MyView : IntegratedBaseUi + { + [SerializeField, BindTo(nameof(MyViewModel.Title))] + private Text _titleText; + + [SerializeField, BindTo(nameof(MyViewModel.CountText))] + private Text _countText; + + [SerializeField, BindTo(nameof(MyViewModel.IsVisible))] + private GameObject _panel; + + // UI 이벤트 핸들러 + public void OnIncrementButtonClicked() + { + ViewModel.IncrementCount(); + } + + public void OnToggleVisibilityClicked() + { + ViewModel.IsVisible = !ViewModel.IsVisible; + } + } +} +``` + +### 3. 컨버터 사용 + +```csharp +[SerializeField, BindTo(nameof(MyViewModel.IsVisible), typeof(InvertBoolConverter))] +private GameObject _hiddenPanel; + +[SerializeField, BindTo(nameof(MyViewModel.Count), typeof(ItemCountConverter))] +private Text _formattedCountText; +``` + +## 기존 InventoryView 변환 예시 + +### 기존 코드 (InventoryView) +```csharp +public class InventoryView : MonoBehaviour +{ + private InventoryCategoryType _currentCategory = InventoryCategoryType.Food; + + public void UpdateCategoryView(InventoryCategoryType category) + { + _currentCategory = category; + // 복잡한 UI 업데이트 로직... + } +} +``` + +### MVVM 변환 후 + +**InventoryViewModel.cs** +```csharp +namespace DDD.MVVM +{ + public class InventoryViewModel : SimpleViewModel + { + private InventoryCategoryType _currentCategory = InventoryCategoryType.Food; + + public InventoryCategoryType CurrentCategory + { + get => _currentCategory; + set => SetField(ref _currentCategory, value); + } + + public string CategoryDisplayText => CurrentCategory switch + { + InventoryCategoryType.Food => "음식", + InventoryCategoryType.Drink => "음료", + _ => "전체" + }; + + public void SetCategory(InventoryCategoryType category) + { + CurrentCategory = category; + OnPropertyChanged(nameof(CategoryDisplayText)); + } + } +} +``` + +**InventoryView.cs** +```csharp +namespace DDD.MVVM +{ + public class InventoryView : AutoBindView + { + [SerializeField, BindTo(nameof(InventoryViewModel.CategoryDisplayText))] + private Text _categoryLabel; + + public void OnCategoryButtonClicked(int categoryIndex) + { + ViewModel.SetCategory((InventoryCategoryType)categoryIndex); + } + } +} +``` + +## 장점 + +### 1. 타입 안전성 +- `nameof()` 사용으로 컴파일 타임 검증 +- 속성명 변경 시 자동 업데이트 +- IDE IntelliSense 지원 + +### 2. Unity 통합성 +- Inspector에서 바인딩 확인 +- MonoBehaviour 패턴 유지 +- 기존 Unity 워크플로우와 호환 + +### 3. 유지보수성 +- View와 비즈니스 로직 분리 +- 테스트 가능한 ViewModel +- 재사용 가능한 컴포넌트 + +### 4. 개발 생산성 +- 자동 바인딩으로 보일러플레이트 코드 감소 +- 데이터 변경 시 UI 자동 업데이트 +- 일관된 개발 패턴 + +## 컨버터 목록 + +### 기본 컨버터들 +- `InvertBoolConverter`: 불린 값 반전 +- `ItemCountConverter`: 숫자를 "아이템 수: N" 형식으로 변환 +- `InventoryCategoryConverter`: 카테고리 열거형을 한국어로 변환 +- `CurrencyConverter`: 숫자를 통화 형식으로 변환 +- `PercentageConverter`: 소수를 백분율로 변환 + +### 커스텀 컨버터 생성 +```csharp +public class CustomConverter : IValueConverter +{ + public object Convert(object value) + { + // 변환 로직 구현 + return convertedValue; + } + + public object ConvertBack(object value) + { + // 역변환 로직 구현 (선택사항) + return originalValue; + } +} +``` + +## 베스트 프랙티스 + +### 1. ViewModel 설계 +- 단일 책임 원칙 준수 +- UI 관련 Unity API 직접 사용 금지 +- 계산된 속성 적극 활용 +- 이벤트를 통한 느슨한 결합 + +### 2. 바인딩 설정 +- `nameof()` 사용 필수 +- 적절한 컨버터 활용 +- 복잡한 로직은 ViewModel에서 처리 + +### 3. 성능 고려사항 +- 배치 업데이트 활용 +- 불필요한 PropertyChanged 이벤트 방지 +- 컬렉션 변경 시 효율적인 업데이트 + +## 마이그레이션 가이드 + +### 단계별 적용 방법 + +#### 1단계: 기존 View에서 ViewModel 분리 +1. View의 상태 변수들을 ViewModel로 이동 +2. 비즈니스 로직을 Service로 분리 +3. UI 업데이트 로직을 바인딩으로 대체 + +#### 2단계: 자동 바인딩 적용 +1. `AutoBindView` 상속 +2. UI 요소에 `BindTo` Attribute 추가 +3. 수동 UI 업데이트 코드 제거 + +#### 3단계: 최적화 및 리팩토링 +1. 컨버터 활용으로 로직 단순화 +2. 계산된 속성으로 중복 제거 +3. 이벤트 시스템과 통합 + +이 MVVM 시스템을 통해 Unity UI 개발의 생산성과 유지보수성을 크게 향상시킬 수 있습니다. \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/README.md.meta b/Assets/_DDD/_Scripts/GameUi/New/README.md.meta new file mode 100644 index 000000000..392b933fe --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5d5b08d15b2e43f4698c0f24977c2582 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_DDD/_Scripts/GameUi/New/Services.meta b/Assets/_DDD/_Scripts/GameUi/New/Services.meta new file mode 100644 index 000000000..4d31e16a4 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/Services.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7196d7444953d844c9b0e893fb3e958a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_DDD/_Scripts/GameUi/New/Services/IService.cs b/Assets/_DDD/_Scripts/GameUi/New/Services/IService.cs new file mode 100644 index 000000000..4162b4604 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/Services/IService.cs @@ -0,0 +1,62 @@ +namespace DDD.MVVM +{ + /// + /// 서비스 계층의 기본 인터페이스 + /// MVVM 패턴에서 비즈니스 로직을 담당하는 서비스들의 공통 인터페이스 + /// + public interface IService + { + /// + /// 서비스 초기화 + /// + void Initialize(); + + /// + /// 서비스 종료 시 리소스 정리 + /// + void Cleanup(); + } + + /// + /// 데이터 서비스 인터페이스 + /// 데이터 CRUD 작업을 담당하는 서비스들의 기본 인터페이스 + /// + /// 관리할 데이터 타입 + public interface IDataService : IService + { + /// + /// 데이터 로드 + /// + void LoadData(); + + /// + /// 데이터 저장 + /// + void SaveData(); + + /// + /// 특정 ID의 데이터 가져오기 + /// + /// 데이터 ID + /// 데이터 객체 또는 null + TData GetData(string id); + + /// + /// 모든 데이터 가져오기 + /// + /// 모든 데이터 컬렉션 + System.Collections.Generic.IEnumerable GetAllData(); + } + + /// + /// UI 서비스 인터페이스 + /// UI 관련 비즈니스 로직을 담당하는 서비스들의 기본 인터페이스 + /// + public interface IUiService : IService + { + /// + /// UI 상태 업데이트 + /// + void UpdateUiState(); + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/Services/IService.cs.meta b/Assets/_DDD/_Scripts/GameUi/New/Services/IService.cs.meta new file mode 100644 index 000000000..ff2af3d05 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/Services/IService.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7f55f2be524632444aeebeb8b8965efc \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/Services/InventoryService.cs b/Assets/_DDD/_Scripts/GameUi/New/Services/InventoryService.cs new file mode 100644 index 000000000..ca8212a86 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/Services/InventoryService.cs @@ -0,0 +1,167 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace DDD.MVVM +{ + /// + /// 인벤토리 관련 비즈니스 로직을 담당하는 서비스 + /// 기존 InventoryView의 로직을 서비스 계층으로 분리 + /// + public class InventoryService : IUiService + { + private RestaurantManagementData _restaurantManagementData; + private RestaurantManagementState _restaurantManagementState; + + /// + /// 서비스 초기화 + /// + public void Initialize() + { + _restaurantManagementState = RestaurantState.Instance.ManagementState; + _restaurantManagementData = RestaurantData.Instance.ManagementData; + + Debug.Assert(_restaurantManagementData != null, "RestaurantManagementData is null"); + Debug.Assert(_restaurantManagementState != null, "RestaurantManagementState is null"); + } + + /// + /// 서비스 정리 + /// + public void Cleanup() + { + // 필요한 경우 리소스 정리 + } + + /// + /// UI 상태 업데이트 + /// + public void UpdateUiState() + { + // UI 상태 업데이트 로직 + } + + /// + /// 인벤토리 아이템 목록 가져오기 + /// + /// 인벤토리 아이템 ViewModel 목록 + public List GetInventoryItems() + { + return ItemViewModelFactory.CreateRestaurantManagementInventoryItem(); + } + + /// + /// 카테고리와 정렬 타입에 따라 필터링된 아이템 목록 가져오기 + /// + /// 카테고리 필터 + /// 정렬 타입 + /// 필터링 및 정렬된 아이템 목록 + public IEnumerable FilterItems(InventoryCategoryType category, InventorySortType sortType) + { + var items = GetInventoryItems(); + + // 카테고리 필터링 + var filtered = items.Where(item => MatchesCategory(item, category)); + + // 정렬 + return ApplySort(filtered, sortType); + } + + /// + /// 오늘의 메뉴에 등록되지 않은 아이템들만 필터링 + /// + /// 원본 아이템 목록 + /// 오늘의 메뉴에 등록되지 않은 아이템들 + public IEnumerable FilterOutTodayMenuItems(IEnumerable items) + { + return items.Where(item => + !(item.ItemType == ItemType.Recipe && _restaurantManagementState.IsContainTodayMenu(item.Id))); + } + + /// + /// 아이템이 특정 카테고리와 일치하는지 확인 + /// + /// 확인할 아이템 + /// 카테고리 + /// 일치 여부 + public bool MatchesCategory(ItemViewModel item, InventoryCategoryType category) + { + switch (category) + { + case InventoryCategoryType.Food: + if (item.ItemType != ItemType.Recipe) return false; + return DataManager.Instance.GetDataSo() + .TryGetDataById(item.Id, out var foodRecipe) && foodRecipe.RecipeType == RecipeType.FoodRecipe; + + case InventoryCategoryType.Drink: + if (item.ItemType != ItemType.Recipe) return false; + return DataManager.Instance.GetDataSo() + .TryGetDataById(item.Id, out var drinkRecipe) && drinkRecipe.RecipeType == RecipeType.DrinkRecipe; + + case InventoryCategoryType.Ingredient: + return item.ItemType == ItemType.Ingredient; + + case InventoryCategoryType.Cookware: + return DataManager.Instance.GetDataSo() + .TryGetDataById(item.Id, out var cookwareData); + + case InventoryCategoryType.Special: + return false; // 특수 아이템 로직 추가 필요 + + default: + return false; + } + } + + /// + /// 정렬 타입에 따라 아이템 목록 정렬 + /// + /// 정렬할 아이템 목록 + /// 정렬 타입 + /// 정렬된 아이템 목록 + public IEnumerable ApplySort(IEnumerable items, InventorySortType sortType) + { + return sortType switch + { + InventorySortType.NameAscending => items.OrderByDescending(item => item.HasItem).ThenBy(item => item.DisplayName), + InventorySortType.NameDescending => items.OrderByDescending(item => item.HasItem).ThenByDescending(item => item.DisplayName), + InventorySortType.QuantityAscending => items.OrderByDescending(item => item.HasItem).ThenBy(item => item.Count), + InventorySortType.QuantityDescending => items.OrderByDescending(item => item.HasItem).ThenByDescending(item => item.Count), + InventorySortType.None => items.OrderBy(item => item.Id), + _ => items + }; + } + + /// + /// 아이템이 아이템을 보유하고 있는지 확인 + /// + /// 확인할 아이템 목록 + /// 아이템을 보유한 목록 + public IEnumerable GetItemsWithStock(IEnumerable items) + { + return items.Where(item => item.HasItem); + } + + /// + /// 카테고리별 아이템 개수 가져오기 + /// + /// 카테고리 + /// 해당 카테고리의 보유 아이템 수 + public int GetItemCountByCategory(InventoryCategoryType category) + { + var filteredItems = FilterItems(category, InventorySortType.None); + var itemsWithStock = GetItemsWithStock(filteredItems); + return itemsWithStock.Count(); + } + + /// + /// 전체 인벤토리에서 보유한 아이템 총 개수 + /// + /// 보유 아이템 총 개수 + public int GetTotalItemCount() + { + var allItems = GetInventoryItems(); + return GetItemsWithStock(allItems).Count(); + } + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/Services/InventoryService.cs.meta b/Assets/_DDD/_Scripts/GameUi/New/Services/InventoryService.cs.meta new file mode 100644 index 000000000..130fe7d1b --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/Services/InventoryService.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 682535d5577b54c499b1f52b4177d202 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/Utils.meta b/Assets/_DDD/_Scripts/GameUi/New/Utils.meta new file mode 100644 index 000000000..a9681e78a --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/Utils.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0d5bef5ee11a4064e84fb4e318be2bee +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_DDD/_Scripts/GameUi/New/Utils/BindToAttribute.cs b/Assets/_DDD/_Scripts/GameUi/New/Utils/BindToAttribute.cs new file mode 100644 index 000000000..cdf9580c2 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/Utils/BindToAttribute.cs @@ -0,0 +1,114 @@ +using System; + +namespace DDD.MVVM +{ + /// + /// UI 요소를 ViewModel 속성에 바인딩하기 위한 Attribute + /// Inspector에서 바인딩 정보를 시각적으로 확인할 수 있도록 지원 + /// + [System.AttributeUsage(System.AttributeTargets.Field)] + public class BindToAttribute : System.Attribute + { + /// + /// 바인딩할 ViewModel 속성의 경로 (nameof 사용 권장) + /// + public string PropertyPath { get; } + + /// + /// 값 변환기 타입 (선택사항) + /// + public System.Type ConverterType { get; } + + /// + /// 바인딩 Attribute 생성자 + /// + /// 바인딩할 속성 경로 (nameof 사용 권장) + /// 값 변환기 타입 (선택사항) + public BindToAttribute(string propertyPath, System.Type converterType = null) + { + PropertyPath = propertyPath; + ConverterType = converterType; + } + } + + // /// + // /// 타입 안전한 바인딩 Attribute (제네릭 버전) + // /// 특정 ViewModel 타입에 대한 바인딩을 명시적으로 지정 + // /// + // /// 바인딩할 ViewModel 타입 + // [System.AttributeUsage(System.AttributeTargets.Field)] + // public class BindToAttribute : System.Attribute where TViewModel : class + // { + // /// + // /// 바인딩할 ViewModel 속성의 경로 + // /// + // public string PropertyPath { get; } + // + // /// + // /// 값 변환기 타입 (선택사항) + // /// + // public System.Type ConverterType { get; } + // + // /// + // /// 타입 안전한 바인딩 Attribute 생성자 + // /// + // /// 바인딩할 속성 경로 + // /// 값 변환기 타입 (선택사항) + // public BindToAttribute(string propertyPath, System.Type converterType = null) + // { + // PropertyPath = propertyPath; + // ConverterType = converterType; + // } + // } + + /// + /// 컬렉션 바인딩을 위한 Attribute + /// 동적으로 생성되는 UI 요소들을 컬렉션에 바인딩 + /// + [System.AttributeUsage(System.AttributeTargets.Field)] + public class BindCollectionAttribute : System.Attribute + { + /// + /// 바인딩할 컬렉션 속성의 경로 + /// + public string PropertyPath { get; } + + /// + /// 아이템 프리팹의 필드명 또는 속성명 + /// + public string ItemPrefabReference { get; } + + /// + /// 컬렉션 바인딩 Attribute 생성자 + /// + /// 바인딩할 컬렉션 속성 경로 + /// 아이템 프리팹 참조 + public BindCollectionAttribute(string propertyPath, string itemPrefabReference = null) + { + PropertyPath = propertyPath; + ItemPrefabReference = itemPrefabReference; + } + } + + /// + /// 커맨드 바인딩을 위한 Attribute + /// 버튼 클릭 등의 이벤트를 ViewModel 메서드에 바인딩 + /// + [System.AttributeUsage(System.AttributeTargets.Field)] + public class BindCommandAttribute : System.Attribute + { + /// + /// 바인딩할 ViewModel 메서드 이름 + /// + public string MethodName { get; } + + /// + /// 커맨드 바인딩 Attribute 생성자 + /// + /// 바인딩할 메서드 이름 + public BindCommandAttribute(string methodName) + { + MethodName = methodName; + } + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/Utils/BindToAttribute.cs.meta b/Assets/_DDD/_Scripts/GameUi/New/Utils/BindToAttribute.cs.meta new file mode 100644 index 000000000..a23282769 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/Utils/BindToAttribute.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 279b3238907a3564f842594af646eab7 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/Utils/BindingContext.cs b/Assets/_DDD/_Scripts/GameUi/New/Utils/BindingContext.cs new file mode 100644 index 000000000..85904a96e --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/Utils/BindingContext.cs @@ -0,0 +1,259 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Reflection; +using UnityEngine; +using UnityEngine.UI; + +namespace DDD.MVVM +{ + /// + /// 바인딩 타겟 인터페이스 + /// UI 요소와 ViewModel 속성을 연결하는 역할 + /// + public interface IBindingTarget + { + /// + /// 바인딩된 속성의 경로 + /// + string PropertyPath { get; } + + /// + /// UI 요소의 값을 업데이트 + /// + /// 새로운 값 + void UpdateValue(object value); + } + + /// + /// Text 컴포넌트에 대한 바인딩 타겟 + /// + 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; + } + } + + /// + /// Image 컴포넌트에 대한 바인딩 타겟 + /// + 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; + } + } + + /// + /// GameObject의 활성화 상태에 대한 바인딩 타겟 + /// + 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); + } + } + + /// + /// Slider 컴포넌트에 대한 바인딩 타겟 + /// + 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; + } + } + + /// + /// 바인딩 컨텍스트 - ViewModel과 View 간의 데이터 바인딩을 관리 + /// + public class BindingContext + { + private readonly Dictionary> _bindings = new(); + private readonly Dictionary _converters = new(); + private INotifyPropertyChanged _dataContext; + + /// + /// 데이터 컨텍스트 (ViewModel) 설정 + /// + /// 바인딩할 ViewModel + public void SetDataContext(INotifyPropertyChanged dataContext) + { + if (_dataContext != null) + _dataContext.PropertyChanged -= OnPropertyChanged; + + _dataContext = dataContext; + + if (_dataContext != null) + { + _dataContext.PropertyChanged += OnPropertyChanged; + RefreshAllBindings(); + } + } + + /// + /// 속성 바인딩 추가 + /// + /// 바인딩할 속성 경로 + /// 바인딩 타겟 + /// 값 변환기 (선택사항) + public void Bind(string propertyPath, IBindingTarget target, IValueConverter converter = null) + { + if (!_bindings.ContainsKey(propertyPath)) + _bindings[propertyPath] = new List(); + + _bindings[propertyPath].Add(target); + + if (converter != null) + _converters[propertyPath] = converter; + + // 즉시 초기값 설정 + UpdateBinding(propertyPath); + } + + /// + /// 속성 변경 이벤트 핸들러 + /// + private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) + { + UpdateBinding(e.PropertyName); + } + + /// + /// 특정 속성의 바인딩 업데이트 + /// + /// 업데이트할 속성 경로 + private void UpdateBinding(string propertyPath) + { + if (!_bindings.ContainsKey(propertyPath)) return; + + var value = GetPropertyValue(propertyPath); + + // 컨버터 적용 + if (_converters.TryGetValue(propertyPath, out var converter)) + value = converter.Convert(value); + + foreach (var target in _bindings[propertyPath]) + { + target.UpdateValue(value); + } + } + + /// + /// 속성 값 가져오기 (리플렉션 사용) + /// + /// 속성 경로 + /// 속성 값 + private object GetPropertyValue(string propertyPath) + { + if (_dataContext == null) return null; + + // 중첩 속성 지원 (예: "ItemData.Name") + var properties = propertyPath.Split('.'); + object current = _dataContext; + + foreach (var prop in properties) + { + if (current == null) return null; + + var property = current.GetType().GetProperty(prop); + current = property?.GetValue(current); + } + + return current; + } + + /// + /// 모든 바인딩 새로고침 + /// + private void RefreshAllBindings() + { + foreach (var propertyPath in _bindings.Keys) + { + UpdateBinding(propertyPath); + } + } + + /// + /// 리소스 정리 + /// + public void Dispose() + { + if (_dataContext != null) + _dataContext.PropertyChanged -= OnPropertyChanged; + + _bindings.Clear(); + _converters.Clear(); + } + } + + /// + /// 속성 경로 캐시 - 성능 최적화를 위한 리플렉션 결과 캐싱 + /// + public static class PropertyPathCache + { + private static readonly Dictionary> _cache = new(); + + /// + /// 캐시된 PropertyInfo 가져오기 + /// + /// 타입 + /// 속성 이름 + /// PropertyInfo + public static PropertyInfo GetProperty(Type type, string propertyName) + { + if (!_cache.ContainsKey(type)) + _cache[type] = new Dictionary(); + + if (!_cache[type].ContainsKey(propertyName)) + _cache[type][propertyName] = type.GetProperty(propertyName); + + return _cache[type][propertyName]; + } + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/Utils/BindingContext.cs.meta b/Assets/_DDD/_Scripts/GameUi/New/Utils/BindingContext.cs.meta new file mode 100644 index 000000000..868e1b8a4 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/Utils/BindingContext.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: bff0e2748b37ec54a982f4bc8568d2fd \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/Utils/InputPhase.cs b/Assets/_DDD/_Scripts/GameUi/New/Utils/InputPhase.cs new file mode 100644 index 000000000..0fbbc29b5 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/Utils/InputPhase.cs @@ -0,0 +1,24 @@ +namespace DDD.MVVM +{ + /// + /// 입력 처리 단계를 나타내는 열거형 + /// 매직 스트링을 제거하고 타입 안전성을 제공 + /// + public enum InputPhaseType + { + /// + /// 입력이 시작됨 + /// + Started, + + /// + /// 입력이 수행됨 + /// + Performed, + + /// + /// 입력이 취소됨 + /// + Canceled + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/Utils/InputPhase.cs.meta b/Assets/_DDD/_Scripts/GameUi/New/Utils/InputPhase.cs.meta new file mode 100644 index 000000000..1152c6f08 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/Utils/InputPhase.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5c9b66b101f99e1458e01b9e0653935f \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/ViewModels.meta b/Assets/_DDD/_Scripts/GameUi/New/ViewModels.meta new file mode 100644 index 000000000..f30e4659d --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/ViewModels.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a394c2737bee3d645a8c74d1449d7176 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_DDD/_Scripts/GameUi/New/ViewModels/Base.meta b/Assets/_DDD/_Scripts/GameUi/New/ViewModels/Base.meta new file mode 100644 index 000000000..e97d0f1fa --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/ViewModels/Base.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c5e7000232c822247a01c4b7c288a6f4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_DDD/_Scripts/GameUi/New/ViewModels/Base/SimpleViewModel.cs b/Assets/_DDD/_Scripts/GameUi/New/ViewModels/Base/SimpleViewModel.cs new file mode 100644 index 000000000..4f7310265 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/ViewModels/Base/SimpleViewModel.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace DDD.MVVM +{ + public abstract class SimpleViewModel : MonoBehaviour, INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void Awake() { } + protected virtual void Start() { } + protected virtual void OnDestroy() { } + public virtual void Initialize() { } + public virtual void Cleanup() { } + + /// + /// PropertyChanged 이벤트 발생 + /// + /// 변경된 속성 이름 (자동으로 설정됨) + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + /// + /// 필드 값 변경 및 PropertyChanged 이벤트 발생 + /// + /// 필드 타입 + /// 변경할 필드 참조 + /// 새로운 값 + /// 속성 이름 (자동으로 설정됨) + /// 값이 실제로 변경되었는지 여부 + protected bool SetField(ref T field, T value, [CallerMemberName] string propertyName = null) + { + if (EqualityComparer.Default.Equals(field, value)) return false; + field = value; + OnPropertyChanged(propertyName); + return true; + } + + /// + /// 배치 업데이트를 위한 플래그 + /// + private bool _isUpdating; + + /// + /// 배치 업데이트 중 보류된 알림들 + /// + private readonly HashSet _pendingNotifications = new(); + + /// + /// 배치 업데이트 시작 - 여러 속성 변경을 한 번에 처리 + /// + protected void BeginUpdate() => _isUpdating = true; + + /// + /// 배치 업데이트 종료 - 보류된 모든 알림을 처리 + /// + protected void EndUpdate() + { + _isUpdating = false; + if (_pendingNotifications.Count > 0) + { + foreach (var prop in _pendingNotifications) + OnPropertyChanged(prop); + _pendingNotifications.Clear(); + } + } + + /// + /// PropertyChanged 이벤트 발생 (배치 업데이트 고려) + /// + protected virtual void OnPropertyChangedInternal([CallerMemberName] string propertyName = null) + { + if (_isUpdating) + { + _pendingNotifications.Add(propertyName); + } + else + { + OnPropertyChanged(propertyName); + } + } + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/ViewModels/Base/SimpleViewModel.cs.meta b/Assets/_DDD/_Scripts/GameUi/New/ViewModels/Base/SimpleViewModel.cs.meta new file mode 100644 index 000000000..4b3d252df --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/ViewModels/Base/SimpleViewModel.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2996e8c7ea7282e4685a79943083c29a \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/ViewModels/InventoryViewModel.cs b/Assets/_DDD/_Scripts/GameUi/New/ViewModels/InventoryViewModel.cs new file mode 100644 index 000000000..dea265beb --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/ViewModels/InventoryViewModel.cs @@ -0,0 +1,274 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using UnityEngine; + +namespace DDD.MVVM +{ + /// + /// 인벤토리 UI의 ViewModel + /// 기존 InventoryView의 상태와 로직을 MVVM 패턴으로 분리 + /// + public class InventoryViewModel : SimpleViewModel, IEventHandler, + IEventHandler, IEventHandler + { + [Header("Services")] + [SerializeField] private InventoryService _inventoryService; + + // Private fields for properties + private InventoryCategoryType _currentCategory = InventoryCategoryType.Food; + private InventorySortType _currentSortType = InventorySortType.None; + private List _allItems = new(); + private List _visibleItems = new(); + private ItemViewModel _selectedItem; + + /// + /// 현재 선택된 카테고리 + /// + public InventoryCategoryType CurrentCategory + { + get => _currentCategory; + set + { + if (SetField(ref _currentCategory, value)) + { + UpdateVisibleItems(); + // 연관된 계산된 속성들도 알림 + OnPropertyChanged(nameof(CategoryDisplayText)); + } + } + } + + /// + /// 현재 정렬 타입 + /// + public InventorySortType CurrentSortType + { + get => _currentSortType; + set + { + if (SetField(ref _currentSortType, value)) + { + UpdateVisibleItems(); + } + } + } + + /// + /// 모든 인벤토리 아이템 목록 + /// + public List AllItems + { + get => _allItems; + private set => SetField(ref _allItems, value); + } + + /// + /// 현재 필터링된 보이는 아이템 목록 + /// + public List VisibleItems + { + get => _visibleItems; + private set => SetField(ref _visibleItems, value); + } + + /// + /// 현재 선택된 아이템 + /// + public ItemViewModel SelectedItem + { + get => _selectedItem; + set => SetField(ref _selectedItem, value); + } + + // Computed Properties (계산된 속성들) + + /// + /// 카테고리 표시 텍스트 (한국어) + /// + public string CategoryDisplayText => CurrentCategory switch + { + InventoryCategoryType.Food => "음식", + InventoryCategoryType.Drink => "음료", + InventoryCategoryType.Ingredient => "재료", + InventoryCategoryType.Cookware => "조리도구", + InventoryCategoryType.Special => "특수", + _ => "전체" + }; + + /// + /// 보이는 아이템들 중 실제 보유한 아이템이 있는지 확인 + /// + public bool HasVisibleItems => VisibleItems.Any(item => item.HasItem); + + /// + /// 보이는 아이템들 중 실제 보유한 아이템 개수 + /// + public int VisibleItemCount => VisibleItems.Count(item => item.HasItem); + + /// + /// 아이템 개수 표시 텍스트 + /// + public string ItemCountText => $"아이템 수: {VisibleItemCount}"; + + /// + /// 빈 목록 메시지 표시 여부 + /// + public bool ShowEmptyMessage => !HasVisibleItems; + + /// + /// 첫 번째 유효한 아이템 (UI 포커스용) + /// + public ItemViewModel FirstValidItem => VisibleItems.FirstOrDefault(item => item.HasItem); + + protected override void Awake() + { + base.Awake(); + + if (_inventoryService == null) + _inventoryService = new InventoryService(); + } + + public override void Initialize() + { + base.Initialize(); + + _inventoryService.Initialize(); + LoadInventoryData(); + RegisterEvents(); + } + + public override void Cleanup() + { + base.Cleanup(); + + UnregisterEvents(); + _inventoryService?.Cleanup(); + } + + /// + /// 이벤트 등록 + /// + private void RegisterEvents() + { + EventBus.Register(this); + EventBus.Register(this); + EventBus.Register(this); + } + + /// + /// 이벤트 등록 해제 + /// + private void UnregisterEvents() + { + EventBus.Unregister(this); + EventBus.Unregister(this); + EventBus.Unregister(this); + } + + /// + /// 인벤토리 데이터 로드 + /// + private void LoadInventoryData() + { + AllItems = _inventoryService.GetInventoryItems(); + UpdateVisibleItems(); + } + + /// + /// 카테고리 설정 (UI에서 호출) + /// + /// 새 카테고리 + public void SetCategory(InventoryCategoryType category) + { + CurrentCategory = category; + } + + /// + /// 정렬 타입 설정 (UI에서 호출) + /// + /// 새 정렬 타입 + public void SetSortType(InventorySortType sortType) + { + CurrentSortType = sortType; + } + + /// + /// 보이는 아이템 목록 업데이트 + /// + private void UpdateVisibleItems() + { + BeginUpdate(); // 배치 업데이트 시작 + + // 서비스에서 필터링된 아이템 가져오기 + var filteredItems = _inventoryService.FilterItems(CurrentCategory, CurrentSortType); + + // 오늘의 메뉴에 등록된 아이템 제외 + var finalItems = _inventoryService.FilterOutTodayMenuItems(filteredItems); + + VisibleItems = finalItems.ToList(); + + // 관련된 계산된 속성들 알림 + OnPropertyChanged(nameof(HasVisibleItems)); + OnPropertyChanged(nameof(VisibleItemCount)); + OnPropertyChanged(nameof(ItemCountText)); + OnPropertyChanged(nameof(ShowEmptyMessage)); + OnPropertyChanged(nameof(FirstValidItem)); + + EndUpdate(); // 배치 업데이트 종료 + } + + /// + /// 아이템 선택 + /// + /// 선택할 아이템 + public void SelectItem(ItemViewModel item) + { + SelectedItem = item; + } + + /// + /// 특정 카테고리의 아이템 개수 가져오기 + /// + /// 카테고리 + /// 아이템 개수 + public int GetItemCountForCategory(InventoryCategoryType category) + { + return _inventoryService.GetItemCountByCategory(category); + } + + // Event Handlers + + public void Invoke(InventoryChangedEvent evt) + { + LoadInventoryData(); + } + + public void Invoke(TodayMenuAddedEvent evt) + { + UpdateVisibleItems(); + } + + public void Invoke(TodayMenuRemovedEvent evt) + { + UpdateVisibleItems(); + } + + // Unity Editor에서 테스트용 메서드들 +#if UNITY_EDITOR + [ContextMenu("Test Category Change")] + private void TestCategoryChange() + { + var nextCategory = (InventoryCategoryType)(((int)CurrentCategory + 1) % 5); + SetCategory(nextCategory); + } + + [ContextMenu("Test Sort Change")] + private void TestSortChange() + { + var nextSort = (InventorySortType)(((int)CurrentSortType + 1) % 5); + SetSortType(nextSort); + } +#endif + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/ViewModels/InventoryViewModel.cs.meta b/Assets/_DDD/_Scripts/GameUi/New/ViewModels/InventoryViewModel.cs.meta new file mode 100644 index 000000000..74c89acef --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/ViewModels/InventoryViewModel.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 321a552f0b0773b4db1ab3dc95217719 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/Views.meta b/Assets/_DDD/_Scripts/GameUi/New/Views.meta new file mode 100644 index 000000000..dac16c089 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/Views.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 36adabeb3767cf64684116798ff0ef30 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_DDD/_Scripts/GameUi/New/Views/Base.meta b/Assets/_DDD/_Scripts/GameUi/New/Views/Base.meta new file mode 100644 index 000000000..368529dc5 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/Views/Base.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 76b62bf64be94ab4bb1e4b610da29fa4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_DDD/_Scripts/GameUi/New/Views/Base/AutoBindView.cs b/Assets/_DDD/_Scripts/GameUi/New/Views/Base/AutoBindView.cs new file mode 100644 index 000000000..2298041d0 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/Views/Base/AutoBindView.cs @@ -0,0 +1,210 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using UnityEngine; +using UnityEngine.UI; + +namespace DDD.MVVM +{ + /// + /// 자동 바인딩을 지원하는 View 기본 클래스 + /// Attribute를 통해 설정된 바인딩을 자동으로 처리 + /// + /// 바인딩할 ViewModel 타입 + public abstract class AutoBindView : MonoBehaviour where TViewModel : SimpleViewModel + { + [SerializeField] protected TViewModel _viewModel; + protected BindingContext _bindingContext; + + /// + /// ViewModel 인스턴스 + /// + public TViewModel ViewModel => _viewModel; + + protected virtual void Awake() + { + if (_viewModel == null) + _viewModel = GetComponent(); + + _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(); + } + + /// + /// Attribute 기반 자동 바인딩 설정 + /// + private void SetupAutoBindings() + { + var fields = GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public) + .Where(f => f.GetCustomAttribute() != null); + + foreach (var field in fields) + { + var bindAttribute = field.GetCustomAttribute(); + SetupBinding(field, bindAttribute); + } + + // 컬렉션 바인딩 설정 + var collectionFields = GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public) + .Where(f => f.GetCustomAttribute() != null); + + foreach (var field in collectionFields) + { + var bindAttribute = field.GetCustomAttribute(); + SetupCollectionBinding(field, bindAttribute); + } + } + + /// + /// 개별 필드의 바인딩 설정 + /// + /// 바인딩할 필드 + /// 바인딩 Attribute + 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); + } + } + + /// + /// 컬렉션 바인딩 설정 + /// + /// 바인딩할 필드 + /// 바인딩 Attribute + 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}"); + } + } + + /// + /// ViewModel 속성 변경 이벤트 핸들러 + /// 추가적인 커스텀 로직이 필요한 경우 오버라이드 + /// + /// 이벤트 발신자 + /// 속성 변경 정보 + protected virtual void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) + { + // 자동 바인딩으로 처리되지 않는 특별한 속성들의 커스텀 처리 + HandleCustomPropertyChanged(e.PropertyName); + } + + /// + /// 커스텀 속성 변경 처리 (하위 클래스에서 오버라이드) + /// + /// 변경된 속성 이름 + protected virtual void HandleCustomPropertyChanged(string propertyName) + { + // 하위 클래스에서 구현 + } + + /// + /// 수동 바인딩 헬퍼 메서드들 + /// Attribute 사용이 어려운 경우 코드로 바인딩 설정 + /// + + 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); + } + + /// + /// ViewModel 메서드 호출 헬퍼 + /// UI 이벤트에서 ViewModel 메서드를 쉽게 호출 + /// + /// 호출할 메서드 이름 + /// 메서드 매개변수 + protected void InvokeViewModelMethod(string methodName, params object[] parameters) + { + if (_viewModel == null) return; + + var method = _viewModel.GetType().GetMethod(methodName); + method?.Invoke(_viewModel, parameters); + } + + /// + /// ViewModel 속성 직접 설정 헬퍼 + /// + /// 속성 이름 + /// 설정할 값 + 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); + } + } + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/Views/Base/AutoBindView.cs.meta b/Assets/_DDD/_Scripts/GameUi/New/Views/Base/AutoBindView.cs.meta new file mode 100644 index 000000000..36e96d1a7 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/Views/Base/AutoBindView.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 738101122cf3fb74e99b244165797ab8 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBasePopupUi.cs b/Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBasePopupUi.cs new file mode 100644 index 000000000..f5139fa52 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBasePopupUi.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.EventSystems; +using DDD.MVVM; +using Sirenix.OdinInspector; +using UnityEngine.InputSystem; + +namespace DDD +{ + public abstract class IntegratedBasePopupUi : IntegratedBaseUi + where TInputEnum : Enum + where TViewModel : SimpleViewModel + { + [SerializeField, Required] protected BaseUiActionsInputBinding _uiActionsInputBinding; + + protected readonly List<(InputAction action, Action handler)> _registeredHandlers = + new(); + + public InputActionMaps InputActionMaps => _uiActionsInputBinding.InputActionMaps; + public bool IsTopPopup => UiManager.Instance.PopupUiState.IsTopPopup(this as BasePopupUi); + + protected override void Awake() + { + base.Awake(); + + // BasePopupUi의 기본값 적용 + _enableBlockImage = true; + } + + protected override void OnEnable() + { + base.OnEnable(); + } + + protected override void Update() + { + base.Update(); + + // BasePopupUi의 Update 로직 구현 + if (IsOpenPanel() == false) return; + + var currentSelectedGameObject = EventSystem.current.currentSelectedGameObject; + if (currentSelectedGameObject == null || currentSelectedGameObject.activeInHierarchy == false) + { + var initialSelected = GetInitialSelected(); + if (initialSelected != null) + { + EventSystem.current.SetSelectedGameObject(initialSelected); + } + } + } + + protected abstract GameObject GetInitialSelected(); + + protected override void TryRegister() + { + base.TryRegister(); + + // PopupUi의 입력 바인딩 등록 + foreach (var actionEnum in _uiActionsInputBinding.BindingActions.GetFlags()) + { + if (actionEnum.Equals(default(TInputEnum))) continue; + + var inputAction = + InputManager.Instance.GetAction(_uiActionsInputBinding.InputActionMaps, actionEnum.ToString()); + if (inputAction == null) continue; + + var startedHandler = new Action(context => + { + OnInputStarted(actionEnum, context); + }); + inputAction.started += startedHandler; + + var performedHandler = new Action(context => + { + OnInputPerformed(actionEnum, context); + }); + inputAction.performed += performedHandler; + + var canceledHandler = new Action(context => + { + OnInputCanceled(actionEnum, context); + }); + inputAction.canceled += canceledHandler; + + _registeredHandlers.Add((inputAction, startedHandler)); + _registeredHandlers.Add((inputAction, performedHandler)); + _registeredHandlers.Add((inputAction, canceledHandler)); + } + } + + protected override void TryUnregister() + { + base.TryUnregister(); + + // 입력 핸들러 해제 + foreach (var (action, handler) in _registeredHandlers) + { + if (action != null) + { + action.started -= handler; + action.performed -= handler; + action.canceled -= handler; + } + } + + _registeredHandlers.Clear(); + } + + // 입력 처리 메서드들 + protected virtual bool OnInputStarted(TInputEnum actionEnum, InputAction.CallbackContext context) => IsTopPopup; + + protected virtual bool OnInputPerformed(TInputEnum actionEnum, InputAction.CallbackContext context) => + IsTopPopup; + + protected virtual bool OnInputCanceled(TInputEnum actionEnum, InputAction.CallbackContext context) => + IsTopPopup; + + + public virtual void Open(OpenPopupUiEvent evt) + { + OpenPanel(); + + var initialSelected = GetInitialSelected(); + if (initialSelected != null) + { + EventSystem.current.SetSelectedGameObject(initialSelected); + } + + transform.SetAsLastSibling(); + + if (IsTopPopup) + { + InputManager.Instance.SwitchCurrentActionMap(_uiActionsInputBinding.InputActionMaps); + } + } + + public virtual void Close() + { + var evt = GameEvents.ClosePopupUiEvent; + evt.UiType = GetType(); + EventBus.Broadcast(evt); + } + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBasePopupUi.cs.meta b/Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBasePopupUi.cs.meta new file mode 100644 index 000000000..23e1d5306 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBasePopupUi.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3ef87d5c2f3d82e488302056ac09a287 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBaseUi.cs b/Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBaseUi.cs new file mode 100644 index 000000000..0f2195045 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBaseUi.cs @@ -0,0 +1,248 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using UnityEngine; +using UnityEngine.UI; +using DDD.MVVM; + +namespace DDD +{ + public abstract class IntegratedBaseUi : MonoBehaviour where TViewModel : SimpleViewModel + { + [SerializeField] protected bool _enableBlockImage; + + protected CanvasGroup _canvasGroup; + protected GameObject _blockImage; + protected GameObject _panel; + protected BindingContext _bindingContext; + protected TViewModel _viewModel; + + public virtual bool IsBlockingTime => false; + public virtual bool IsOpen => _panel != null && _panel.activeSelf; + + protected virtual void Awake() + { + _canvasGroup = GetComponent(); + _panel = transform.Find(CommonConstants.Panel)?.gameObject; + _blockImage = transform.Find(CommonConstants.BlockImage)?.gameObject; + + if (_viewModel == null) + _viewModel = GetComponent(); + + _bindingContext = new BindingContext(); + SetupAutoBindings(); + SetupBindings(); + } + + protected virtual void OnEnable() + { + if (_viewModel && _bindingContext != null) + { + _bindingContext.SetDataContext(_viewModel); + _viewModel.PropertyChanged += OnViewModelPropertyChanged; + } + } + + protected virtual void Start() + { + TryRegister(); + ClosePanel(); + } + + protected virtual void Update() + { + + } + + protected virtual void OnDisable() + { + if (_viewModel != null) + { + _viewModel.PropertyChanged -= OnViewModelPropertyChanged; + } + } + + protected virtual void OnDestroy() + { + TryUnregister(); + _bindingContext?.Dispose(); + } + + protected virtual void TryRegister() { } + protected virtual void TryUnregister() { } + + // BaseUi 메서드들을 직접 구현 + public virtual void OpenPanel() + { + if (_enableBlockImage) + { + _blockImage.SetActive(true); + } + + _panel.SetActive(true); + _viewModel?.Initialize(); + } + + public virtual void ClosePanel() + { + if (_enableBlockImage) + { + _blockImage.SetActive(false); + } + + _panel.SetActive(false); + _viewModel?.Cleanup(); + } + + public virtual void SetUiInteractable(bool active) + { + if (_canvasGroup != null) + { + _canvasGroup.interactable = active; + _canvasGroup.blocksRaycasts = active; + } + } + + public bool IsOpenPanel() => _panel && _panel.activeInHierarchy; + + /// + /// Attribute 기반 자동 바인딩 설정 + /// + private void SetupAutoBindings() + { + var fields = GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public) + .Where(f => f.GetCustomAttribute() != null); + + foreach (var field in fields) + { + var bindAttribute = field.GetCustomAttribute(); + SetupBinding(field, bindAttribute); + } + + // 컬렉션 바인딩 설정 + var collectionFields = GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public) + .Where(f => f.GetCustomAttribute() != null); + + foreach (var field in collectionFields) + { + var bindAttribute = field.GetCustomAttribute(); + SetupCollectionBinding(field, bindAttribute); + } + } + + /// + /// 개별 필드의 바인딩 설정 + /// + 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); + } + } + + /// + /// 컬렉션 바인딩 설정 + /// + 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}"); + } + } + + /// + /// 추가 바인딩 설정 - 하위 클래스에서 구현 + /// + protected virtual void SetupBindings() { } + + /// + /// ViewModel 속성 변경 이벤트 핸들러 + /// + protected virtual void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) + { + HandleCustomPropertyChanged(e.PropertyName); + } + + /// + /// 커스텀 속성 변경 처리 (하위 클래스에서 오버라이드) + /// + 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); + } + + /// + /// ViewModel 메서드 호출 헬퍼 + /// + protected void InvokeViewModelMethod(string methodName, params object[] parameters) + { + if (_viewModel == null) return; + + var method = _viewModel.GetType().GetMethod(methodName); + method?.Invoke(_viewModel, parameters); + } + + /// + /// ViewModel 속성 설정 헬퍼 + /// + 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); + } + } + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBaseUi.cs.meta b/Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBaseUi.cs.meta new file mode 100644 index 000000000..ed069a47f --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBaseUi.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 868322e05b33bdd4cbbe3e1495fe359b \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/Views/Examples.meta b/Assets/_DDD/_Scripts/GameUi/New/Views/Examples.meta new file mode 100644 index 000000000..53babe302 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/Views/Examples.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: abb1dd67b48daeb4f968a2641cf7b4a3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_DDD/_Scripts/GameUi/New/Views/Examples/IntegratedInventoryView.cs b/Assets/_DDD/_Scripts/GameUi/New/Views/Examples/IntegratedInventoryView.cs new file mode 100644 index 000000000..344ad881d --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/Views/Examples/IntegratedInventoryView.cs @@ -0,0 +1,221 @@ +using UnityEngine; +using UnityEngine.UI; +using DDD.MVVM; + +namespace DDD +{ + /// + /// IntegratedPopupUi를 사용한 인벤토리 뷰 예시 + /// 상속 대신 컴포지션 기반으로 모든 기능을 통합하여 구현 + /// Attribute 기반 자동 바인딩을 적극 활용 + /// + public class IntegratedInventoryView : IntegratedBasePopupUi + { + [Header("UI References")] + // Attribute를 통한 자동 바인딩 설정 + [SerializeField, BindTo(nameof(InventoryViewModel.CategoryDisplayText))] + private Text _categoryLabel; + + [SerializeField, BindTo(nameof(InventoryViewModel.ItemCountText))] + private Text _itemCountLabel; + + [SerializeField, BindTo(nameof(InventoryViewModel.ShowEmptyMessage))] + private GameObject _emptyMessage; + + // 수동 바인딩이 필요한 복잡한 UI 요소들 + [SerializeField] private Transform _slotParent; + [SerializeField] private Button[] _categoryButtons; + [SerializeField] private Button[] _sortButtons; + + [Header("Prefab References")] + [SerializeField] private GameObject _itemSlotPrefab; + + protected override GameObject GetInitialSelected() + { + // ViewModel의 FirstValidItem을 활용하여 초기 선택 UI 결정 + var firstItem = _viewModel?.FirstValidItem; + if (firstItem != null) + { + return FindSlotGameObject(firstItem); + } + + return _categoryButtons?.Length > 0 ? _categoryButtons[0].gameObject : null; + } + + /// + /// 수동 바인딩이 필요한 복잡한 UI 요소들 설정 + /// Attribute로 처리하기 어려운 컬렉션이나 복잡한 로직이 필요한 경우 + /// + protected override void SetupBindings() + { + // 복잡한 컬렉션 바인딩은 수동으로 처리 + // BindCollection은 아직 완전 구현되지 않았으므로 HandleCustomPropertyChanged에서 처리 + } + + protected override void HandleCustomPropertyChanged(string propertyName) + { + switch (propertyName) + { + case nameof(InventoryViewModel.VisibleItems): + UpdateItemSlots(); + break; + + case nameof(InventoryViewModel.CurrentCategory): + UpdateCategoryButtons(); + break; + + case nameof(InventoryViewModel.CurrentSortType): + UpdateSortButtons(); + break; + + case nameof(InventoryViewModel.FirstValidItem): + UpdateInitialSelection(); + break; + } + } + + /// + /// 아이템 슬롯 UI 업데이트 + /// + private void UpdateItemSlots() + { + if (_viewModel?.VisibleItems == null) return; + + // 기존 슬롯들 정리 + ClearSlots(); + + int siblingIndex = 0; + foreach (var itemViewModel in _viewModel.VisibleItems) + { + if (!itemViewModel.HasItem) continue; + + // 아이템 슬롯 UI 생성 + var slotGameObject = Instantiate(_itemSlotPrefab, _slotParent); + var slotUi = slotGameObject.GetComponent(); + + // 기존 방식대로 슬롯 초기화 + slotUi.Initialize(itemViewModel, new InventorySlotUiStrategy()); + slotGameObject.name = $"ItemSlotUi_{itemViewModel.Id}"; + + // 인터랙터 설정 + var interactor = slotGameObject.GetComponent(); + if (itemViewModel.ItemType == ItemType.Recipe) + { + interactor.Initialize(TodayMenuEventType.Add, new TodayMenuInteractorStrategy()); + } + else if (DataManager.Instance.GetDataSo().TryGetDataById(itemViewModel.Id, out var cookwareData)) + { + interactor.Initialize(TodayMenuEventType.Add, new TodayCookwareInteractorStrategy()); + } + + slotGameObject.transform.SetSiblingIndex(siblingIndex++); + } + } + + /// + /// 카테고리 버튼 상태 업데이트 + /// + private void UpdateCategoryButtons() + { + if (_categoryButtons == null || _viewModel == null) return; + + for (int i = 0; i < _categoryButtons.Length; i++) + { + var button = _categoryButtons[i]; + var isSelected = (int)_viewModel.CurrentCategory == i; + + button.interactable = !isSelected; + } + } + + /// + /// 정렬 버튼 상태 업데이트 + /// + private void UpdateSortButtons() + { + if (_sortButtons == null || _viewModel == null) return; + + for (int i = 0; i < _sortButtons.Length; i++) + { + var button = _sortButtons[i]; + var isSelected = (int)_viewModel.CurrentSortType == i; + + button.interactable = !isSelected; + } + } + + /// + /// 초기 선택 UI 업데이트 + /// + private void UpdateInitialSelection() + { + var initialSelected = GetInitialSelected(); + if (initialSelected != null) + { + UnityEngine.EventSystems.EventSystem.current.SetSelectedGameObject(initialSelected); + } + } + + /// + /// 기존 슬롯들 정리 + /// + private void ClearSlots() + { + foreach (Transform child in _slotParent) + { + Destroy(child.gameObject); + } + } + + /// + /// ItemViewModel에 해당하는 UI GameObject 찾기 + /// + private GameObject FindSlotGameObject(ItemViewModel itemViewModel) + { + foreach (Transform child in _slotParent) + { + if (child.name == $"ItemSlotUi_{itemViewModel.Id}") + { + return child.gameObject; + } + } + return null; + } + + // UI 이벤트 핸들러들 - ViewModel 메서드 호출 + public void OnCategoryButtonClicked(int categoryIndex) + { + _viewModel?.SetCategory((InventoryCategoryType)categoryIndex); + } + + public void OnSortButtonClicked(int sortIndex) + { + _viewModel?.SetSortType((InventorySortType)sortIndex); + } + + public void OnItemSlotClicked(ItemViewModel item) + { + _viewModel?.SelectItem(item); + } + + // 입력 처리 - ViewModel로 위임 + protected override bool OnInputPerformed(RestaurantUiActions actionEnum, UnityEngine.InputSystem.InputAction.CallbackContext context) + { + var isHandled = base.OnInputPerformed(actionEnum, context); + + // 특별한 입력 처리 로직이 필요한 경우 여기에 추가 + if (isHandled) + { + switch (actionEnum) + { + case RestaurantUiActions.Cancel: + Close(); + break; + // 기타 액션들은 ViewModel로 위임됨 + } + } + + return isHandled; + } + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/Views/Examples/IntegratedInventoryView.cs.meta b/Assets/_DDD/_Scripts/GameUi/New/Views/Examples/IntegratedInventoryView.cs.meta new file mode 100644 index 000000000..4c0ae5b0a --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/Views/Examples/IntegratedInventoryView.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 12f3141cdd485054e8c73be9d549feb7 \ No newline at end of file From 4c7d9c17e694c4bd6f7c52f07b3948f547ed2d31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=82=B0?= Date: Wed, 20 Aug 2025 15:30:08 +0900 Subject: [PATCH 06/36] =?UTF-8?q?=EC=9D=B8=ED=84=B0=EB=A0=89=EC=85=98=20?= =?UTF-8?q?=EC=84=9C=EB=B8=8C=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EC=B4=88?= =?UTF-8?q?=EC=95=88=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Props/Prop_CustomerTable.prefab | 276 +++++++++++++++++- .../Prop/Brazier1/Mat_Brazier1.mat | 2 +- .../Prop/Brazier2/Mat_Brazier2.mat | 2 +- .../Prop/Brazier3/Mat_Brazier3.mat | 2 +- .../Prop/Brazier4/Mat_Brazier4.mat | 2 +- .../Prop/DreamSeaweed/Mat_DreamSeaweed.mat | 2 +- .../Environments/Prop/Duck/Mat_Duck.mat | 2 +- .../Environments/Prop/Fried/Mat_Fried.mat | 2 +- .../Environments/Prop/Fried2/Mat_Fried2.mat | 2 +- .../Environments/Prop/Fried3/Mat_Fried3.mat | 2 +- .../Environments/Prop/Fried4/Mat_Fried4.mat | 2 +- .../Environments/Prop/Pot1/Mat_Pot1.mat | 2 +- .../Environments/Prop/Pot2/Mat_Pot2.mat | 2 +- .../Environments/Prop/Pot3/Mat_Pot3.mat | 2 +- .../Environments/Prop/Pot4/Mat_Pot4.mat | 2 +- .../Prefabs/RestaurantPlayer.prefab | 2 +- .../GameEvent/InteractionSubsystem.cs | 14 +- .../RestaurantManagementInteraction.cs | 16 + .../RestaurantManagementInteraction.cs.meta | 3 + .../RestaurantOrderInteraction.cs | 57 ++-- .../RestaurantInteractionComponent.cs | 14 +- .../Solvers/RestaurantOrderSolver.cs | 17 +- ProjectSettings/EditorBuildSettings.asset | 2 +- ProjectSettings/EntitiesClientSettings.asset | 3 + 24 files changed, 378 insertions(+), 54 deletions(-) create mode 100644 Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantManagementInteraction.cs create mode 100644 Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantManagementInteraction.cs.meta create mode 100644 ProjectSettings/EntitiesClientSettings.asset diff --git a/Assets/_DDD/Restaurant/Environments/Props/Prop_CustomerTable.prefab b/Assets/_DDD/Restaurant/Environments/Props/Prop_CustomerTable.prefab index f05b17a99..7d256654c 100644 --- a/Assets/_DDD/Restaurant/Environments/Props/Prop_CustomerTable.prefab +++ b/Assets/_DDD/Restaurant/Environments/Props/Prop_CustomerTable.prefab @@ -99,5 +99,279 @@ PrefabInstance: m_RemovedComponents: [] m_RemovedGameObjects: [] m_AddedGameObjects: [] - m_AddedComponents: [] + m_AddedComponents: + - targetCorrespondingSourceObject: {fileID: 4438924429928472453, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + insertIndex: -1 + addedObject: {fileID: 8605899758048842936} + - targetCorrespondingSourceObject: {fileID: 4438924429928472453, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + insertIndex: -1 + addedObject: {fileID: 6282952769554945552} + - targetCorrespondingSourceObject: {fileID: 4438924429928472453, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + insertIndex: -1 + addedObject: {fileID: 4598203232635129220} m_SourcePrefab: {fileID: 100100000, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} +--- !u!1 &9211739394093953175 stripped +GameObject: + m_CorrespondingSourceObject: {fileID: 4438924429928472453, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + m_PrefabInstance: {fileID: 4777358697124966162} + m_PrefabAsset: {fileID: 0} +--- !u!114 &8605899758048842936 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9211739394093953175} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c0b1e0992510498b8d33d5b6094b8f4b, type: 3} + m_Name: + m_EditorClassIdentifier: + _interactionType: 4 + _executionParameters: + _holdTime: 1 + _displayParameters: + _messageKey: + _interactionAvailableFlows: 1 + _aiInteractionPoints: [] + _initialOrderInteractionType: 0 +--- !u!114 &6282952769554945552 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9211739394093953175} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 888380afc233049ce9e618f9f36c8ba8, type: 3} + m_Name: + m_EditorClassIdentifier: + profile: {fileID: 0} + profileSync: 0 + camerasLayerMask: + serializedVersion: 2 + m_Bits: 4294967295 + effectGroup: 0 + effectTarget: {fileID: 0} + effectGroupLayer: + serializedVersion: 2 + m_Bits: 4294967295 + effectNameFilter: + effectNameUseRegEx: 0 + combineMeshes: 0 + alphaCutOff: 0 + cullBackFaces: 1 + padding: 0 + ignoreObjectVisibility: 0 + reflectionProbes: 0 + GPUInstancing: 1 + sortingPriority: 0 + optimizeSkinnedMesh: 1 + depthClip: 0 + cameraDistanceFade: 0 + cameraDistanceFadeNear: 0 + cameraDistanceFadeFar: 1000 + normalsOption: 0 + ignore: 0 + _highlighted: 0 + fadeInDuration: 0 + fadeOutDuration: 0 + flipY: 0 + constantWidth: 1 + extraCoveragePixels: 0 + minimumWidth: 0 + subMeshMask: -1 + overlay: 0 + overlayMode: 0 + overlayColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1} + overlayAnimationSpeed: 1 + overlayMinIntensity: 0.5 + overlayBlending: 1 + overlayTexture: {fileID: 0} + overlayTextureUVSpace: 0 + overlayTextureScale: 1 + overlayTextureScrolling: {x: 0, y: 0} + overlayVisibility: 0 + outline: 1 + outlineColor: {r: 0, g: 0, b: 0, a: 1} + outlineColorStyle: 0 + outlineGradient: + serializedVersion: 2 + key0: {r: 1, g: 1, b: 1, a: 1} + key1: {r: 1, g: 1, b: 1, a: 1} + key2: {r: 0, g: 0, b: 0, a: 0} + key3: {r: 0, g: 0, b: 0, a: 0} + key4: {r: 0, g: 0, b: 0, a: 0} + key5: {r: 0, g: 0, b: 0, a: 0} + key6: {r: 0, g: 0, b: 0, a: 0} + key7: {r: 0, g: 0, b: 0, a: 0} + ctime0: 0 + ctime1: 65535 + ctime2: 0 + ctime3: 0 + ctime4: 0 + ctime5: 0 + ctime6: 0 + ctime7: 0 + atime0: 0 + atime1: 65535 + atime2: 0 + atime3: 0 + atime4: 0 + atime5: 0 + atime6: 0 + atime7: 0 + m_Mode: 0 + m_ColorSpace: -1 + m_NumColorKeys: 2 + m_NumAlphaKeys: 2 + outlineGradientInLocalSpace: 0 + outlineWidth: 0.45 + outlineBlurPasses: 2 + outlineQuality: 3 + outlineEdgeMode: 0 + outlineEdgeThreshold: 0.995 + outlineSharpness: 1 + outlineDownsampling: 1 + outlineVisibility: 0 + glowBlendMode: 0 + outlineBlitDebug: 0 + outlineIndependent: 0 + outlineContourStyle: 0 + outlineMaskMode: 0 + glow: 0 + glowWidth: 0.4 + glowQuality: 3 + glowBlurMethod: 0 + glowDownsampling: 2 + glowHQColor: {r: 0.64, g: 1, b: 0, a: 1} + glowDithering: 1 + glowDitheringStyle: 0 + glowMagicNumber1: 0.75 + glowMagicNumber2: 0.5 + glowAnimationSpeed: 1 + glowVisibility: 0 + glowBlitDebug: 0 + glowBlendPasses: 1 + glowPasses: + - offset: 4 + alpha: 0.1 + color: {r: 0.64, g: 1, b: 0, a: 1} + - offset: 3 + alpha: 0.2 + color: {r: 0.64, g: 1, b: 0, a: 1} + - offset: 2 + alpha: 0.3 + color: {r: 0.64, g: 1, b: 0, a: 1} + - offset: 1 + alpha: 0.4 + color: {r: 0.64, g: 1, b: 0, a: 1} + glowMaskMode: 0 + innerGlow: 0 + innerGlowWidth: 1 + innerGlowColor: {r: 1, g: 1, b: 1, a: 1} + innerGlowBlendMode: 0 + innerGlowVisibility: 0 + targetFX: 0 + targetFXTexture: {fileID: 0} + targetFXColor: {r: 1, g: 1, b: 1, a: 1} + targetFXCenter: {fileID: 0} + targetFXRotationSpeed: 50 + targetFXInitialScale: 4 + targetFXEndScale: 1.5 + targetFXScaleToRenderBounds: 1 + targetFXUseEnclosingBounds: 0 + targetFXAlignToGround: 0 + targetFXOffset: {x: 0, y: 0, z: 0} + targetFXFadePower: 32 + targetFXGroundMaxDistance: 10 + targetFXGroundLayerMask: + serializedVersion: 2 + m_Bits: 4294967295 + targetFXTransitionDuration: 0.5 + targetFXStayDuration: 1.5 + targetFXVisibility: 1 + iconFX: 0 + iconFXMesh: {fileID: 0} + iconFXLightColor: {r: 1, g: 1, b: 1, a: 1} + iconFXDarkColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + iconFXCenter: {fileID: 0} + iconFXRotationSpeed: 50 + iconFXAnimationOption: 0 + iconFXAnimationAmount: 0.1 + iconFXAnimationSpeed: 3 + iconFXScale: 1 + iconFXScaleToRenderBounds: 0 + iconFXOffset: {x: 0, y: 1, z: 0} + iconFXTransitionDuration: 0.5 + iconFXStayDuration: 1.5 + seeThrough: 2 + seeThroughOccluderMask: + serializedVersion: 2 + m_Bits: 4294967295 + seeThroughOccluderThreshold: 0.3 + seeThroughOccluderMaskAccurate: 0 + seeThroughOccluderCheckInterval: 1 + seeThroughOccluderCheckIndividualObjects: 0 + seeThroughDepthOffset: 0 + seeThroughMaxDepth: 0 + seeThroughIntensity: 0.8 + seeThroughTintAlpha: 0.5 + seeThroughTintColor: {r: 1, g: 0, b: 0, a: 1} + seeThroughNoise: 1 + seeThroughBorder: 0 + seeThroughBorderColor: {r: 0, g: 0, b: 0, a: 1} + seeThroughBorderOnly: 0 + seeThroughBorderWidth: 0.45 + seeThroughOrdered: 0 + seeThroughTexture: {fileID: 0} + seeThroughTextureUVSpace: 0 + seeThroughTextureScale: 1 + seeThroughChildrenSortingMode: 0 + rmsCount: 1 + hitFxInitialIntensity: 0 + hitFxMode: 0 + hitFxFadeOutDuration: 0.25 + hitFxColor: {r: 1, g: 1, b: 1, a: 1} + hitFxRadius: 0.5 +--- !u!114 &4598203232635129220 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9211739394093953175} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f0feb22ab60a4d1885271637838f43b9, type: 3} + m_Name: + m_EditorClassIdentifier: + _availableStyle: + Color: {r: 1, g: 1, b: 1, a: 1} + Width: 1 + Opacity: 1 + _focusedStyle: + Color: {r: 1, g: 0.92156863, b: 0.015686275, a: 1} + Width: 1 + Opacity: 1 + _unavailableStyle: + Color: {r: 0.5, g: 0.5, b: 0.5, a: 1} + Width: 0.5 + Opacity: 1 + _objectiveStyle: + Color: {r: 0, g: 1, b: 1, a: 1} + Width: 1 + Opacity: 1 + _breathingSpeed: 2 + _breathingRange: 0.3 + _enableBreathingEffect: 1 + _alphaCutOff: 0.5 + _combineMeshes: 1 + _constantWidth: 1 + _outlineQuality: 2 + _outlineIndependent: 1 + _outlineBlurPasses: 1 + _outlineSharpness: 8 + _currentOutlineType: 0 + _currentOpacityMultiplier: 1 diff --git a/Assets/_DDD/_Addressables/Environments/Prop/Brazier1/Mat_Brazier1.mat b/Assets/_DDD/_Addressables/Environments/Prop/Brazier1/Mat_Brazier1.mat index 647d83081..29c00b75b 100644 --- a/Assets/_DDD/_Addressables/Environments/Prop/Brazier1/Mat_Brazier1.mat +++ b/Assets/_DDD/_Addressables/Environments/Prop/Brazier1/Mat_Brazier1.mat @@ -34,7 +34,7 @@ Material: m_LightmapFlags: 2 m_EnableInstancingVariants: 0 m_DoubleSidedGI: 0 - m_CustomRenderQueue: -1 + m_CustomRenderQueue: 2450 stringTagMap: RenderType: TransparentCutout disabledShaderPasses: diff --git a/Assets/_DDD/_Addressables/Environments/Prop/Brazier2/Mat_Brazier2.mat b/Assets/_DDD/_Addressables/Environments/Prop/Brazier2/Mat_Brazier2.mat index f735857d9..f1003b969 100644 --- a/Assets/_DDD/_Addressables/Environments/Prop/Brazier2/Mat_Brazier2.mat +++ b/Assets/_DDD/_Addressables/Environments/Prop/Brazier2/Mat_Brazier2.mat @@ -21,7 +21,7 @@ Material: m_LightmapFlags: 2 m_EnableInstancingVariants: 0 m_DoubleSidedGI: 0 - m_CustomRenderQueue: -1 + m_CustomRenderQueue: 2450 stringTagMap: RenderType: TransparentCutout disabledShaderPasses: diff --git a/Assets/_DDD/_Addressables/Environments/Prop/Brazier3/Mat_Brazier3.mat b/Assets/_DDD/_Addressables/Environments/Prop/Brazier3/Mat_Brazier3.mat index 3a2fe671b..b1f8866c3 100644 --- a/Assets/_DDD/_Addressables/Environments/Prop/Brazier3/Mat_Brazier3.mat +++ b/Assets/_DDD/_Addressables/Environments/Prop/Brazier3/Mat_Brazier3.mat @@ -21,7 +21,7 @@ Material: m_LightmapFlags: 2 m_EnableInstancingVariants: 0 m_DoubleSidedGI: 0 - m_CustomRenderQueue: -1 + m_CustomRenderQueue: 2450 stringTagMap: RenderType: TransparentCutout disabledShaderPasses: diff --git a/Assets/_DDD/_Addressables/Environments/Prop/Brazier4/Mat_Brazier4.mat b/Assets/_DDD/_Addressables/Environments/Prop/Brazier4/Mat_Brazier4.mat index 9e3fd6944..e71a81b58 100644 --- a/Assets/_DDD/_Addressables/Environments/Prop/Brazier4/Mat_Brazier4.mat +++ b/Assets/_DDD/_Addressables/Environments/Prop/Brazier4/Mat_Brazier4.mat @@ -34,7 +34,7 @@ Material: m_LightmapFlags: 2 m_EnableInstancingVariants: 0 m_DoubleSidedGI: 0 - m_CustomRenderQueue: -1 + m_CustomRenderQueue: 2450 stringTagMap: RenderType: TransparentCutout disabledShaderPasses: diff --git a/Assets/_DDD/_Addressables/Environments/Prop/DreamSeaweed/Mat_DreamSeaweed.mat b/Assets/_DDD/_Addressables/Environments/Prop/DreamSeaweed/Mat_DreamSeaweed.mat index 8fefba955..31d7318e1 100644 --- a/Assets/_DDD/_Addressables/Environments/Prop/DreamSeaweed/Mat_DreamSeaweed.mat +++ b/Assets/_DDD/_Addressables/Environments/Prop/DreamSeaweed/Mat_DreamSeaweed.mat @@ -21,7 +21,7 @@ Material: m_LightmapFlags: 2 m_EnableInstancingVariants: 0 m_DoubleSidedGI: 0 - m_CustomRenderQueue: -1 + m_CustomRenderQueue: 2450 stringTagMap: RenderType: TransparentCutout disabledShaderPasses: diff --git a/Assets/_DDD/_Addressables/Environments/Prop/Duck/Mat_Duck.mat b/Assets/_DDD/_Addressables/Environments/Prop/Duck/Mat_Duck.mat index b587b5c03..344e1affc 100644 --- a/Assets/_DDD/_Addressables/Environments/Prop/Duck/Mat_Duck.mat +++ b/Assets/_DDD/_Addressables/Environments/Prop/Duck/Mat_Duck.mat @@ -34,7 +34,7 @@ Material: m_LightmapFlags: 2 m_EnableInstancingVariants: 0 m_DoubleSidedGI: 0 - m_CustomRenderQueue: -1 + m_CustomRenderQueue: 2450 stringTagMap: RenderType: TransparentCutout disabledShaderPasses: diff --git a/Assets/_DDD/_Addressables/Environments/Prop/Fried/Mat_Fried.mat b/Assets/_DDD/_Addressables/Environments/Prop/Fried/Mat_Fried.mat index 904a5d256..66faed0bf 100644 --- a/Assets/_DDD/_Addressables/Environments/Prop/Fried/Mat_Fried.mat +++ b/Assets/_DDD/_Addressables/Environments/Prop/Fried/Mat_Fried.mat @@ -21,7 +21,7 @@ Material: m_LightmapFlags: 2 m_EnableInstancingVariants: 0 m_DoubleSidedGI: 0 - m_CustomRenderQueue: -1 + m_CustomRenderQueue: 2450 stringTagMap: RenderType: TransparentCutout disabledShaderPasses: diff --git a/Assets/_DDD/_Addressables/Environments/Prop/Fried2/Mat_Fried2.mat b/Assets/_DDD/_Addressables/Environments/Prop/Fried2/Mat_Fried2.mat index 6e519d0a6..2fec2d8ec 100644 --- a/Assets/_DDD/_Addressables/Environments/Prop/Fried2/Mat_Fried2.mat +++ b/Assets/_DDD/_Addressables/Environments/Prop/Fried2/Mat_Fried2.mat @@ -34,7 +34,7 @@ Material: m_LightmapFlags: 2 m_EnableInstancingVariants: 0 m_DoubleSidedGI: 0 - m_CustomRenderQueue: -1 + m_CustomRenderQueue: 2450 stringTagMap: RenderType: TransparentCutout disabledShaderPasses: diff --git a/Assets/_DDD/_Addressables/Environments/Prop/Fried3/Mat_Fried3.mat b/Assets/_DDD/_Addressables/Environments/Prop/Fried3/Mat_Fried3.mat index 714898f02..ea91bd3db 100644 --- a/Assets/_DDD/_Addressables/Environments/Prop/Fried3/Mat_Fried3.mat +++ b/Assets/_DDD/_Addressables/Environments/Prop/Fried3/Mat_Fried3.mat @@ -21,7 +21,7 @@ Material: m_LightmapFlags: 2 m_EnableInstancingVariants: 0 m_DoubleSidedGI: 0 - m_CustomRenderQueue: -1 + m_CustomRenderQueue: 2450 stringTagMap: RenderType: TransparentCutout disabledShaderPasses: diff --git a/Assets/_DDD/_Addressables/Environments/Prop/Fried4/Mat_Fried4.mat b/Assets/_DDD/_Addressables/Environments/Prop/Fried4/Mat_Fried4.mat index 74e4f7fc3..c73583cdb 100644 --- a/Assets/_DDD/_Addressables/Environments/Prop/Fried4/Mat_Fried4.mat +++ b/Assets/_DDD/_Addressables/Environments/Prop/Fried4/Mat_Fried4.mat @@ -21,7 +21,7 @@ Material: m_LightmapFlags: 2 m_EnableInstancingVariants: 0 m_DoubleSidedGI: 0 - m_CustomRenderQueue: -1 + m_CustomRenderQueue: 2450 stringTagMap: RenderType: TransparentCutout disabledShaderPasses: diff --git a/Assets/_DDD/_Addressables/Environments/Prop/Pot1/Mat_Pot1.mat b/Assets/_DDD/_Addressables/Environments/Prop/Pot1/Mat_Pot1.mat index 8fdcb7a91..8b44fbd7f 100644 --- a/Assets/_DDD/_Addressables/Environments/Prop/Pot1/Mat_Pot1.mat +++ b/Assets/_DDD/_Addressables/Environments/Prop/Pot1/Mat_Pot1.mat @@ -34,7 +34,7 @@ Material: m_LightmapFlags: 2 m_EnableInstancingVariants: 0 m_DoubleSidedGI: 0 - m_CustomRenderQueue: -1 + m_CustomRenderQueue: 2450 stringTagMap: RenderType: TransparentCutout disabledShaderPasses: diff --git a/Assets/_DDD/_Addressables/Environments/Prop/Pot2/Mat_Pot2.mat b/Assets/_DDD/_Addressables/Environments/Prop/Pot2/Mat_Pot2.mat index 53c7f07d6..0f4c37073 100644 --- a/Assets/_DDD/_Addressables/Environments/Prop/Pot2/Mat_Pot2.mat +++ b/Assets/_DDD/_Addressables/Environments/Prop/Pot2/Mat_Pot2.mat @@ -21,7 +21,7 @@ Material: m_LightmapFlags: 2 m_EnableInstancingVariants: 0 m_DoubleSidedGI: 0 - m_CustomRenderQueue: -1 + m_CustomRenderQueue: 2450 stringTagMap: RenderType: TransparentCutout disabledShaderPasses: diff --git a/Assets/_DDD/_Addressables/Environments/Prop/Pot3/Mat_Pot3.mat b/Assets/_DDD/_Addressables/Environments/Prop/Pot3/Mat_Pot3.mat index da3c48f99..742bb6d22 100644 --- a/Assets/_DDD/_Addressables/Environments/Prop/Pot3/Mat_Pot3.mat +++ b/Assets/_DDD/_Addressables/Environments/Prop/Pot3/Mat_Pot3.mat @@ -34,7 +34,7 @@ Material: m_LightmapFlags: 2 m_EnableInstancingVariants: 0 m_DoubleSidedGI: 0 - m_CustomRenderQueue: -1 + m_CustomRenderQueue: 2450 stringTagMap: RenderType: TransparentCutout disabledShaderPasses: diff --git a/Assets/_DDD/_Addressables/Environments/Prop/Pot4/Mat_Pot4.mat b/Assets/_DDD/_Addressables/Environments/Prop/Pot4/Mat_Pot4.mat index 6bc0d0e21..9b2c73cc4 100644 --- a/Assets/_DDD/_Addressables/Environments/Prop/Pot4/Mat_Pot4.mat +++ b/Assets/_DDD/_Addressables/Environments/Prop/Pot4/Mat_Pot4.mat @@ -34,7 +34,7 @@ Material: m_LightmapFlags: 2 m_EnableInstancingVariants: 0 m_DoubleSidedGI: 0 - m_CustomRenderQueue: -1 + m_CustomRenderQueue: 2450 stringTagMap: RenderType: TransparentCutout disabledShaderPasses: diff --git a/Assets/_DDD/_Addressables/Prefabs/RestaurantPlayer.prefab b/Assets/_DDD/_Addressables/Prefabs/RestaurantPlayer.prefab index 92d5aded1..c1e3a3a75 100644 --- a/Assets/_DDD/_Addressables/Prefabs/RestaurantPlayer.prefab +++ b/Assets/_DDD/_Addressables/Prefabs/RestaurantPlayer.prefab @@ -423,7 +423,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 81e01dd8c1cc3404d805400eba1bb4ae, type: 3} m_Name: m_EditorClassIdentifier: - _interactionType: 4294967295 + _interactionType: 4294967287 _nearColliders: - {fileID: 0} - {fileID: 0} diff --git a/Assets/_DDD/_Scripts/GameEvent/InteractionSubsystem.cs b/Assets/_DDD/_Scripts/GameEvent/InteractionSubsystem.cs index 8ca5eefc8..c5b79395d 100644 --- a/Assets/_DDD/_Scripts/GameEvent/InteractionSubsystem.cs +++ b/Assets/_DDD/_Scripts/GameEvent/InteractionSubsystem.cs @@ -1,4 +1,16 @@ +using System; +using UnityEngine; + namespace DDD { - + public interface IInteractionSubsystemObject where T : Enum + { + T GetInteractionSubsystemType(); + } + public interface IInteractionSubsystemSolver where T : Enum + { + bool ExecuteInteractionSubsystem(IInteractor interactor, IInteractable interactable, ScriptableObject payloadSo = null); + bool CanExecuteInteractionSubsystem(IInteractor interactor = null, IInteractable interactable = null, + ScriptableObject payloadSo = null); + } } \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantManagementInteraction.cs b/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantManagementInteraction.cs new file mode 100644 index 000000000..c2df3a351 --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantManagementInteraction.cs @@ -0,0 +1,16 @@ +using System; + +namespace DDD +{ + [Flags] + public enum RestaurantManagementType : uint + { + OpenRestaurantMenu = 0, + StartRestaurant = 1, + } + + //public class RestaurantManagementInteraction : RestaurantInteractionComponent, IInteractionSubsystemObject + //{ + // + //} +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantManagementInteraction.cs.meta b/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantManagementInteraction.cs.meta new file mode 100644 index 000000000..2cf0b9e79 --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantManagementInteraction.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1235f6bde9304d8f85079f2777bd4b3c +timeCreated: 1755671037 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteraction.cs b/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteraction.cs index 8c3ca91c1..d39dd3d8f 100644 --- a/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteraction.cs +++ b/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteraction.cs @@ -3,29 +3,12 @@ namespace DDD { - public interface IInteractionSubsystemObject where T : Enum - { - T GetInteractionSubsystemType(); - } - public interface IInteractionSubsystemSolver where T : Enum - { - bool ExecuteInteractionSubsystem(IInteractor interactor, IInteractable interactable, ScriptableObject payloadSo = null); - bool CanExecuteInteractionSubsystem(IInteractor interactor = null, IInteractable interactable = null, - ScriptableObject payloadSo = null); - } - - public interface IInteractionSubsystemSubject where T : Enum - { - bool CanSolveInteractionType(T interactionSubsystemType); - bool CanInteractTo(IInteractionSubsystemObject interactableSubsystemObject, - ScriptableObject payloadSo = null); - } - [Flags] public enum RestaurantOrderInteractionType : uint { // None = 0u, - WaitCustomer = 1u << 0, + WaitCustomer = 0, + // WaitCustomer = 1u << 0, // WaitOrder = 1u << 1, // WaitServe = 1u << 2, // All = 0xFFFFFFFFu @@ -48,26 +31,46 @@ private void OnValidate() private void Start() { - + _currentRestaurantOrderInteractionType = _initialOrderInteractionType; } private void SetInteractionTypeToRestaurantOrder() { _interactionType = InteractionType.RestaurantOrder; } - InteractionType GetInteractionType() + public override InteractionType GetInteractionType() { return InteractionType.RestaurantOrder; } - bool CanInteract() + public override bool CanInteract() { - return TODO_IMPLEMENT_ME; + // 현재 RestaurantOrderInteractionType를 수행할 수 있는지? + if (GetInteractionSubsystemType() == RestaurantOrderInteractionType.WaitCustomer) + { + // Check WaitCustomer + return true; + } + + return false; } - bool OnInteracted(IInteractor interactor, ScriptableObject payloadSo = null); - void InitializeInteraction(InteractionType interactionType); - InteractionExecutionParameters GetExecutionParameters(); - InteractionDisplayParameters GetDisplayParameters(); + public override bool OnInteracted(IInteractor interactor, ScriptableObject payloadSo = null) + { + // _currentRestaurantOrderInteractionType에 따라 동작이 달라지겠지 + if (GetInteractionSubsystemType() == RestaurantOrderInteractionType.WaitCustomer) + { + // DO WAIT CUSTOMER + } + return base.OnInteracted(interactor, payloadSo); + } + + public override void InitializeInteraction(InteractionType interactionType) + { + // RestaurantOrderInteractionType에 따른 동작들을 초기화 + // Initialize WaitCustomer actions + base.InitializeInteraction(interactionType); + } + public RestaurantOrderInteractionType GetInteractionSubsystemType() { return _currentRestaurantOrderInteractionType; diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionComponent.cs b/Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionComponent.cs index fcd7860d7..d88aefe9c 100644 --- a/Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionComponent.cs +++ b/Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionComponent.cs @@ -11,19 +11,19 @@ public class RestaurantInteractionComponent : MonoBehaviour, IInteractable [SerializeField] protected GameFlowState _interactionAvailableFlows; [SerializeField] private Transform[] _aiInteractionPoints; - public bool CanInteract() + public virtual bool CanInteract() { return !IsInteractionHidden(); } - public bool IsInteractionHidden() + public virtual bool IsInteractionHidden() { var currentGameFlowState = GameFlowManager.Instance.GameFlowDataSo.CurrentGameState; var flowDisabled = (currentGameFlowState & _interactionAvailableFlows) == 0; return flowDisabled; } - public bool OnInteracted(IInteractor interactor, ScriptableObject payloadSo = null) + public virtual bool OnInteracted(IInteractor interactor, ScriptableObject payloadSo = null) { if (CanInteract() == false) { @@ -34,7 +34,7 @@ public bool OnInteracted(IInteractor interactor, ScriptableObject payloadSo = nu return interactionResult; } - public InteractionType GetInteractionType() + public virtual InteractionType GetInteractionType() { return _interactionType; } @@ -44,18 +44,18 @@ public GameObject GetInteractableGameObject() return gameObject; } - public void InitializeInteraction(InteractionType interactionType) + public virtual void InitializeInteraction(InteractionType interactionType) { _interactionType = interactionType; } // 새로운 스트럭트 기반 메서드들 - public InteractionExecutionParameters GetExecutionParameters() + public virtual InteractionExecutionParameters GetExecutionParameters() { return _executionParameters; } - public InteractionDisplayParameters GetDisplayParameters() + public virtual InteractionDisplayParameters GetDisplayParameters() { return _displayParameters; } diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrderSolver.cs b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrderSolver.cs index 653b7e355..3f8089dee 100644 --- a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrderSolver.cs +++ b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrderSolver.cs @@ -17,7 +17,17 @@ public bool CanExecuteInteraction(IInteractor interactor = null, IInteractable i public bool ExecuteInteractionSubsystem(IInteractor interactor, IInteractable interactable, ScriptableObject payloadSo = null) { - TODO_IMPLEMENT_ME + if (interactable is IInteractionSubsystemObject subsystem) + { + RestaurantOrderInteractionType interactionType = subsystem.GetInteractionSubsystemType(); + // Can I solve this interaction type? + if (interactionType == RestaurantOrderInteractionType.WaitCustomer) + { + // DO SOMETHING!!! + return true; + } + } + return false; } public bool CanExecuteInteractionSubsystem(IInteractor interactor = null, IInteractable interactable = null, @@ -27,7 +37,10 @@ public bool CanExecuteInteractionSubsystem(IInteractor interactor = null, IInter { RestaurantOrderInteractionType interactionType = subsystem.GetInteractionSubsystemType(); // Can I solve this interaction type? - TODO_IMPLEMENT_ME + if (interactionType == RestaurantOrderInteractionType.WaitCustomer) + { + return true; + } } return false; } diff --git a/ProjectSettings/EditorBuildSettings.asset b/ProjectSettings/EditorBuildSettings.asset index ed8485fa0..6459f8e33 100644 --- a/ProjectSettings/EditorBuildSettings.asset +++ b/ProjectSettings/EditorBuildSettings.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f86abe14bf9fc4c7c93dfa96efd110ef0f56c3fb96a952c57e06a723fc87c352 +oid sha256:b8ae76b7c8f92890e759f46ff36b5db6b9e756533c8f14d3ef3ea41df7f4c5ad size 1075 diff --git a/ProjectSettings/EntitiesClientSettings.asset b/ProjectSettings/EntitiesClientSettings.asset new file mode 100644 index 000000000..5f47f97f5 --- /dev/null +++ b/ProjectSettings/EntitiesClientSettings.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0197d88686b9f0693f82b87bb787205373c1a8b7c6f1ff8d89d8ed61355369e8 +size 440 From a28415330c761fde12f7e75d9c0754ed41951967 Mon Sep 17 00:00:00 2001 From: Jeonghyeon Ha Date: Wed, 20 Aug 2025 16:50:29 +0900 Subject: [PATCH 07/36] =?UTF-8?q?=EB=A0=88=EC=8A=A4=ED=86=A0=EB=9E=91=20?= =?UTF-8?q?=EC=98=A4=EB=8D=94=20=EC=83=81=ED=98=B8=EC=9E=91=EC=9A=A9=20?= =?UTF-8?q?=EC=84=9C=EB=B8=8C=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EB=8F=84?= =?UTF-8?q?=EC=9E=85=20=EB=B0=8F=20=EC=A3=BC=EB=AC=B8/=EC=84=9C=EB=B9=99?= =?UTF-8?q?=20=EC=86=94=EB=B2=84=20=EC=B6=94=EA=B0=80,=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=83=81=ED=98=B8=EC=9E=91=EC=9A=A9/=EC=BA=90?= =?UTF-8?q?=EB=A6=AD=ED=84=B0=20=EB=A1=9C=EC=A7=81=20=EA=B0=B1=EC=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GameEvent/InteractionSubsystem.cs | 15 +++- .../Core/RestaurantCharacter.cs | 2 +- .../Core/RestaurantCharacterInteraction.cs | 4 +- .../RestaurantOrderInteraction.cs | 79 ------------------- .../RestaurantOrderInteractionSubsystem.cs | 59 ++++++++++++++ ...staurantOrderInteractionSubsystem.cs.meta} | 0 .../RestaurantInteractionComponent.cs | 65 ++++++++++++++- .../Solvers/RestaurantOrderSolver.cs | 68 +++++++++------- .../Solvers/RestaurantOrders.meta | 3 + .../RestaurantOrderSolver_Order.cs | 19 +++++ .../RestaurantOrderSolver_Order.cs.meta | 3 + .../RestaurantOrderSolver_Serve.cs | 19 +++++ .../RestaurantOrderSolver_Serve.cs.meta | 3 + .../RestaurantOrderSolver_Wait.cs | 19 +++++ .../RestaurantOrderSolver_Wait.cs.meta | 3 + 15 files changed, 245 insertions(+), 116 deletions(-) delete mode 100644 Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteraction.cs create mode 100644 Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteractionSubsystem.cs rename Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/{RestaurantOrderInteraction.cs.meta => RestaurantOrderInteractionSubsystem.cs.meta} (100%) create mode 100644 Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders.meta create mode 100644 Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Order.cs create mode 100644 Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Order.cs.meta create mode 100644 Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Serve.cs create mode 100644 Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Serve.cs.meta create mode 100644 Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Wait.cs create mode 100644 Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Wait.cs.meta diff --git a/Assets/_DDD/_Scripts/GameEvent/InteractionSubsystem.cs b/Assets/_DDD/_Scripts/GameEvent/InteractionSubsystem.cs index c5b79395d..1085117d3 100644 --- a/Assets/_DDD/_Scripts/GameEvent/InteractionSubsystem.cs +++ b/Assets/_DDD/_Scripts/GameEvent/InteractionSubsystem.cs @@ -3,11 +3,22 @@ namespace DDD { - public interface IInteractionSubsystemObject where T : Enum + public interface IInteractionSubsystemObject + { + Type GetSubsystemEnumType(); + void InitializeSubsystem(); + bool CanInteract(); + bool OnInteracted(IInteractor interactor, ScriptableObject payloadSo = null); + } + public interface IInteractionSubsystemObject : IInteractionSubsystemObject where T : Enum { T GetInteractionSubsystemType(); } - public interface IInteractionSubsystemSolver where T : Enum + + public interface IInteractionSubsystemSolver + { + } + public interface IInteractionSubsystemSolver : IInteractionSubsystemSolver where T : Enum { bool ExecuteInteractionSubsystem(IInteractor interactor, IInteractable interactable, ScriptableObject payloadSo = null); bool CanExecuteInteractionSubsystem(IInteractor interactor = null, IInteractable interactable = null, diff --git a/Assets/_DDD/_Scripts/RestaurantCharacter/Core/RestaurantCharacter.cs b/Assets/_DDD/_Scripts/RestaurantCharacter/Core/RestaurantCharacter.cs index 0772089b6..45c56de5d 100644 --- a/Assets/_DDD/_Scripts/RestaurantCharacter/Core/RestaurantCharacter.cs +++ b/Assets/_DDD/_Scripts/RestaurantCharacter/Core/RestaurantCharacter.cs @@ -23,7 +23,7 @@ protected virtual void Start() var flag = typeToSolver.Key; if (flag == InteractionType.None) continue; - if ((_interactionComponent.InteractionType & flag) == 0) continue; + if ((_interactionComponent.AvailableInteractions & flag) == 0) continue; if (!TryGetComponent(typeToSolver.Value, out _)) { diff --git a/Assets/_DDD/_Scripts/RestaurantCharacter/Core/RestaurantCharacterInteraction.cs b/Assets/_DDD/_Scripts/RestaurantCharacter/Core/RestaurantCharacterInteraction.cs index c4745fafd..3db7a4412 100644 --- a/Assets/_DDD/_Scripts/RestaurantCharacter/Core/RestaurantCharacterInteraction.cs +++ b/Assets/_DDD/_Scripts/RestaurantCharacter/Core/RestaurantCharacterInteraction.cs @@ -6,10 +6,10 @@ namespace DDD { public class RestaurantCharacterInteraction : MonoBehaviour, IInteractor, IEventHandler { - [EnumToggleButtons, SerializeField] protected InteractionType _interactionType; + [EnumToggleButtons, SerializeField] protected InteractionType _availableInteractions; [SerializeField, ReadOnly] protected Collider[] _nearColliders = new Collider[10]; - public InteractionType InteractionType => _interactionType; + public InteractionType AvailableInteractions => _availableInteractions; protected IInteractable _nearestInteractable; protected IInteractable _previousInteractable; diff --git a/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteraction.cs b/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteraction.cs deleted file mode 100644 index d39dd3d8f..000000000 --- a/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteraction.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using UnityEngine; - -namespace DDD -{ - [Flags] - public enum RestaurantOrderInteractionType : uint - { - // None = 0u, - WaitCustomer = 0, - // WaitCustomer = 1u << 0, - // WaitOrder = 1u << 1, - // WaitServe = 1u << 2, - // All = 0xFFFFFFFFu - } - public class RestaurantOrderInteraction : RestaurantInteractionComponent, IInteractionSubsystemObject - { - [SerializeField] protected RestaurantOrderInteractionType _initialOrderInteractionType = RestaurantOrderInteractionType.WaitCustomer; - private RestaurantOrderInteractionType _currentRestaurantOrderInteractionType; - - // EDITOR - private void Reset() - { - SetInteractionTypeToRestaurantOrder(); - } - private void OnValidate() - { - SetInteractionTypeToRestaurantOrder(); - } - // ~EDITOR - - private void Start() - { - _currentRestaurantOrderInteractionType = _initialOrderInteractionType; - } - - private void SetInteractionTypeToRestaurantOrder() - { - _interactionType = InteractionType.RestaurantOrder; - } - public override InteractionType GetInteractionType() - { - return InteractionType.RestaurantOrder; - } - public override bool CanInteract() - { - // 현재 RestaurantOrderInteractionType를 수행할 수 있는지? - if (GetInteractionSubsystemType() == RestaurantOrderInteractionType.WaitCustomer) - { - // Check WaitCustomer - return true; - } - - return false; - } - - public override bool OnInteracted(IInteractor interactor, ScriptableObject payloadSo = null) - { - // _currentRestaurantOrderInteractionType에 따라 동작이 달라지겠지 - if (GetInteractionSubsystemType() == RestaurantOrderInteractionType.WaitCustomer) - { - // DO WAIT CUSTOMER - } - return base.OnInteracted(interactor, payloadSo); - } - - public override void InitializeInteraction(InteractionType interactionType) - { - // RestaurantOrderInteractionType에 따른 동작들을 초기화 - // Initialize WaitCustomer actions - base.InitializeInteraction(interactionType); - } - - public RestaurantOrderInteractionType GetInteractionSubsystemType() - { - return _currentRestaurantOrderInteractionType; - } - } -} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteractionSubsystem.cs b/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteractionSubsystem.cs new file mode 100644 index 000000000..40b619387 --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteractionSubsystem.cs @@ -0,0 +1,59 @@ +using System; +using UnityEngine; + +namespace DDD +{ + [Flags] + public enum RestaurantOrderType : uint + { + Wait = 0, + Order = 1u << 0, + Serve = 1u << 1, + } + + public class RestaurantOrderInteractionSubsystem : MonoBehaviour, IInteractionSubsystemObject + { + [SerializeField] protected RestaurantOrderType orderType = RestaurantOrderType.Wait; + private RestaurantOrderType currentRestaurantOrderType; + + public Type GetSubsystemEnumType() => typeof(RestaurantOrderType); + + private void Start() + { + currentRestaurantOrderType = orderType; + } + + public bool CanInteract() + { + // 현재 RestaurantOrderInteractionType를 수행할 수 있는지? + if (GetInteractionSubsystemType() == RestaurantOrderType.Wait) + { + Debug.Assert(false); // TODO + // Check WaitCustomer + return true; + } + return false; + } + + public bool OnInteracted(IInteractor interactor, ScriptableObject payloadSo = null) + { + // _currentRestaurantOrderInteractionType에 따라 동작이 달라지겠지 + if (GetInteractionSubsystemType() == RestaurantOrderType.Wait) + { + // DO WAIT CUSTOMER + } + + return true; + } + + public void InitializeSubsystem() + { + + } + + public RestaurantOrderType GetInteractionSubsystemType() + { + return currentRestaurantOrderType; + } + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteraction.cs.meta b/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteractionSubsystem.cs.meta similarity index 100% rename from Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteraction.cs.meta rename to Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteractionSubsystem.cs.meta diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionComponent.cs b/Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionComponent.cs index d88aefe9c..72090e59d 100644 --- a/Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionComponent.cs +++ b/Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionComponent.cs @@ -1,19 +1,49 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; using UnityEngine; +using Sirenix.OdinInspector; namespace DDD { + public static class RestaurantInteractionSubsystems + { + public static Dictionary TypeToSubsystem = new() + { + {InteractionType.RestaurantOrder, typeof(RestaurantOrderInteractionSubsystem)} + }; + } + public class RestaurantInteractionComponent : MonoBehaviour, IInteractable { + // Single interaction type + [ValueDropdown("GetAllInteractionTypes")] [SerializeField] protected InteractionType _interactionType = InteractionType.None; [SerializeField] protected InteractionExecutionParameters _executionParameters = new InteractionExecutionParameters(1f); [SerializeField] protected InteractionDisplayParameters _displayParameters = new InteractionDisplayParameters(""); [SerializeField] protected GameFlowState _interactionAvailableFlows; [SerializeField] private Transform[] _aiInteractionPoints; - + + private Dictionary _subsystems = new(); + + private static IEnumerable GetAllInteractionTypes() + { + return System.Enum.GetValues(typeof(InteractionType)) + .Cast() + .Where(x => x != InteractionType.All); // All은 제외 + } + public virtual bool CanInteract() { - return !IsInteractionHidden(); + bool isInteractionVisible = !IsInteractionHidden(); + bool hasValidSubsystem = true; + if (HasSubsystem(_interactionType)) + { + hasValidSubsystem = GetSubsystem(_interactionType).CanInteract(); + } + return isInteractionVisible && hasValidSubsystem; } public virtual bool IsInteractionHidden() @@ -31,6 +61,10 @@ public virtual bool OnInteracted(IInteractor interactor, ScriptableObject payloa } bool interactionResult = RestaurantInteractionEvents.RestaurantInteraction.RequestInteraction(interactor.GetInteractorGameObject(), GetInteractableGameObject(), GetInteractionType(), payloadSo, true); + if (HasSubsystem(_interactionType)) + { + interactionResult &= GetSubsystem(_interactionType).OnInteracted(interactor, payloadSo); + } return interactionResult; } @@ -47,6 +81,33 @@ public GameObject GetInteractableGameObject() public virtual void InitializeInteraction(InteractionType interactionType) { _interactionType = interactionType; + + InitializeSubsystems(); + } + + private void InitializeSubsystems() + { + // Initialize Interaction Subsystems + bool hasSubsystemType = RestaurantInteractionSubsystems.TypeToSubsystem.TryGetValue(_interactionType, out var subsystemType); + if (!hasSubsystemType) return; + + var subsystem = gameObject.GetComponent(subsystemType) as IInteractionSubsystemObject; + if (subsystem == null) + { + subsystem = gameObject.AddComponent(subsystemType) as IInteractionSubsystemObject; + } + _subsystems.Add(_interactionType, subsystem); + subsystem?.InitializeSubsystem(); + } + + private bool HasSubsystem(InteractionType interactionType) + { + return _subsystems.ContainsKey(interactionType); + } + + private IInteractionSubsystemObject GetSubsystem(InteractionType interactionType) + { + return _subsystems.GetValueOrDefault(interactionType) as IInteractionSubsystemObject; } // 새로운 스트럭트 기반 메서드들 diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrderSolver.cs b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrderSolver.cs index 3f8089dee..38bfe2d87 100644 --- a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrderSolver.cs +++ b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrderSolver.cs @@ -1,48 +1,56 @@ +using System; +using System.Collections.Generic; +using DDD.RestaurantOrders; using UnityEngine; namespace DDD { - public class RestaurantOrderSolver : MonoBehaviour, IInteractionSolver, IInteractionSubsystemSolver + public static class RestaurantOrderSolvers { + public static Dictionary TypeToOrderSolver = new() + { + { RestaurantOrderType.Wait, typeof(RestaurantOrderSolver_Wait) }, + { RestaurantOrderType.Order, typeof(RestaurantOrderSolver_Order) }, + { RestaurantOrderType.Serve, typeof(RestaurantOrderSolver_Serve) } + }; + } + + public class RestaurantOrderSolver : MonoBehaviour, IInteractionSolver + { + private Dictionary> _solvers = new(); + + private void Start() + { + foreach (var orderSolver in RestaurantOrderSolvers.TypeToOrderSolver) + { + var solver = (IInteractionSubsystemSolver)gameObject.AddComponent(orderSolver.Value); + _solvers.Add(orderSolver.Key, solver); + } + } + public bool ExecuteInteraction(IInteractor interactor, IInteractable interactable, ScriptableObject payloadSo = null) { - return ExecuteInteractionSubsystem(interactor, interactable, payloadSo); + return TryGetSolver(interactable, out var solver) && + solver.ExecuteInteractionSubsystem(interactor, interactable, payloadSo); } public bool CanExecuteInteraction(IInteractor interactor = null, IInteractable interactable = null, ScriptableObject payloadSo = null) { - return CanExecuteInteractionSubsystem(interactor, interactable, payloadSo); + return TryGetSolver(interactable, out var solver) && + solver.CanExecuteInteractionSubsystem(interactor, interactable, payloadSo); } - public bool ExecuteInteractionSubsystem(IInteractor interactor, IInteractable interactable, ScriptableObject payloadSo = null) + // Solver를 가져오는 공통 로직 + private bool TryGetSolver(IInteractable interactable, out IInteractionSubsystemSolver solver) { - if (interactable is IInteractionSubsystemObject subsystem) - { - RestaurantOrderInteractionType interactionType = subsystem.GetInteractionSubsystemType(); - // Can I solve this interaction type? - if (interactionType == RestaurantOrderInteractionType.WaitCustomer) - { - // DO SOMETHING!!! - return true; - } - } - return false; - } - - public bool CanExecuteInteractionSubsystem(IInteractor interactor = null, IInteractable interactable = null, - ScriptableObject payloadSo = null) - { - if (interactable is IInteractionSubsystemObject subsystem) - { - RestaurantOrderInteractionType interactionType = subsystem.GetInteractionSubsystemType(); - // Can I solve this interaction type? - if (interactionType == RestaurantOrderInteractionType.WaitCustomer) - { - return true; - } - } - return false; + solver = null; + + if (interactable is not IInteractionSubsystemObject subsystem) + return false; + + var type = subsystem.GetInteractionSubsystemType(); + return _solvers.TryGetValue(type, out solver); } } } \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders.meta b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders.meta new file mode 100644 index 000000000..9518f5240 --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0a7eea23af674c2aa6ffc20bd5801efb +timeCreated: 1755672003 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Order.cs b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Order.cs new file mode 100644 index 000000000..edee1940d --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Order.cs @@ -0,0 +1,19 @@ +using UnityEngine; + +namespace DDD.RestaurantOrders +{ + public class RestaurantOrderSolver_Order : MonoBehaviour, IInteractionSubsystemSolver + { + public bool ExecuteInteractionSubsystem(IInteractor interactor, IInteractable interactable, ScriptableObject payloadSo = null) + { + // TODO : DO SOMETHING!!! + return true; + } + + public bool CanExecuteInteractionSubsystem(IInteractor interactor = null, IInteractable interactable = null, + ScriptableObject payloadSo = null) + { + return true; + } + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Order.cs.meta b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Order.cs.meta new file mode 100644 index 000000000..a47a2ccd0 --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Order.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3afc1759b02e4230967b3b72fe354ea3 +timeCreated: 1755672492 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Serve.cs b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Serve.cs new file mode 100644 index 000000000..263c65494 --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Serve.cs @@ -0,0 +1,19 @@ +using UnityEngine; + +namespace DDD.RestaurantOrders +{ + public class RestaurantOrderSolver_Serve : MonoBehaviour, IInteractionSubsystemSolver + { + public bool ExecuteInteractionSubsystem(IInteractor interactor, IInteractable interactable, ScriptableObject payloadSo = null) + { + // TODO : DO SOMETHING!!! + return true; + } + + public bool CanExecuteInteractionSubsystem(IInteractor interactor = null, IInteractable interactable = null, + ScriptableObject payloadSo = null) + { + return true; + } + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Serve.cs.meta b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Serve.cs.meta new file mode 100644 index 000000000..3186c427f --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Serve.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4df15c40347044648623d5932bb0724e +timeCreated: 1755672501 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Wait.cs b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Wait.cs new file mode 100644 index 000000000..14f6f77d9 --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Wait.cs @@ -0,0 +1,19 @@ +using UnityEngine; + +namespace DDD.RestaurantOrders +{ + public class RestaurantOrderSolver_Wait : MonoBehaviour, IInteractionSubsystemSolver + { + public bool ExecuteInteractionSubsystem(IInteractor interactor, IInteractable interactable, ScriptableObject payloadSo = null) + { + // TODO : DO SOMETHING!!! + return true; + } + + public bool CanExecuteInteractionSubsystem(IInteractor interactor = null, IInteractable interactable = null, + ScriptableObject payloadSo = null) + { + return true; + } + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Wait.cs.meta b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Wait.cs.meta new file mode 100644 index 000000000..3e85dc4c2 --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Wait.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: cc81a22ff98a4b42b45ad27219ec05fa +timeCreated: 1755672371 \ No newline at end of file From 0cce2efe62bc0440238b71e491702948be6b1c01 Mon Sep 17 00:00:00 2001 From: NTG Date: Wed, 20 Aug 2025 17:04:51 +0900 Subject: [PATCH 08/36] =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=82=B4?= =?UTF-8?q?=EC=9A=A9=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PopupUis/RestaurantManagementUi.prefab | 9 +- .../GameUi/New/NewRestaurantManagementUi.cs | 275 ++++++++++++++++++ .../New/NewRestaurantManagementUi.cs.meta | 3 + .../New/RestaurantManagementViewModel.cs | 241 +++++++++++++++ .../New/RestaurantManagementViewModel.cs.meta | 3 + .../New/ViewModels/Base/SimpleViewModel.cs | 2 +- .../New/Views/Base/IntegratedBasePopupUi.cs | 164 +++++++++-- 7 files changed, 662 insertions(+), 35 deletions(-) create mode 100644 Assets/_DDD/_Scripts/GameUi/New/NewRestaurantManagementUi.cs create mode 100644 Assets/_DDD/_Scripts/GameUi/New/NewRestaurantManagementUi.cs.meta create mode 100644 Assets/_DDD/_Scripts/GameUi/New/RestaurantManagementViewModel.cs create mode 100644 Assets/_DDD/_Scripts/GameUi/New/RestaurantManagementViewModel.cs.meta diff --git a/Assets/_DDD/_Addressables/Prefabs/Uis/GameUi/PopupUis/RestaurantManagementUi.prefab b/Assets/_DDD/_Addressables/Prefabs/Uis/GameUi/PopupUis/RestaurantManagementUi.prefab index 1a346f07d..1cb4e065e 100644 --- a/Assets/_DDD/_Addressables/Prefabs/Uis/GameUi/PopupUis/RestaurantManagementUi.prefab +++ b/Assets/_DDD/_Addressables/Prefabs/Uis/GameUi/PopupUis/RestaurantManagementUi.prefab @@ -6311,7 +6311,7 @@ PrefabInstance: m_AddedComponents: - targetCorrespondingSourceObject: {fileID: 6952779389930089995, guid: 4f2bf029cb06b084ba41defc8fc76731, type: 3} insertIndex: -1 - addedObject: {fileID: 2438716745211137680} + addedObject: {fileID: 7197937761328488698} m_SourcePrefab: {fileID: 100100000, guid: 4f2bf029cb06b084ba41defc8fc76731, type: 3} --- !u!224 &4720669467062659157 stripped RectTransform: @@ -6323,7 +6323,7 @@ GameObject: m_CorrespondingSourceObject: {fileID: 6952779389930089995, guid: 4f2bf029cb06b084ba41defc8fc76731, type: 3} m_PrefabInstance: {fileID: 4463400116329503023} m_PrefabAsset: {fileID: 0} ---- !u!114 &2438716745211137680 +--- !u!114 &7197937761328488698 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -6332,10 +6332,10 @@ MonoBehaviour: m_GameObject: {fileID: 6740783381500491556} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 46c8396c996c804449b383960b44e812, type: 3} + m_Script: {fileID: 11500000, guid: 8a2e0954aa144633aad86e53dc80a46a, type: 3} m_Name: m_EditorClassIdentifier: - _enableBlockImage: 1 + _enableBlockImage: 0 _uiActionsInputBinding: {fileID: 11400000, guid: 8073fcaf56fc7c34e996d0d47044f146, type: 2} _checklistView: {fileID: 7075966153492927588} _inventoryView: {fileID: 3570087040626823091} @@ -6346,7 +6346,6 @@ MonoBehaviour: _menuCategoryTabs: {fileID: 6805049896193344908} _cookwareCategoryTabs: {fileID: 6628923975427483430} _completeBatchFilledImage: {fileID: 2965326806322860544} - _holdCompleteTime: 0.5 --- !u!1001 &4530765275021007961 PrefabInstance: m_ObjectHideFlags: 0 diff --git a/Assets/_DDD/_Scripts/GameUi/New/NewRestaurantManagementUi.cs b/Assets/_DDD/_Scripts/GameUi/New/NewRestaurantManagementUi.cs new file mode 100644 index 000000000..6f7404022 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/NewRestaurantManagementUi.cs @@ -0,0 +1,275 @@ +using UnityEngine; +using UnityEngine.UI; +using UnityEngine.EventSystems; +using UnityEngine.InputSystem; +using DDD.MVVM; + +namespace DDD +{ + /// + /// MVVM 패턴을 적용한 새로운 레스토랑 관리 UI + /// 기존 RestaurantManagementUi의 기능을 ViewModel과 분리하여 구현 + /// + public class NewRestaurantManagementUi : IntegratedBasePopupUi + { + [Header("Sub Views")] + [SerializeField] private ChecklistView _checklistView; + [SerializeField] private InventoryView _inventoryView; + [SerializeField] private ItemDetailView _itemDetailView; + [SerializeField] private TodayMenuView _todayMenuView; + [SerializeField] private TodayRestaurantStateView _todayRestaurantStateView; + + [Header("Tab Groups")] + [SerializeField] private TabGroupUi _sectionTabs; + [SerializeField] private TabGroupUi _menuCategoryTabs; + [SerializeField] private TabGroupUi _cookwareCategoryTabs; + + [Header("Hold Progress UI")] + [SerializeField, BindTo(nameof(RestaurantManagementViewModel.NormalizedHoldProgress))] + private Image _completeBatchFilledImage; + + protected override void Awake() + { + base.Awake(); + + SetupViewModelEvents(); + } + + protected override void Update() + { + base.Update(); + + if (_viewModel != null && _viewModel.IsHolding) + { + _viewModel.UpdateHoldProgress(); + } + } + + public override void Open(OpenPopupUiEvent evt) + { + base.Open(evt); + + InitializeViews(); + SetupTabs(); + } + + protected override GameObject GetInitialSelected() + { + // ViewModel의 현재 상태에 따라 초기 선택 UI 결정 + var inventoryInitialSelected = _inventoryView.GetInitialSelected(); + if (inventoryInitialSelected) return inventoryInitialSelected; + + var menuCategoryButton = _menuCategoryTabs.GetFirstInteractableButton(); + if (menuCategoryButton != null && menuCategoryButton.activeInHierarchy) + return menuCategoryButton; + + var cookwareCategoryButton = _cookwareCategoryTabs.GetFirstInteractableButton(); + if (cookwareCategoryButton != null && cookwareCategoryButton.activeInHierarchy) + return cookwareCategoryButton; + + return null; + } + + protected override void SetupBindings() + { + // Attribute 기반 자동 바인딩이 처리됨 + // 추가적인 수동 바인딩이 필요한 경우 여기에 구현 + } + + protected override void HandleCustomPropertyChanged(string propertyName) + { + switch (propertyName) + { + case nameof(RestaurantManagementViewModel.CurrentSection): + UpdateSectionTabs(); + break; + case nameof(RestaurantManagementViewModel.CurrentCategory): + UpdateCategoryTabs(); + break; + } + } + + private void SetupViewModelEvents() + { + if (!_viewModel) return; + + _viewModel.OnBatchCompleted = HandleBatchCompleted; + _viewModel.OnChecklistFailed = HandleChecklistFailed; + _viewModel.OnMenuSectionSelected = HandleMenuSectionSelected; + _viewModel.OnCookwareSectionSelected = HandleCookwareSectionSelected; + _viewModel.OnCategoryChanged = HandleCategoryChanged; + _viewModel.OnTabMoved = HandleTabMoved; + _viewModel.OnInteractRequested = HandleInteractRequested; + _viewModel.OnCloseRequested = HandleCloseRequested; + _viewModel.OnMenuCategorySelected = HandleMenuCategorySelected; + } + + private void InitializeViews() + { + _checklistView.Initalize(); + _inventoryView.Initialize(); + _itemDetailView.Initialize(); + _todayMenuView.Initialize(); + _todayRestaurantStateView.Initialize(); + } + + private void SetupTabs() + { + SetupCategoryTabs(); + InitializeTabGroups(); + SelectInitialTabs(); + } + + private void SetupCategoryTabs() + { + _menuCategoryTabs.UseDefaultAllowedValues(); + _cookwareCategoryTabs.UseDefaultAllowedValues(); + } + + private void InitializeTabGroups() + { + _sectionTabs.Initialize(OnSectionTabSelected); + _menuCategoryTabs.Initialize(OnCategoryTabSelected); + _cookwareCategoryTabs.Initialize(OnCategoryTabSelected); + } + + private void SelectInitialTabs() + { + _sectionTabs.SelectFirstTab(); + _menuCategoryTabs.SelectFirstTab(); + } + + private void UpdateSectionTabs() + { + if (_viewModel == null) return; + _sectionTabs.SelectTab((int)_viewModel.CurrentSection); + } + + private void UpdateCategoryTabs() + { + if (_viewModel == null) return; + + switch (_viewModel.CurrentSection) + { + case SectionButtonType.Menu: + _menuCategoryTabs.SelectTab((int)_viewModel.CurrentCategory); + break; + case SectionButtonType.Cookware: + _cookwareCategoryTabs.SelectTab((int)_viewModel.CurrentCategory); + break; + } + } + + // ViewModel 이벤트 핸들러들 + private void HandleBatchCompleted() + { + Close(); + } + + private void HandleChecklistFailed() + { + var evt = GameEvents.OpenPopupUiEvent; + evt.UiType = typeof(ConfirmUi); + evt.IsCancelButtonVisible = true; + evt.NewMessageKey = "checklist_failed_message"; + evt.OnConfirm = ClosePanel; + EventBus.Broadcast(evt); + } + + private void HandleMenuSectionSelected() + { + _menuCategoryTabs.SelectFirstTab(); + } + + private void HandleCookwareSectionSelected() + { + _cookwareCategoryTabs.SelectFirstTab(); + } + + private void HandleCategoryChanged(InventoryCategoryType category) + { + _inventoryView.UpdateCategoryView(category); + _itemDetailView.UpdateCategory(category); + } + + private void HandleTabMoved(int direction) + { + _sectionTabs.Move(direction); + } + + private void HandleInteractRequested() + { + var selected = EventSystem.current.currentSelectedGameObject; + var interactable = selected?.GetComponent(); + interactable?.OnInteract(); + } + + private void HandleCloseRequested() + { + Close(); + } + + private void HandleMenuCategorySelected(InventoryCategoryType category) + { + _menuCategoryTabs.SelectTab((int)category); + } + + // UI 이벤트 핸들러들 (TabGroupUi 콜백) + private void OnSectionTabSelected(int sectionValue) + { + _viewModel?.SetSection((SectionButtonType)sectionValue); + } + + private void OnCategoryTabSelected(int categoryValue) + { + _viewModel?.SetCategory((InventoryCategoryType)categoryValue); + } + + // 입력 처리 - ViewModel로 위임 + protected override bool OnInputPerformed(RestaurantUiActions actionEnum, InputAction.CallbackContext context) + { + var isHandled = base.OnInputPerformed(actionEnum, context); + + if (isHandled && _viewModel != null) + { + switch (actionEnum) + { + case RestaurantUiActions.Cancel: + _viewModel.CloseUi(); + break; + case RestaurantUiActions.PreviousTab: + _viewModel.MoveTab(-1); + break; + case RestaurantUiActions.NextTab: + _viewModel.MoveTab(1); + break; + case RestaurantUiActions.Interact1: + _viewModel.InteractWithSelected(); + break; + case RestaurantUiActions.Interact2: + _viewModel.StartHold(); + break; + } + } + + return isHandled; + } + + protected override bool OnInputCanceled(RestaurantUiActions actionEnum, InputAction.CallbackContext context) + { + var isHandled = base.OnInputCanceled(actionEnum, context); + + if (isHandled && _viewModel != null) + { + switch (actionEnum) + { + case RestaurantUiActions.Interact2: + _viewModel.CancelHold(); + break; + } + } + + return isHandled; + } + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/NewRestaurantManagementUi.cs.meta b/Assets/_DDD/_Scripts/GameUi/New/NewRestaurantManagementUi.cs.meta new file mode 100644 index 000000000..fb8636ee8 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/NewRestaurantManagementUi.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8a2e0954aa144633aad86e53dc80a46a +timeCreated: 1755673386 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/RestaurantManagementViewModel.cs b/Assets/_DDD/_Scripts/GameUi/New/RestaurantManagementViewModel.cs new file mode 100644 index 000000000..e88b79e83 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/RestaurantManagementViewModel.cs @@ -0,0 +1,241 @@ +using System.Linq; +using DDD.MVVM; +using UnityEngine; + +namespace DDD +{ + /// + /// 레스토랑 관리 UI의 ViewModel + /// 기존 RestaurantManagementUi의 상태 관리와 비즈니스 로직을 담당 + /// + public class RestaurantManagementViewModel : SimpleViewModel, IEventHandler + { + // 홀드 진행 상태 관리 + private bool _isHolding; + private float _elapsedTime; + private float _holdCompleteTime = 1f; + + // 탭 상태 관리 + private SectionButtonType _currentSection = SectionButtonType.Menu; + private InventoryCategoryType _currentCategory = InventoryCategoryType.Food; + + /// + /// 현재 홀드 상태 + /// + public bool IsHolding + { + get => _isHolding; + private set => SetField(ref _isHolding, value); + } + + /// + /// 홀드 진행 시간 (0.0 ~ 1.0) + /// + public float HoldProgress + { + get => _elapsedTime; + private set => SetField(ref _elapsedTime, value); + } + + /// + /// 홀드 완료에 필요한 시간 + /// + public float HoldCompleteTime + { + get => _holdCompleteTime; + set => SetField(ref _holdCompleteTime, value); + } + + /// + /// 현재 선택된 섹션 + /// + public SectionButtonType CurrentSection + { + get => _currentSection; + set => SetField(ref _currentSection, value); + } + + /// + /// 현재 선택된 카테고리 + /// + public InventoryCategoryType CurrentCategory + { + get => _currentCategory; + set => SetField(ref _currentCategory, value); + } + + /// + /// 배치 완료 가능 여부 (체크리스트 완료 상태) + /// + public bool CanCompleteBatch => + RestaurantState.Instance.ManagementState.GetChecklistStates().All(state => state); + + /// + /// 홀드 진행률을 0~1 범위로 변환한 값 + /// + public float NormalizedHoldProgress => HoldCompleteTime <= 0f ? 1f : Mathf.Clamp01(HoldProgress / HoldCompleteTime); + + public override void Initialize() + { + base.Initialize(); + RegisterEvents(); + ResetHoldState(); + } + + public override void Cleanup() + { + base.Cleanup(); + UnregisterEvents(); + } + + private void RegisterEvents() + { + EventBus.Register(this); + } + + private void UnregisterEvents() + { + EventBus.Unregister(this); + } + + /// + /// 홀드 진행 업데이트 (View에서 Update마다 호출) + /// + public void UpdateHoldProgress() + { + if (!IsHolding) return; + + if (HoldCompleteTime <= 0f) + { + ProcessCompleteBatch(); + return; + } + + var deltaTime = Time.deltaTime; + HoldProgress += deltaTime; + + if (HoldProgress >= HoldCompleteTime) + { + ProcessCompleteBatch(); + } + + // UI 업데이트를 위한 정규화된 진행률 알림 + OnPropertyChanged(nameof(NormalizedHoldProgress)); + } + + /// + /// 홀드 시작 + /// + public void StartHold() + { + IsHolding = true; + HoldProgress = 0f; + OnPropertyChanged(nameof(NormalizedHoldProgress)); + } + + /// + /// 홀드 취소 + /// + public void CancelHold() + { + ResetHoldState(); + } + + private void ResetHoldState() + { + IsHolding = false; + HoldProgress = 0f; + OnPropertyChanged(nameof(NormalizedHoldProgress)); + } + + /// + /// 배치 완료 처리 + /// + private void ProcessCompleteBatch() + { + ResetHoldState(); + + if (CanCompleteBatch) + { + // 배치 완료 - UI 닫기 이벤트 발생 + OnBatchCompleted?.Invoke(); + } + else + { + // 체크리스트 미완료 - 실패 팝업 표시 이벤트 발생 + OnChecklistFailed?.Invoke(); + } + } + + /// + /// 섹션 탭 선택 처리 + /// + public void SetSection(SectionButtonType section) + { + CurrentSection = section; + + // 섹션 변경 시 해당 섹션의 첫 번째 카테고리로 설정 + switch (section) + { + case SectionButtonType.Menu: + OnMenuSectionSelected?.Invoke(); + break; + case SectionButtonType.Cookware: + OnCookwareSectionSelected?.Invoke(); + break; + } + } + + /// + /// 카테고리 탭 선택 처리 + /// + public void SetCategory(InventoryCategoryType category) + { + CurrentCategory = category; + OnCategoryChanged?.Invoke(category); + } + + /// + /// 탭 이동 (이전/다음) + /// + public void MoveTab(int direction) + { + OnTabMoved?.Invoke(direction); + } + + /// + /// 현재 선택된 UI 요소와 상호작용 + /// + public void InteractWithSelected() + { + OnInteractRequested?.Invoke(); + } + + /// + /// UI 닫기 + /// + public void CloseUi() + { + OnCloseRequested?.Invoke(); + } + + // View에서 구독할 이벤트들 + public System.Action OnBatchCompleted; + public System.Action OnChecklistFailed; + public System.Action OnMenuSectionSelected; + public System.Action OnCookwareSectionSelected; + public System.Action OnCategoryChanged; + public System.Action OnTabMoved; + public System.Action OnInteractRequested; + public System.Action OnCloseRequested; + + // 이벤트 핸들러 + public void Invoke(TodayMenuRemovedEvent evt) + { + SetCategory(evt.InventoryCategoryType); + OnMenuCategorySelected?.Invoke(evt.InventoryCategoryType); + } + + public System.Action OnMenuCategorySelected; + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/RestaurantManagementViewModel.cs.meta b/Assets/_DDD/_Scripts/GameUi/New/RestaurantManagementViewModel.cs.meta new file mode 100644 index 000000000..10b0cc221 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/New/RestaurantManagementViewModel.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 80dee5e1862248aab26236036049e5fc +timeCreated: 1755673405 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/ViewModels/Base/SimpleViewModel.cs b/Assets/_DDD/_Scripts/GameUi/New/ViewModels/Base/SimpleViewModel.cs index 4f7310265..4ab0a5655 100644 --- a/Assets/_DDD/_Scripts/GameUi/New/ViewModels/Base/SimpleViewModel.cs +++ b/Assets/_DDD/_Scripts/GameUi/New/ViewModels/Base/SimpleViewModel.cs @@ -3,7 +3,7 @@ using System.Runtime.CompilerServices; using UnityEngine; -namespace DDD.MVVM +namespace DDD { public abstract class SimpleViewModel : MonoBehaviour, INotifyPropertyChanged { diff --git a/Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBasePopupUi.cs b/Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBasePopupUi.cs index f5139fa52..6805b6df9 100644 --- a/Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBasePopupUi.cs +++ b/Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBasePopupUi.cs @@ -1,31 +1,43 @@ using System; using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; using UnityEngine; using UnityEngine.EventSystems; -using DDD.MVVM; +using UnityEngine.UI; using Sirenix.OdinInspector; using UnityEngine.InputSystem; +using DDD.MVVM; namespace DDD { - public abstract class IntegratedBasePopupUi : IntegratedBaseUi + public abstract class IntegratedBasePopupUi : BasePopupUi where TInputEnum : Enum where TViewModel : SimpleViewModel { [SerializeField, Required] protected BaseUiActionsInputBinding _uiActionsInputBinding; - + protected readonly List<(InputAction action, Action handler)> _registeredHandlers = new(); + + // MVVM 기능들 + protected BindingContext _bindingContext; + protected TViewModel _viewModel; - public InputActionMaps InputActionMaps => _uiActionsInputBinding.InputActionMaps; - public bool IsTopPopup => UiManager.Instance.PopupUiState.IsTopPopup(this as BasePopupUi); + public override InputActionMaps InputActionMaps => _uiActionsInputBinding.InputActionMaps; + public bool IsTopPopup => UiManager.Instance.PopupUiState.IsTopPopup(this); protected override void Awake() { base.Awake(); - - // BasePopupUi의 기본값 적용 + _enableBlockImage = true; + _viewModel = GetComponent(); + + _bindingContext = new BindingContext(); + SetupAutoBindings(); + SetupBindings(); } protected override void OnEnable() @@ -36,8 +48,7 @@ protected override void OnEnable() protected override void Update() { base.Update(); - - // BasePopupUi의 Update 로직 구현 + if (IsOpenPanel() == false) return; var currentSelectedGameObject = EventSystem.current.currentSelectedGameObject; @@ -50,8 +61,26 @@ protected override void Update() } } } + + public override void Open(OpenPopupUiEvent evt) + { + base.Open(evt); - protected abstract GameObject GetInitialSelected(); + var initialSelected = GetInitialSelected(); + if (initialSelected != null) + { + EventSystem.current.SetSelectedGameObject(initialSelected); + } + + transform.SetAsLastSibling(); + + if (IsTopPopup) + { + InputManager.Instance.SwitchCurrentActionMap(_uiActionsInputBinding.InputActionMaps); + } + } + + protected override abstract GameObject GetInitialSelected(); protected override void TryRegister() { @@ -110,37 +139,114 @@ protected override void TryUnregister() // 입력 처리 메서드들 protected virtual bool OnInputStarted(TInputEnum actionEnum, InputAction.CallbackContext context) => IsTopPopup; + protected virtual bool OnInputPerformed(TInputEnum actionEnum, InputAction.CallbackContext context) => IsTopPopup; + protected virtual bool OnInputCanceled(TInputEnum actionEnum, InputAction.CallbackContext context) => IsTopPopup; - protected virtual bool OnInputPerformed(TInputEnum actionEnum, InputAction.CallbackContext context) => - IsTopPopup; - - protected virtual bool OnInputCanceled(TInputEnum actionEnum, InputAction.CallbackContext context) => - IsTopPopup; - - - public virtual void Open(OpenPopupUiEvent evt) + /// + /// Attribute 기반 자동 바인딩 설정 + /// + private void SetupAutoBindings() { - OpenPanel(); + var fields = GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public) + .Where(f => f.GetCustomAttribute() != null); - var initialSelected = GetInitialSelected(); - if (initialSelected != null) + foreach (var field in fields) { - EventSystem.current.SetSelectedGameObject(initialSelected); + var bindAttribute = field.GetCustomAttribute(); + SetupBinding(field, bindAttribute); } - transform.SetAsLastSibling(); + // 컬렉션 바인딩 설정 + var collectionFields = GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public) + .Where(f => f.GetCustomAttribute() != null); - if (IsTopPopup) + foreach (var field in collectionFields) { - InputManager.Instance.SwitchCurrentActionMap(_uiActionsInputBinding.InputActionMaps); + var bindAttribute = field.GetCustomAttribute(); + SetupCollectionBinding(field, bindAttribute); } } - public virtual void Close() + /// + /// 개별 필드의 바인딩 설정 + /// + private void SetupBinding(FieldInfo field, BindToAttribute bindAttribute) { - var evt = GameEvents.ClosePopupUiEvent; - evt.UiType = GetType(); - EventBus.Broadcast(evt); + 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); + } + } + + /// + /// 컬렉션 바인딩 설정 + /// + 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}"); + } + } + + /// + /// 추가 바인딩 설정 - 하위 클래스에서 구현 + /// + protected virtual void SetupBindings() { } + + /// + /// ViewModel 속성 변경 이벤트 핸들러 + /// + protected virtual void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) + { + HandleCustomPropertyChanged(e.PropertyName); + } + + /// + /// 커스텀 속성 변경 처리 (하위 클래스에서 오버라이드) + /// + protected virtual void HandleCustomPropertyChanged(string propertyName) + { + // 하위 클래스에서 구현 + } + + /// + /// UI 패널 열기 오버라이드 (ViewModel 초기화 추가) + /// + public override void OpenPanel() + { + base.OpenPanel(); + _viewModel?.Initialize(); + } + + /// + /// UI 패널 닫기 오버라이드 (ViewModel 정리 추가) + /// + public override void ClosePanel() + { + _viewModel?.Cleanup(); + base.ClosePanel(); } } } \ No newline at end of file From 6f4417cd981e8e0cbdb8f091b2b3f4a3c367c407 Mon Sep 17 00:00:00 2001 From: NTG Date: Wed, 20 Aug 2025 18:08:41 +0900 Subject: [PATCH 09/36] =?UTF-8?q?ui=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EC=9E=84=EC=8B=9C=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PopupUis/RestaurantManagementUi.prefab | 22 +- .../_DDD/_Addressables/So/PopupUiState.asset | 2 +- Assets/_DDD/_Scripts/GameUi/BaseUi.cs | 152 +++++++- .../_DDD/_Scripts/GameUi/BaseViewModelUi.cs | 74 ++++ .../_Scripts/GameUi/BaseViewModelUi.cs.meta | 3 + Assets/_DDD/_Scripts/GameUi/ConfirmUi.cs | 2 +- .../_DDD/_Scripts/GameUi/ConfirmViewModel.cs | 7 + .../_Scripts/GameUi/ConfirmViewModel.cs.meta | 3 + .../_Scripts/GameUi/New/MIGRATION_GUIDE.md | 321 ----------------- .../GameUi/New/MIGRATION_GUIDE.md.meta | 7 - .../GameUi/New/NewRestaurantManagementUi.cs | 275 --------------- .../New/NewRestaurantManagementUi.cs.meta | 3 - Assets/_DDD/_Scripts/GameUi/New/README.md | 278 --------------- .../_DDD/_Scripts/GameUi/New/README.md.meta | 7 - .../New/Views/Base/IntegratedBasePopupUi.cs | 252 -------------- .../Views/Base/IntegratedBasePopupUi.cs.meta | 2 - .../GameUi/New/Views/Base/IntegratedBaseUi.cs | 248 ------------- .../New/Views/Base/IntegratedBaseUi.cs.meta | 2 - .../Views/Examples/IntegratedInventoryView.cs | 221 ------------ .../Examples/IntegratedInventoryView.cs.meta | 2 - .../_Scripts/GameUi/PopupUi/BasePopupUi.cs | 49 ++- .../_DDD/_Scripts/GameUi/PopupUi/PopupUi.cs | 82 +++-- .../RestaurantManagementUi.cs | 329 ++++++++++-------- 23 files changed, 519 insertions(+), 1824 deletions(-) create mode 100644 Assets/_DDD/_Scripts/GameUi/BaseViewModelUi.cs create mode 100644 Assets/_DDD/_Scripts/GameUi/BaseViewModelUi.cs.meta create mode 100644 Assets/_DDD/_Scripts/GameUi/ConfirmViewModel.cs create mode 100644 Assets/_DDD/_Scripts/GameUi/ConfirmViewModel.cs.meta delete mode 100644 Assets/_DDD/_Scripts/GameUi/New/MIGRATION_GUIDE.md delete mode 100644 Assets/_DDD/_Scripts/GameUi/New/MIGRATION_GUIDE.md.meta delete mode 100644 Assets/_DDD/_Scripts/GameUi/New/NewRestaurantManagementUi.cs delete mode 100644 Assets/_DDD/_Scripts/GameUi/New/NewRestaurantManagementUi.cs.meta delete mode 100644 Assets/_DDD/_Scripts/GameUi/New/README.md delete mode 100644 Assets/_DDD/_Scripts/GameUi/New/README.md.meta delete mode 100644 Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBasePopupUi.cs delete mode 100644 Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBasePopupUi.cs.meta delete mode 100644 Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBaseUi.cs delete mode 100644 Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBaseUi.cs.meta delete mode 100644 Assets/_DDD/_Scripts/GameUi/New/Views/Examples/IntegratedInventoryView.cs delete mode 100644 Assets/_DDD/_Scripts/GameUi/New/Views/Examples/IntegratedInventoryView.cs.meta diff --git a/Assets/_DDD/_Addressables/Prefabs/Uis/GameUi/PopupUis/RestaurantManagementUi.prefab b/Assets/_DDD/_Addressables/Prefabs/Uis/GameUi/PopupUis/RestaurantManagementUi.prefab index 1cb4e065e..f33167b14 100644 --- a/Assets/_DDD/_Addressables/Prefabs/Uis/GameUi/PopupUis/RestaurantManagementUi.prefab +++ b/Assets/_DDD/_Addressables/Prefabs/Uis/GameUi/PopupUis/RestaurantManagementUi.prefab @@ -6311,7 +6311,10 @@ PrefabInstance: m_AddedComponents: - targetCorrespondingSourceObject: {fileID: 6952779389930089995, guid: 4f2bf029cb06b084ba41defc8fc76731, type: 3} insertIndex: -1 - addedObject: {fileID: 7197937761328488698} + addedObject: {fileID: 3190533777077691364} + - targetCorrespondingSourceObject: {fileID: 6952779389930089995, guid: 4f2bf029cb06b084ba41defc8fc76731, type: 3} + insertIndex: -1 + addedObject: {fileID: 245423958264921561} m_SourcePrefab: {fileID: 100100000, guid: 4f2bf029cb06b084ba41defc8fc76731, type: 3} --- !u!224 &4720669467062659157 stripped RectTransform: @@ -6323,7 +6326,7 @@ GameObject: m_CorrespondingSourceObject: {fileID: 6952779389930089995, guid: 4f2bf029cb06b084ba41defc8fc76731, type: 3} m_PrefabInstance: {fileID: 4463400116329503023} m_PrefabAsset: {fileID: 0} ---- !u!114 &7197937761328488698 +--- !u!114 &3190533777077691364 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -6332,10 +6335,11 @@ MonoBehaviour: m_GameObject: {fileID: 6740783381500491556} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 8a2e0954aa144633aad86e53dc80a46a, type: 3} + m_Script: {fileID: 11500000, guid: 46c8396c996c804449b383960b44e812, type: 3} m_Name: m_EditorClassIdentifier: _enableBlockImage: 0 + InputActionMaps: 3 _uiActionsInputBinding: {fileID: 11400000, guid: 8073fcaf56fc7c34e996d0d47044f146, type: 2} _checklistView: {fileID: 7075966153492927588} _inventoryView: {fileID: 3570087040626823091} @@ -6346,6 +6350,18 @@ MonoBehaviour: _menuCategoryTabs: {fileID: 6805049896193344908} _cookwareCategoryTabs: {fileID: 6628923975427483430} _completeBatchFilledImage: {fileID: 2965326806322860544} +--- !u!114 &245423958264921561 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6740783381500491556} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 80dee5e1862248aab26236036049e5fc, type: 3} + m_Name: + m_EditorClassIdentifier: --- !u!1001 &4530765275021007961 PrefabInstance: m_ObjectHideFlags: 0 diff --git a/Assets/_DDD/_Addressables/So/PopupUiState.asset b/Assets/_DDD/_Addressables/So/PopupUiState.asset index 2283d5b74..f792667ef 100644 --- a/Assets/_DDD/_Addressables/So/PopupUiState.asset +++ b/Assets/_DDD/_Addressables/So/PopupUiState.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ce0f58b16cd51536806732f34bb1c1c46f59b6ee19b1d85e982978d11c370cee +oid sha256:a2ffcd5421ab4902f8d7df18de785daba51fd284682cec5bc8b83e3f87a1debb size 1962 diff --git a/Assets/_DDD/_Scripts/GameUi/BaseUi.cs b/Assets/_DDD/_Scripts/GameUi/BaseUi.cs index 448195596..05871ee2a 100644 --- a/Assets/_DDD/_Scripts/GameUi/BaseUi.cs +++ b/Assets/_DDD/_Scripts/GameUi/BaseUi.cs @@ -1,28 +1,39 @@ using System; +using System.ComponentModel; +using System.Linq; +using System.Reflection; using UnityEngine; +using UnityEngine.UI; +using DDD.MVVM; namespace DDD { public abstract class BaseUi : MonoBehaviour { + [SerializeField] protected bool _enableBlockImage; + protected CanvasGroup _canvasGroup; protected GameObject _blockImage; protected GameObject _panel; + protected BindingContext _bindingContext; + public virtual bool IsBlockingTime => false; - public virtual bool IsOpen => _panel.activeSelf; - - [SerializeField] protected bool _enableBlockImage; + public virtual bool IsOpen => _panel != null && _panel.activeSelf; protected virtual void Awake() { _canvasGroup = GetComponent(); - _panel = transform.Find(CommonConstants.Panel).gameObject; - _blockImage = transform.Find(CommonConstants.BlockImage).gameObject; + _panel = transform.Find(CommonConstants.Panel)?.gameObject; + _blockImage = transform.Find(CommonConstants.BlockImage)?.gameObject; + + _bindingContext = new BindingContext(); + SetupAutoBindings(); + SetupBindings(); } protected virtual void OnEnable() { - + } protected virtual void Start() @@ -33,22 +44,24 @@ protected virtual void Start() protected virtual void Update() { - + } protected virtual void OnDisable() { - + } protected virtual void OnDestroy() { TryUnregister(); + _bindingContext?.Dispose(); } protected virtual void TryRegister() { } protected virtual void TryUnregister() { } + // BaseUi 메서드들을 직접 구현 public virtual void OpenPanel() { if (_enableBlockImage) @@ -71,10 +84,127 @@ public virtual void ClosePanel() public virtual void SetUiInteractable(bool active) { - _canvasGroup.interactable = active; - _canvasGroup.blocksRaycasts = active; + if (_canvasGroup != null) + { + _canvasGroup.interactable = active; + _canvasGroup.blocksRaycasts = active; + } } - public bool IsOpenPanel() => _panel.activeInHierarchy; + public bool IsOpenPanel() => _panel && _panel.activeInHierarchy; + + /// + /// Attribute 기반 자동 바인딩 설정 + /// + private void SetupAutoBindings() + { + var fields = GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public) + .Where(f => f.GetCustomAttribute() != null); + + foreach (var field in fields) + { + var bindAttribute = field.GetCustomAttribute(); + SetupBinding(field, bindAttribute); + } + + // 컬렉션 바인딩 설정 + var collectionFields = GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public) + .Where(f => f.GetCustomAttribute() != null); + + foreach (var field in collectionFields) + { + var bindAttribute = field.GetCustomAttribute(); + SetupCollectionBinding(field, bindAttribute); + } + } + + /// + /// 개별 필드의 바인딩 설정 + /// + 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); + } + } + + /// + /// 컬렉션 바인딩 설정 + /// + 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}"); + } + } + + /// + /// 추가 바인딩 설정 - 하위 클래스에서 구현 + /// + protected virtual void SetupBindings() { } + + /// + /// ViewModel 속성 변경 이벤트 핸들러 + /// + protected virtual void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) + { + HandleCustomPropertyChanged(e.PropertyName); + } + + /// + /// 커스텀 속성 변경 처리 (하위 클래스에서 오버라이드) + /// + 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); + } } } \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/BaseViewModelUi.cs b/Assets/_DDD/_Scripts/GameUi/BaseViewModelUi.cs new file mode 100644 index 000000000..6a3a6b243 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/BaseViewModelUi.cs @@ -0,0 +1,74 @@ +namespace DDD +{ + public class BaseViewModelUi : BaseUi where TViewModel : SimpleViewModel + { + protected TViewModel _viewModel; + + protected override void Awake() + { + base.Awake(); + + _viewModel = GetComponent(); + } + + protected override void OnEnable() + { + base.OnEnable(); + + if (_viewModel && _bindingContext != null) + { + _bindingContext.SetDataContext(_viewModel); + _viewModel.PropertyChanged += OnViewModelPropertyChanged; + } + } + + protected override void OnDisable() + { + base.OnDisable(); + + if (_viewModel != null) + { + _viewModel.PropertyChanged -= OnViewModelPropertyChanged; + } + } + + public override void OpenPanel() + { + base.OpenPanel(); + + _viewModel?.Initialize(); + } + + public override void ClosePanel() + { + base.ClosePanel(); + + _viewModel?.Cleanup(); + } + + /// + /// ViewModel 메서드 호출 헬퍼 + /// + protected void InvokeViewModelMethod(string methodName, params object[] parameters) + { + if (_viewModel == null) return; + + var method = _viewModel.GetType().GetMethod(methodName); + method?.Invoke(_viewModel, parameters); + } + + /// + /// ViewModel 속성 설정 헬퍼 + /// + 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); + } + } + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/BaseViewModelUi.cs.meta b/Assets/_DDD/_Scripts/GameUi/BaseViewModelUi.cs.meta new file mode 100644 index 000000000..395a2f0b8 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/BaseViewModelUi.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: df6384ea09a44f188d636ca7ee47db13 +timeCreated: 1755678434 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/ConfirmUi.cs b/Assets/_DDD/_Scripts/GameUi/ConfirmUi.cs index 0d6015b27..c0f8ec30f 100644 --- a/Assets/_DDD/_Scripts/GameUi/ConfirmUi.cs +++ b/Assets/_DDD/_Scripts/GameUi/ConfirmUi.cs @@ -8,7 +8,7 @@ namespace DDD { - public class ConfirmUi : PopupUi + public class ConfirmUi : PopupUi { [SerializeField] private TextMeshProUGUI _messageLabel; [SerializeField] private LocalizeStringEvent _messageLabelLocalizeStringEvent; diff --git a/Assets/_DDD/_Scripts/GameUi/ConfirmViewModel.cs b/Assets/_DDD/_Scripts/GameUi/ConfirmViewModel.cs new file mode 100644 index 000000000..c64f2fdcc --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/ConfirmViewModel.cs @@ -0,0 +1,7 @@ +namespace DDD +{ + public class ConfirmViewModel : SimpleViewModel + { + + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/ConfirmViewModel.cs.meta b/Assets/_DDD/_Scripts/GameUi/ConfirmViewModel.cs.meta new file mode 100644 index 000000000..6c174b2b8 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/ConfirmViewModel.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 7552fc9cc76345e09148a145ed7799a5 +timeCreated: 1755679705 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/MIGRATION_GUIDE.md b/Assets/_DDD/_Scripts/GameUi/New/MIGRATION_GUIDE.md deleted file mode 100644 index 5c57932cb..000000000 --- a/Assets/_DDD/_Scripts/GameUi/New/MIGRATION_GUIDE.md +++ /dev/null @@ -1,321 +0,0 @@ -# 기존 UI 시스템을 MVVM으로 마이그레이션 가이드 - -Unity 프로젝트에서 기존 UI 시스템(BaseUi, BasePopupUi, PopupUi)을 MVVM 패턴으로 점진적으로 전환하는 방법을 설명합니다. - -## 호환성 보장 - -### 1. 기존 UI 시스템은 그대로 유지됩니다 -- `BaseUi`, `BasePopupUi`, `PopupUi` 클래스들은 변경되지 않음 -- 기존 UI들은 계속해서 정상 동작 -- `UiManager`와 `PopupUiState`도 기존 방식 그대로 지원 - -### 2. 새로운 Integrated 클래스들 사용 -- `IntegratedBaseUi` : BaseUi + MVVM 기능 통합 -- `IntegratedBasePopupUi` : BasePopupUi + MVVM 기능 통합 -- `IntegratedPopupUi` : PopupUi + MVVM 기능 통합 - -## 마이그레이션 전략 - -### 단계 1: 점진적 전환 계획 수립 - -1. **우선순위 결정** - ``` - 높음: 복잡한 상태 관리가 필요한 UI (InventoryView, ShopView 등) - 중간: 중간 규모의 팝업 UI - 낮음: 간단한 확인/알림 다이얼로그 - ``` - -2. **전환 범위 설정** - - 한 번에 하나의 UI씩 전환 - - 관련된 ViewModel과 Service 함께 구현 - - 철저한 테스트 후 다음 UI 진행 - -### 단계 2: ViewModel 및 Service 구현 - -#### 기존 View 분석 -```csharp -// 기존 InventoryView -public class InventoryView : MonoBehaviour -{ - private InventoryCategoryType _currentCategory; - private List _items; - - public void UpdateCategoryView(InventoryCategoryType category) - { - _currentCategory = category; - // 복잡한 UI 업데이트 로직 - } -} -``` - -#### ViewModel로 분리 -```csharp -// 새로운 InventoryViewModel -public class InventoryViewModel : SimpleViewModel -{ - private InventoryCategoryType _currentCategory; - private List _visibleItems; - - public InventoryCategoryType CurrentCategory - { - get => _currentCategory; - set => SetField(ref _currentCategory, value); - } - - public void SetCategory(InventoryCategoryType category) - { - CurrentCategory = category; - UpdateVisibleItems(); // 비즈니스 로직 - } -} -``` - -#### Service 계층 분리 -```csharp -// 비즈니스 로직을 Service로 분리 -public class InventoryService : IUiService -{ - public IEnumerable FilterItems(InventoryCategoryType category) - { - // 복잡한 필터링 로직 - } -} -``` - -### 단계 3: View를 MVVM으로 전환 - -#### 기존 View 코드 -```csharp -public class InventoryView : MonoBehaviour, IEventHandler -{ - [SerializeField] private Transform _slotParent; - [SerializeField] private Text _categoryLabel; - - // 많은 상태 변수들과 복잡한 로직들... -} -``` - -#### MVVM View로 전환 -```csharp -public class MvvmInventoryView : MvvmBasePopupUi -{ - [SerializeField] private Transform _slotParent; - [SerializeField] private Text _categoryLabel; - - protected override void SetupBindings() - { - BindText(_categoryLabel, nameof(InventoryViewModel.CategoryDisplayText)); - // 간단한 바인딩 설정만 - } - - public void OnCategoryButtonClicked(int categoryIndex) - { - ViewModel.SetCategory((InventoryCategoryType)categoryIndex); - } -} -``` - -### 단계 4: 마이그레이션 체크리스트 - -#### ViewModel 체크리스트 -- [ ] SimpleViewModel 상속 -- [ ] 모든 상태를 속성으로 변환 -- [ ] SetField 사용한 PropertyChanged 알림 -- [ ] 계산된 속성 구현 -- [ ] 비즈니스 로직을 Service로 위임 - -#### View 체크리스트 -- [ ] 적절한 MVVM Base 클래스 상속 -- [ ] SetupBindings() 구현 -- [ ] UI 이벤트를 ViewModel 메서드 호출로 변경 -- [ ] 직접적인 UI 업데이트 코드 제거 -- [ ] HandleCustomPropertyChanged에서 복잡한 UI 처리 - -#### Service 체크리스트 -- [ ] IService 인터페이스 구현 -- [ ] 복잡한 비즈니스 로직 포함 -- [ ] 데이터 접근 로직 캡슐화 -- [ ] 테스트 가능한 구조 - -## 구체적인 마이그레이션 예시 - -### 예시 1: 간단한 팝업 UI - -#### Before (기존) -```csharp -public class SimpleDialogUi : BasePopupUi -{ - [SerializeField] private Text _messageText; - [SerializeField] private Button _confirmButton; - - public void ShowMessage(string message) - { - _messageText.text = message; - _confirmButton.onClick.AddListener(Close); - } -} -``` - -#### After (MVVM) -```csharp -// ViewModel -public class DialogViewModel : SimpleViewModel -{ - private string _message; - - public string Message - { - get => _message; - set => SetField(ref _message, value); - } -} - -// View -public class MvvmSimpleDialogUi : MvvmBasePopupUi -{ - [SerializeField] private Text _messageText; - [SerializeField] private Button _confirmButton; - - protected override void SetupBindings() - { - BindText(_messageText, nameof(DialogViewModel.Message)); - } - - protected override void Awake() - { - base.Awake(); - _confirmButton.onClick.AddListener(() => Close()); - } -} -``` - -### 예시 2: 복잡한 목록 UI - -#### Before (기존 InventoryView) -```csharp -public class InventoryView : MonoBehaviour -{ - private InventoryCategoryType _currentCategory; - private Dictionary _slotLookup = new(); - - public void UpdateCategoryView(InventoryCategoryType category) - { - _currentCategory = category; - - // 복잡한 필터링 로직 - foreach (var slot in _slotLookup.Values) - { - bool shouldShow = MatchesCategory(slot.Model, category); - slot.SetActive(shouldShow); - } - - // 정렬 로직 - // UI 업데이트 로직 - } - - private bool MatchesCategory(ItemViewModel model, InventoryCategoryType category) - { - // 복잡한 카테고리 매칭 로직 - } -} -``` - -#### After (MVVM) -```csharp -// ViewModel (상태 관리) -public class InventoryViewModel : SimpleViewModel -{ - private InventoryCategoryType _currentCategory; - private List _visibleItems; - - public InventoryCategoryType CurrentCategory - { - get => _currentCategory; - set => SetField(ref _currentCategory, value); - } - - public List VisibleItems - { - get => _visibleItems; - private set => SetField(ref _visibleItems, value); - } - - public string CategoryDisplayText => GetCategoryDisplayText(); - - public void SetCategory(InventoryCategoryType category) - { - CurrentCategory = category; - UpdateVisibleItems(); - OnPropertyChanged(nameof(CategoryDisplayText)); - } - - private void UpdateVisibleItems() - { - var filtered = _inventoryService.FilterItems(CurrentCategory, CurrentSortType); - VisibleItems = filtered.ToList(); - } -} - -// Service (비즈니스 로직) -public class InventoryService : IUiService -{ - public IEnumerable FilterItems(InventoryCategoryType category, InventorySortType sortType) - { - // 복잡한 필터링과 정렬 로직을 Service로 이동 - } -} - -// View (UI 바인딩만) -public class MvvmInventoryView : MvvmBasePopupUi -{ - [SerializeField] private Text _categoryLabel; - [SerializeField] private Transform _slotParent; - - protected override void SetupBindings() - { - BindText(_categoryLabel, nameof(InventoryViewModel.CategoryDisplayText)); - } - - protected override void HandleCustomPropertyChanged(string propertyName) - { - if (propertyName == nameof(InventoryViewModel.VisibleItems)) - { - UpdateItemSlots(); // 복잡한 UI 업데이트는 여전히 필요 - } - } -} -``` - -## 마이그레이션 주의사항 - -### 1. 기존 코드와의 의존성 -- 기존 UI를 참조하는 다른 코드들 확인 -- 이벤트 시스템과의 연동 유지 -- ScriptableObject 설정 파일들과의 호환성 - -### 2. 성능 고려사항 -- PropertyChanged 이벤트의 과도한 발생 방지 -- UI 업데이트 배칭 활용 -- 메모리 누수 방지 (이벤트 핸들러 정리) - -### 3. 테스트 전략 -- ViewModel 단위 테스트 작성 -- 기존 UI와 새 UI 동시 테스트 -- 사용자 시나리오 기반 통합 테스트 - -## 마이그레이션 우선순위 추천 - -### 1차 마이그레이션 (높은 우선순위) -- **InventoryView**: 복잡한 상태 관리, 필터링, 정렬 -- **ShopView**: 상품 목록, 카테고리, 구매 로직 -- **MenuView**: 메뉴 관리, 레시피 선택 - -### 2차 마이그레이션 (중간 우선순위) -- **SettingsView**: 다양한 설정 옵션들 -- **StatisticsView**: 데이터 표시와 계산 - -### 3차 마이그레이션 (낮은 우선순위) -- **SimpleDialogUi**: 간단한 확인 다이얼로그들 -- **NotificationUi**: 알림 팝업들 - -이 가이드를 따라 점진적으로 UI를 MVVM으로 전환하면, 기존 시스템의 안정성을 유지하면서도 새로운 아키텍처의 이점을 얻을 수 있습니다. \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/MIGRATION_GUIDE.md.meta b/Assets/_DDD/_Scripts/GameUi/New/MIGRATION_GUIDE.md.meta deleted file mode 100644 index 423f0dde4..000000000 --- a/Assets/_DDD/_Scripts/GameUi/New/MIGRATION_GUIDE.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 4d3c9e8511cb71c42b53aa3543216518 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/_DDD/_Scripts/GameUi/New/NewRestaurantManagementUi.cs b/Assets/_DDD/_Scripts/GameUi/New/NewRestaurantManagementUi.cs deleted file mode 100644 index 6f7404022..000000000 --- a/Assets/_DDD/_Scripts/GameUi/New/NewRestaurantManagementUi.cs +++ /dev/null @@ -1,275 +0,0 @@ -using UnityEngine; -using UnityEngine.UI; -using UnityEngine.EventSystems; -using UnityEngine.InputSystem; -using DDD.MVVM; - -namespace DDD -{ - /// - /// MVVM 패턴을 적용한 새로운 레스토랑 관리 UI - /// 기존 RestaurantManagementUi의 기능을 ViewModel과 분리하여 구현 - /// - public class NewRestaurantManagementUi : IntegratedBasePopupUi - { - [Header("Sub Views")] - [SerializeField] private ChecklistView _checklistView; - [SerializeField] private InventoryView _inventoryView; - [SerializeField] private ItemDetailView _itemDetailView; - [SerializeField] private TodayMenuView _todayMenuView; - [SerializeField] private TodayRestaurantStateView _todayRestaurantStateView; - - [Header("Tab Groups")] - [SerializeField] private TabGroupUi _sectionTabs; - [SerializeField] private TabGroupUi _menuCategoryTabs; - [SerializeField] private TabGroupUi _cookwareCategoryTabs; - - [Header("Hold Progress UI")] - [SerializeField, BindTo(nameof(RestaurantManagementViewModel.NormalizedHoldProgress))] - private Image _completeBatchFilledImage; - - protected override void Awake() - { - base.Awake(); - - SetupViewModelEvents(); - } - - protected override void Update() - { - base.Update(); - - if (_viewModel != null && _viewModel.IsHolding) - { - _viewModel.UpdateHoldProgress(); - } - } - - public override void Open(OpenPopupUiEvent evt) - { - base.Open(evt); - - InitializeViews(); - SetupTabs(); - } - - protected override GameObject GetInitialSelected() - { - // ViewModel의 현재 상태에 따라 초기 선택 UI 결정 - var inventoryInitialSelected = _inventoryView.GetInitialSelected(); - if (inventoryInitialSelected) return inventoryInitialSelected; - - var menuCategoryButton = _menuCategoryTabs.GetFirstInteractableButton(); - if (menuCategoryButton != null && menuCategoryButton.activeInHierarchy) - return menuCategoryButton; - - var cookwareCategoryButton = _cookwareCategoryTabs.GetFirstInteractableButton(); - if (cookwareCategoryButton != null && cookwareCategoryButton.activeInHierarchy) - return cookwareCategoryButton; - - return null; - } - - protected override void SetupBindings() - { - // Attribute 기반 자동 바인딩이 처리됨 - // 추가적인 수동 바인딩이 필요한 경우 여기에 구현 - } - - protected override void HandleCustomPropertyChanged(string propertyName) - { - switch (propertyName) - { - case nameof(RestaurantManagementViewModel.CurrentSection): - UpdateSectionTabs(); - break; - case nameof(RestaurantManagementViewModel.CurrentCategory): - UpdateCategoryTabs(); - break; - } - } - - private void SetupViewModelEvents() - { - if (!_viewModel) return; - - _viewModel.OnBatchCompleted = HandleBatchCompleted; - _viewModel.OnChecklistFailed = HandleChecklistFailed; - _viewModel.OnMenuSectionSelected = HandleMenuSectionSelected; - _viewModel.OnCookwareSectionSelected = HandleCookwareSectionSelected; - _viewModel.OnCategoryChanged = HandleCategoryChanged; - _viewModel.OnTabMoved = HandleTabMoved; - _viewModel.OnInteractRequested = HandleInteractRequested; - _viewModel.OnCloseRequested = HandleCloseRequested; - _viewModel.OnMenuCategorySelected = HandleMenuCategorySelected; - } - - private void InitializeViews() - { - _checklistView.Initalize(); - _inventoryView.Initialize(); - _itemDetailView.Initialize(); - _todayMenuView.Initialize(); - _todayRestaurantStateView.Initialize(); - } - - private void SetupTabs() - { - SetupCategoryTabs(); - InitializeTabGroups(); - SelectInitialTabs(); - } - - private void SetupCategoryTabs() - { - _menuCategoryTabs.UseDefaultAllowedValues(); - _cookwareCategoryTabs.UseDefaultAllowedValues(); - } - - private void InitializeTabGroups() - { - _sectionTabs.Initialize(OnSectionTabSelected); - _menuCategoryTabs.Initialize(OnCategoryTabSelected); - _cookwareCategoryTabs.Initialize(OnCategoryTabSelected); - } - - private void SelectInitialTabs() - { - _sectionTabs.SelectFirstTab(); - _menuCategoryTabs.SelectFirstTab(); - } - - private void UpdateSectionTabs() - { - if (_viewModel == null) return; - _sectionTabs.SelectTab((int)_viewModel.CurrentSection); - } - - private void UpdateCategoryTabs() - { - if (_viewModel == null) return; - - switch (_viewModel.CurrentSection) - { - case SectionButtonType.Menu: - _menuCategoryTabs.SelectTab((int)_viewModel.CurrentCategory); - break; - case SectionButtonType.Cookware: - _cookwareCategoryTabs.SelectTab((int)_viewModel.CurrentCategory); - break; - } - } - - // ViewModel 이벤트 핸들러들 - private void HandleBatchCompleted() - { - Close(); - } - - private void HandleChecklistFailed() - { - var evt = GameEvents.OpenPopupUiEvent; - evt.UiType = typeof(ConfirmUi); - evt.IsCancelButtonVisible = true; - evt.NewMessageKey = "checklist_failed_message"; - evt.OnConfirm = ClosePanel; - EventBus.Broadcast(evt); - } - - private void HandleMenuSectionSelected() - { - _menuCategoryTabs.SelectFirstTab(); - } - - private void HandleCookwareSectionSelected() - { - _cookwareCategoryTabs.SelectFirstTab(); - } - - private void HandleCategoryChanged(InventoryCategoryType category) - { - _inventoryView.UpdateCategoryView(category); - _itemDetailView.UpdateCategory(category); - } - - private void HandleTabMoved(int direction) - { - _sectionTabs.Move(direction); - } - - private void HandleInteractRequested() - { - var selected = EventSystem.current.currentSelectedGameObject; - var interactable = selected?.GetComponent(); - interactable?.OnInteract(); - } - - private void HandleCloseRequested() - { - Close(); - } - - private void HandleMenuCategorySelected(InventoryCategoryType category) - { - _menuCategoryTabs.SelectTab((int)category); - } - - // UI 이벤트 핸들러들 (TabGroupUi 콜백) - private void OnSectionTabSelected(int sectionValue) - { - _viewModel?.SetSection((SectionButtonType)sectionValue); - } - - private void OnCategoryTabSelected(int categoryValue) - { - _viewModel?.SetCategory((InventoryCategoryType)categoryValue); - } - - // 입력 처리 - ViewModel로 위임 - protected override bool OnInputPerformed(RestaurantUiActions actionEnum, InputAction.CallbackContext context) - { - var isHandled = base.OnInputPerformed(actionEnum, context); - - if (isHandled && _viewModel != null) - { - switch (actionEnum) - { - case RestaurantUiActions.Cancel: - _viewModel.CloseUi(); - break; - case RestaurantUiActions.PreviousTab: - _viewModel.MoveTab(-1); - break; - case RestaurantUiActions.NextTab: - _viewModel.MoveTab(1); - break; - case RestaurantUiActions.Interact1: - _viewModel.InteractWithSelected(); - break; - case RestaurantUiActions.Interact2: - _viewModel.StartHold(); - break; - } - } - - return isHandled; - } - - protected override bool OnInputCanceled(RestaurantUiActions actionEnum, InputAction.CallbackContext context) - { - var isHandled = base.OnInputCanceled(actionEnum, context); - - if (isHandled && _viewModel != null) - { - switch (actionEnum) - { - case RestaurantUiActions.Interact2: - _viewModel.CancelHold(); - break; - } - } - - return isHandled; - } - } -} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/NewRestaurantManagementUi.cs.meta b/Assets/_DDD/_Scripts/GameUi/New/NewRestaurantManagementUi.cs.meta deleted file mode 100644 index fb8636ee8..000000000 --- a/Assets/_DDD/_Scripts/GameUi/New/NewRestaurantManagementUi.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 8a2e0954aa144633aad86e53dc80a46a -timeCreated: 1755673386 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/README.md b/Assets/_DDD/_Scripts/GameUi/New/README.md deleted file mode 100644 index 1b09e6a8e..000000000 --- a/Assets/_DDD/_Scripts/GameUi/New/README.md +++ /dev/null @@ -1,278 +0,0 @@ -# Unity MVVM 시스템 가이드 - -Unity에서 MVVM(Model-View-ViewModel) 패턴을 구현한 시스템입니다. Attribute 기반 데이터 바인딩과 `nameof`를 활용한 타입 안전한 구조를 제공합니다. - -## 폴더 구조 - -``` -GameUi/New/ -├── ViewModels/ # ViewModel 클래스들 -│ ├── Base/ # 기본 ViewModel 클래스 -│ │ └── SimpleViewModel.cs -│ └── InventoryViewModel.cs # 예시 ViewModel -├── Views/ # View 클래스들 -│ └── Base/ -│ └── AutoBindView.cs # 자동 바인딩 View 기본 클래스 -├── Services/ # 서비스 계층 클래스들 -│ ├── IService.cs # 서비스 인터페이스 -│ └── InventoryService.cs # 예시 서비스 -├── Utils/ # 유틸리티 클래스들 -│ ├── BindToAttribute.cs # 바인딩 Attribute -│ └── BindingContext.cs # 바인딩 컨텍스트 -└── Converters/ # 값 변환기들 - ├── IValueConverter.cs # 컨버터 인터페이스 - └── CommonConverters.cs # 공통 컨버터들 -``` - -## 핵심 클래스 - -### 1. SimpleViewModel -- ViewModel의 기본 클래스 -- `INotifyPropertyChanged` 구현 -- 속성 변경 알림 자동 처리 -- 배치 업데이트 지원 - -### 2. IntegratedBaseUi -- UI의 기본 클래스 (BaseUi + MVVM 기능 통합) -- Attribute 기반 자동 바인딩 -- ViewModel과 UI 요소 자동 연결 - -### 3. BindToAttribute -- UI 요소를 ViewModel 속성에 바인딩 -- `nameof()` 사용으로 타입 안전성 보장 -- 컨버터 지원 - -## 사용법 - -### 1. ViewModel 생성 - -```csharp -namespace DDD.MVVM -{ - public class MyViewModel : SimpleViewModel - { - private string _title = "기본 제목"; - private int _count = 0; - private bool _isVisible = true; - - public string Title - { - get => _title; - set => SetField(ref _title, value); - } - - public int Count - { - get => _count; - set => SetField(ref _count, value); - } - - public bool IsVisible - { - get => _isVisible; - set => SetField(ref _isVisible, value); - } - - public string CountText => $"개수: {Count}"; - - public void IncrementCount() - { - Count++; - OnPropertyChanged(nameof(CountText)); - } - } -} -``` - -### 2. View 생성 - -```csharp -namespace DDD -{ - public class MyView : IntegratedBaseUi - { - [SerializeField, BindTo(nameof(MyViewModel.Title))] - private Text _titleText; - - [SerializeField, BindTo(nameof(MyViewModel.CountText))] - private Text _countText; - - [SerializeField, BindTo(nameof(MyViewModel.IsVisible))] - private GameObject _panel; - - // UI 이벤트 핸들러 - public void OnIncrementButtonClicked() - { - ViewModel.IncrementCount(); - } - - public void OnToggleVisibilityClicked() - { - ViewModel.IsVisible = !ViewModel.IsVisible; - } - } -} -``` - -### 3. 컨버터 사용 - -```csharp -[SerializeField, BindTo(nameof(MyViewModel.IsVisible), typeof(InvertBoolConverter))] -private GameObject _hiddenPanel; - -[SerializeField, BindTo(nameof(MyViewModel.Count), typeof(ItemCountConverter))] -private Text _formattedCountText; -``` - -## 기존 InventoryView 변환 예시 - -### 기존 코드 (InventoryView) -```csharp -public class InventoryView : MonoBehaviour -{ - private InventoryCategoryType _currentCategory = InventoryCategoryType.Food; - - public void UpdateCategoryView(InventoryCategoryType category) - { - _currentCategory = category; - // 복잡한 UI 업데이트 로직... - } -} -``` - -### MVVM 변환 후 - -**InventoryViewModel.cs** -```csharp -namespace DDD.MVVM -{ - public class InventoryViewModel : SimpleViewModel - { - private InventoryCategoryType _currentCategory = InventoryCategoryType.Food; - - public InventoryCategoryType CurrentCategory - { - get => _currentCategory; - set => SetField(ref _currentCategory, value); - } - - public string CategoryDisplayText => CurrentCategory switch - { - InventoryCategoryType.Food => "음식", - InventoryCategoryType.Drink => "음료", - _ => "전체" - }; - - public void SetCategory(InventoryCategoryType category) - { - CurrentCategory = category; - OnPropertyChanged(nameof(CategoryDisplayText)); - } - } -} -``` - -**InventoryView.cs** -```csharp -namespace DDD.MVVM -{ - public class InventoryView : AutoBindView - { - [SerializeField, BindTo(nameof(InventoryViewModel.CategoryDisplayText))] - private Text _categoryLabel; - - public void OnCategoryButtonClicked(int categoryIndex) - { - ViewModel.SetCategory((InventoryCategoryType)categoryIndex); - } - } -} -``` - -## 장점 - -### 1. 타입 안전성 -- `nameof()` 사용으로 컴파일 타임 검증 -- 속성명 변경 시 자동 업데이트 -- IDE IntelliSense 지원 - -### 2. Unity 통합성 -- Inspector에서 바인딩 확인 -- MonoBehaviour 패턴 유지 -- 기존 Unity 워크플로우와 호환 - -### 3. 유지보수성 -- View와 비즈니스 로직 분리 -- 테스트 가능한 ViewModel -- 재사용 가능한 컴포넌트 - -### 4. 개발 생산성 -- 자동 바인딩으로 보일러플레이트 코드 감소 -- 데이터 변경 시 UI 자동 업데이트 -- 일관된 개발 패턴 - -## 컨버터 목록 - -### 기본 컨버터들 -- `InvertBoolConverter`: 불린 값 반전 -- `ItemCountConverter`: 숫자를 "아이템 수: N" 형식으로 변환 -- `InventoryCategoryConverter`: 카테고리 열거형을 한국어로 변환 -- `CurrencyConverter`: 숫자를 통화 형식으로 변환 -- `PercentageConverter`: 소수를 백분율로 변환 - -### 커스텀 컨버터 생성 -```csharp -public class CustomConverter : IValueConverter -{ - public object Convert(object value) - { - // 변환 로직 구현 - return convertedValue; - } - - public object ConvertBack(object value) - { - // 역변환 로직 구현 (선택사항) - return originalValue; - } -} -``` - -## 베스트 프랙티스 - -### 1. ViewModel 설계 -- 단일 책임 원칙 준수 -- UI 관련 Unity API 직접 사용 금지 -- 계산된 속성 적극 활용 -- 이벤트를 통한 느슨한 결합 - -### 2. 바인딩 설정 -- `nameof()` 사용 필수 -- 적절한 컨버터 활용 -- 복잡한 로직은 ViewModel에서 처리 - -### 3. 성능 고려사항 -- 배치 업데이트 활용 -- 불필요한 PropertyChanged 이벤트 방지 -- 컬렉션 변경 시 효율적인 업데이트 - -## 마이그레이션 가이드 - -### 단계별 적용 방법 - -#### 1단계: 기존 View에서 ViewModel 분리 -1. View의 상태 변수들을 ViewModel로 이동 -2. 비즈니스 로직을 Service로 분리 -3. UI 업데이트 로직을 바인딩으로 대체 - -#### 2단계: 자동 바인딩 적용 -1. `AutoBindView` 상속 -2. UI 요소에 `BindTo` Attribute 추가 -3. 수동 UI 업데이트 코드 제거 - -#### 3단계: 최적화 및 리팩토링 -1. 컨버터 활용으로 로직 단순화 -2. 계산된 속성으로 중복 제거 -3. 이벤트 시스템과 통합 - -이 MVVM 시스템을 통해 Unity UI 개발의 생산성과 유지보수성을 크게 향상시킬 수 있습니다. \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/README.md.meta b/Assets/_DDD/_Scripts/GameUi/New/README.md.meta deleted file mode 100644 index 392b933fe..000000000 --- a/Assets/_DDD/_Scripts/GameUi/New/README.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 5d5b08d15b2e43f4698c0f24977c2582 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBasePopupUi.cs b/Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBasePopupUi.cs deleted file mode 100644 index 6805b6df9..000000000 --- a/Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBasePopupUi.cs +++ /dev/null @@ -1,252 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Reflection; -using UnityEngine; -using UnityEngine.EventSystems; -using UnityEngine.UI; -using Sirenix.OdinInspector; -using UnityEngine.InputSystem; -using DDD.MVVM; - -namespace DDD -{ - public abstract class IntegratedBasePopupUi : BasePopupUi - where TInputEnum : Enum - where TViewModel : SimpleViewModel - { - [SerializeField, Required] protected BaseUiActionsInputBinding _uiActionsInputBinding; - - protected readonly List<(InputAction action, Action handler)> _registeredHandlers = - new(); - - // MVVM 기능들 - protected BindingContext _bindingContext; - protected TViewModel _viewModel; - - public override InputActionMaps InputActionMaps => _uiActionsInputBinding.InputActionMaps; - public bool IsTopPopup => UiManager.Instance.PopupUiState.IsTopPopup(this); - - protected override void Awake() - { - base.Awake(); - - _enableBlockImage = true; - _viewModel = GetComponent(); - - _bindingContext = new BindingContext(); - SetupAutoBindings(); - SetupBindings(); - } - - protected override void OnEnable() - { - base.OnEnable(); - } - - protected override void Update() - { - base.Update(); - - if (IsOpenPanel() == false) return; - - var currentSelectedGameObject = EventSystem.current.currentSelectedGameObject; - if (currentSelectedGameObject == null || currentSelectedGameObject.activeInHierarchy == false) - { - var initialSelected = GetInitialSelected(); - if (initialSelected != null) - { - EventSystem.current.SetSelectedGameObject(initialSelected); - } - } - } - - public override void Open(OpenPopupUiEvent evt) - { - base.Open(evt); - - var initialSelected = GetInitialSelected(); - if (initialSelected != null) - { - EventSystem.current.SetSelectedGameObject(initialSelected); - } - - transform.SetAsLastSibling(); - - if (IsTopPopup) - { - InputManager.Instance.SwitchCurrentActionMap(_uiActionsInputBinding.InputActionMaps); - } - } - - protected override abstract GameObject GetInitialSelected(); - - protected override void TryRegister() - { - base.TryRegister(); - - // PopupUi의 입력 바인딩 등록 - foreach (var actionEnum in _uiActionsInputBinding.BindingActions.GetFlags()) - { - if (actionEnum.Equals(default(TInputEnum))) continue; - - var inputAction = - InputManager.Instance.GetAction(_uiActionsInputBinding.InputActionMaps, actionEnum.ToString()); - if (inputAction == null) continue; - - var startedHandler = new Action(context => - { - OnInputStarted(actionEnum, context); - }); - inputAction.started += startedHandler; - - var performedHandler = new Action(context => - { - OnInputPerformed(actionEnum, context); - }); - inputAction.performed += performedHandler; - - var canceledHandler = new Action(context => - { - OnInputCanceled(actionEnum, context); - }); - inputAction.canceled += canceledHandler; - - _registeredHandlers.Add((inputAction, startedHandler)); - _registeredHandlers.Add((inputAction, performedHandler)); - _registeredHandlers.Add((inputAction, canceledHandler)); - } - } - - protected override void TryUnregister() - { - base.TryUnregister(); - - // 입력 핸들러 해제 - foreach (var (action, handler) in _registeredHandlers) - { - if (action != null) - { - action.started -= handler; - action.performed -= handler; - action.canceled -= handler; - } - } - - _registeredHandlers.Clear(); - } - - // 입력 처리 메서드들 - protected virtual bool OnInputStarted(TInputEnum actionEnum, InputAction.CallbackContext context) => IsTopPopup; - protected virtual bool OnInputPerformed(TInputEnum actionEnum, InputAction.CallbackContext context) => IsTopPopup; - protected virtual bool OnInputCanceled(TInputEnum actionEnum, InputAction.CallbackContext context) => IsTopPopup; - - /// - /// Attribute 기반 자동 바인딩 설정 - /// - private void SetupAutoBindings() - { - var fields = GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public) - .Where(f => f.GetCustomAttribute() != null); - - foreach (var field in fields) - { - var bindAttribute = field.GetCustomAttribute(); - SetupBinding(field, bindAttribute); - } - - // 컬렉션 바인딩 설정 - var collectionFields = GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public) - .Where(f => f.GetCustomAttribute() != null); - - foreach (var field in collectionFields) - { - var bindAttribute = field.GetCustomAttribute(); - SetupCollectionBinding(field, bindAttribute); - } - } - - /// - /// 개별 필드의 바인딩 설정 - /// - 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); - } - } - - /// - /// 컬렉션 바인딩 설정 - /// - 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}"); - } - } - - /// - /// 추가 바인딩 설정 - 하위 클래스에서 구현 - /// - protected virtual void SetupBindings() { } - - /// - /// ViewModel 속성 변경 이벤트 핸들러 - /// - protected virtual void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) - { - HandleCustomPropertyChanged(e.PropertyName); - } - - /// - /// 커스텀 속성 변경 처리 (하위 클래스에서 오버라이드) - /// - protected virtual void HandleCustomPropertyChanged(string propertyName) - { - // 하위 클래스에서 구현 - } - - /// - /// UI 패널 열기 오버라이드 (ViewModel 초기화 추가) - /// - public override void OpenPanel() - { - base.OpenPanel(); - _viewModel?.Initialize(); - } - - /// - /// UI 패널 닫기 오버라이드 (ViewModel 정리 추가) - /// - public override void ClosePanel() - { - _viewModel?.Cleanup(); - base.ClosePanel(); - } - } -} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBasePopupUi.cs.meta b/Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBasePopupUi.cs.meta deleted file mode 100644 index 23e1d5306..000000000 --- a/Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBasePopupUi.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 3ef87d5c2f3d82e488302056ac09a287 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBaseUi.cs b/Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBaseUi.cs deleted file mode 100644 index 0f2195045..000000000 --- a/Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBaseUi.cs +++ /dev/null @@ -1,248 +0,0 @@ -using System; -using System.ComponentModel; -using System.Linq; -using System.Reflection; -using UnityEngine; -using UnityEngine.UI; -using DDD.MVVM; - -namespace DDD -{ - public abstract class IntegratedBaseUi : MonoBehaviour where TViewModel : SimpleViewModel - { - [SerializeField] protected bool _enableBlockImage; - - protected CanvasGroup _canvasGroup; - protected GameObject _blockImage; - protected GameObject _panel; - protected BindingContext _bindingContext; - protected TViewModel _viewModel; - - public virtual bool IsBlockingTime => false; - public virtual bool IsOpen => _panel != null && _panel.activeSelf; - - protected virtual void Awake() - { - _canvasGroup = GetComponent(); - _panel = transform.Find(CommonConstants.Panel)?.gameObject; - _blockImage = transform.Find(CommonConstants.BlockImage)?.gameObject; - - if (_viewModel == null) - _viewModel = GetComponent(); - - _bindingContext = new BindingContext(); - SetupAutoBindings(); - SetupBindings(); - } - - protected virtual void OnEnable() - { - if (_viewModel && _bindingContext != null) - { - _bindingContext.SetDataContext(_viewModel); - _viewModel.PropertyChanged += OnViewModelPropertyChanged; - } - } - - protected virtual void Start() - { - TryRegister(); - ClosePanel(); - } - - protected virtual void Update() - { - - } - - protected virtual void OnDisable() - { - if (_viewModel != null) - { - _viewModel.PropertyChanged -= OnViewModelPropertyChanged; - } - } - - protected virtual void OnDestroy() - { - TryUnregister(); - _bindingContext?.Dispose(); - } - - protected virtual void TryRegister() { } - protected virtual void TryUnregister() { } - - // BaseUi 메서드들을 직접 구현 - public virtual void OpenPanel() - { - if (_enableBlockImage) - { - _blockImage.SetActive(true); - } - - _panel.SetActive(true); - _viewModel?.Initialize(); - } - - public virtual void ClosePanel() - { - if (_enableBlockImage) - { - _blockImage.SetActive(false); - } - - _panel.SetActive(false); - _viewModel?.Cleanup(); - } - - public virtual void SetUiInteractable(bool active) - { - if (_canvasGroup != null) - { - _canvasGroup.interactable = active; - _canvasGroup.blocksRaycasts = active; - } - } - - public bool IsOpenPanel() => _panel && _panel.activeInHierarchy; - - /// - /// Attribute 기반 자동 바인딩 설정 - /// - private void SetupAutoBindings() - { - var fields = GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public) - .Where(f => f.GetCustomAttribute() != null); - - foreach (var field in fields) - { - var bindAttribute = field.GetCustomAttribute(); - SetupBinding(field, bindAttribute); - } - - // 컬렉션 바인딩 설정 - var collectionFields = GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public) - .Where(f => f.GetCustomAttribute() != null); - - foreach (var field in collectionFields) - { - var bindAttribute = field.GetCustomAttribute(); - SetupCollectionBinding(field, bindAttribute); - } - } - - /// - /// 개별 필드의 바인딩 설정 - /// - 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); - } - } - - /// - /// 컬렉션 바인딩 설정 - /// - 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}"); - } - } - - /// - /// 추가 바인딩 설정 - 하위 클래스에서 구현 - /// - protected virtual void SetupBindings() { } - - /// - /// ViewModel 속성 변경 이벤트 핸들러 - /// - protected virtual void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) - { - HandleCustomPropertyChanged(e.PropertyName); - } - - /// - /// 커스텀 속성 변경 처리 (하위 클래스에서 오버라이드) - /// - 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); - } - - /// - /// ViewModel 메서드 호출 헬퍼 - /// - protected void InvokeViewModelMethod(string methodName, params object[] parameters) - { - if (_viewModel == null) return; - - var method = _viewModel.GetType().GetMethod(methodName); - method?.Invoke(_viewModel, parameters); - } - - /// - /// ViewModel 속성 설정 헬퍼 - /// - 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); - } - } - } -} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBaseUi.cs.meta b/Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBaseUi.cs.meta deleted file mode 100644 index ed069a47f..000000000 --- a/Assets/_DDD/_Scripts/GameUi/New/Views/Base/IntegratedBaseUi.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 868322e05b33bdd4cbbe3e1495fe359b \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/Views/Examples/IntegratedInventoryView.cs b/Assets/_DDD/_Scripts/GameUi/New/Views/Examples/IntegratedInventoryView.cs deleted file mode 100644 index 344ad881d..000000000 --- a/Assets/_DDD/_Scripts/GameUi/New/Views/Examples/IntegratedInventoryView.cs +++ /dev/null @@ -1,221 +0,0 @@ -using UnityEngine; -using UnityEngine.UI; -using DDD.MVVM; - -namespace DDD -{ - /// - /// IntegratedPopupUi를 사용한 인벤토리 뷰 예시 - /// 상속 대신 컴포지션 기반으로 모든 기능을 통합하여 구현 - /// Attribute 기반 자동 바인딩을 적극 활용 - /// - public class IntegratedInventoryView : IntegratedBasePopupUi - { - [Header("UI References")] - // Attribute를 통한 자동 바인딩 설정 - [SerializeField, BindTo(nameof(InventoryViewModel.CategoryDisplayText))] - private Text _categoryLabel; - - [SerializeField, BindTo(nameof(InventoryViewModel.ItemCountText))] - private Text _itemCountLabel; - - [SerializeField, BindTo(nameof(InventoryViewModel.ShowEmptyMessage))] - private GameObject _emptyMessage; - - // 수동 바인딩이 필요한 복잡한 UI 요소들 - [SerializeField] private Transform _slotParent; - [SerializeField] private Button[] _categoryButtons; - [SerializeField] private Button[] _sortButtons; - - [Header("Prefab References")] - [SerializeField] private GameObject _itemSlotPrefab; - - protected override GameObject GetInitialSelected() - { - // ViewModel의 FirstValidItem을 활용하여 초기 선택 UI 결정 - var firstItem = _viewModel?.FirstValidItem; - if (firstItem != null) - { - return FindSlotGameObject(firstItem); - } - - return _categoryButtons?.Length > 0 ? _categoryButtons[0].gameObject : null; - } - - /// - /// 수동 바인딩이 필요한 복잡한 UI 요소들 설정 - /// Attribute로 처리하기 어려운 컬렉션이나 복잡한 로직이 필요한 경우 - /// - protected override void SetupBindings() - { - // 복잡한 컬렉션 바인딩은 수동으로 처리 - // BindCollection은 아직 완전 구현되지 않았으므로 HandleCustomPropertyChanged에서 처리 - } - - protected override void HandleCustomPropertyChanged(string propertyName) - { - switch (propertyName) - { - case nameof(InventoryViewModel.VisibleItems): - UpdateItemSlots(); - break; - - case nameof(InventoryViewModel.CurrentCategory): - UpdateCategoryButtons(); - break; - - case nameof(InventoryViewModel.CurrentSortType): - UpdateSortButtons(); - break; - - case nameof(InventoryViewModel.FirstValidItem): - UpdateInitialSelection(); - break; - } - } - - /// - /// 아이템 슬롯 UI 업데이트 - /// - private void UpdateItemSlots() - { - if (_viewModel?.VisibleItems == null) return; - - // 기존 슬롯들 정리 - ClearSlots(); - - int siblingIndex = 0; - foreach (var itemViewModel in _viewModel.VisibleItems) - { - if (!itemViewModel.HasItem) continue; - - // 아이템 슬롯 UI 생성 - var slotGameObject = Instantiate(_itemSlotPrefab, _slotParent); - var slotUi = slotGameObject.GetComponent(); - - // 기존 방식대로 슬롯 초기화 - slotUi.Initialize(itemViewModel, new InventorySlotUiStrategy()); - slotGameObject.name = $"ItemSlotUi_{itemViewModel.Id}"; - - // 인터랙터 설정 - var interactor = slotGameObject.GetComponent(); - if (itemViewModel.ItemType == ItemType.Recipe) - { - interactor.Initialize(TodayMenuEventType.Add, new TodayMenuInteractorStrategy()); - } - else if (DataManager.Instance.GetDataSo().TryGetDataById(itemViewModel.Id, out var cookwareData)) - { - interactor.Initialize(TodayMenuEventType.Add, new TodayCookwareInteractorStrategy()); - } - - slotGameObject.transform.SetSiblingIndex(siblingIndex++); - } - } - - /// - /// 카테고리 버튼 상태 업데이트 - /// - private void UpdateCategoryButtons() - { - if (_categoryButtons == null || _viewModel == null) return; - - for (int i = 0; i < _categoryButtons.Length; i++) - { - var button = _categoryButtons[i]; - var isSelected = (int)_viewModel.CurrentCategory == i; - - button.interactable = !isSelected; - } - } - - /// - /// 정렬 버튼 상태 업데이트 - /// - private void UpdateSortButtons() - { - if (_sortButtons == null || _viewModel == null) return; - - for (int i = 0; i < _sortButtons.Length; i++) - { - var button = _sortButtons[i]; - var isSelected = (int)_viewModel.CurrentSortType == i; - - button.interactable = !isSelected; - } - } - - /// - /// 초기 선택 UI 업데이트 - /// - private void UpdateInitialSelection() - { - var initialSelected = GetInitialSelected(); - if (initialSelected != null) - { - UnityEngine.EventSystems.EventSystem.current.SetSelectedGameObject(initialSelected); - } - } - - /// - /// 기존 슬롯들 정리 - /// - private void ClearSlots() - { - foreach (Transform child in _slotParent) - { - Destroy(child.gameObject); - } - } - - /// - /// ItemViewModel에 해당하는 UI GameObject 찾기 - /// - private GameObject FindSlotGameObject(ItemViewModel itemViewModel) - { - foreach (Transform child in _slotParent) - { - if (child.name == $"ItemSlotUi_{itemViewModel.Id}") - { - return child.gameObject; - } - } - return null; - } - - // UI 이벤트 핸들러들 - ViewModel 메서드 호출 - public void OnCategoryButtonClicked(int categoryIndex) - { - _viewModel?.SetCategory((InventoryCategoryType)categoryIndex); - } - - public void OnSortButtonClicked(int sortIndex) - { - _viewModel?.SetSortType((InventorySortType)sortIndex); - } - - public void OnItemSlotClicked(ItemViewModel item) - { - _viewModel?.SelectItem(item); - } - - // 입력 처리 - ViewModel로 위임 - protected override bool OnInputPerformed(RestaurantUiActions actionEnum, UnityEngine.InputSystem.InputAction.CallbackContext context) - { - var isHandled = base.OnInputPerformed(actionEnum, context); - - // 특별한 입력 처리 로직이 필요한 경우 여기에 추가 - if (isHandled) - { - switch (actionEnum) - { - case RestaurantUiActions.Cancel: - Close(); - break; - // 기타 액션들은 ViewModel로 위임됨 - } - } - - return isHandled; - } - } -} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/New/Views/Examples/IntegratedInventoryView.cs.meta b/Assets/_DDD/_Scripts/GameUi/New/Views/Examples/IntegratedInventoryView.cs.meta deleted file mode 100644 index 4c0ae5b0a..000000000 --- a/Assets/_DDD/_Scripts/GameUi/New/Views/Examples/IntegratedInventoryView.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 12f3141cdd485054e8c73be9d549feb7 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/PopupUi/BasePopupUi.cs b/Assets/_DDD/_Scripts/GameUi/PopupUi/BasePopupUi.cs index 28901bfe2..c13bc96bf 100644 --- a/Assets/_DDD/_Scripts/GameUi/PopupUi/BasePopupUi.cs +++ b/Assets/_DDD/_Scripts/GameUi/PopupUi/BasePopupUi.cs @@ -5,36 +5,65 @@ namespace DDD { public abstract class BasePopupUi : BaseUi { - public abstract InputActionMaps InputActionMaps { get; } - protected abstract GameObject GetInitialSelected(); + public bool IsTopPopup => UiManager.Instance.PopupUiState.IsTopPopup(this); + public InputActionMaps InputActionMaps; protected override void Awake() { base.Awake(); + // BasePopupUi의 기본값 적용 _enableBlockImage = true; } - + + protected override void OnEnable() + { + base.OnEnable(); + } + protected override void Update() { base.Update(); + // BasePopupUi의 Update 로직 구현 if (IsOpenPanel() == false) return; var currentSelectedGameObject = EventSystem.current.currentSelectedGameObject; if (currentSelectedGameObject == null || currentSelectedGameObject.activeInHierarchy == false) { - if (GetInitialSelected() == null) return; - - EventSystem.current.SetSelectedGameObject(GetInitialSelected()); + var initialSelected = GetInitialSelected(); + if (initialSelected != null) + { + EventSystem.current.SetSelectedGameObject(initialSelected); + } } } + protected override void TryRegister() + { + base.TryRegister(); + + UiManager.Instance.PopupUiState?.RegisterPopupUI(this); + } + + protected override void TryUnregister() + { + base.TryUnregister(); + + UiManager.Instance?.PopupUiState?.UnregisterPopupUI(this); + } + public virtual void Open(OpenPopupUiEvent evt) { - base.OpenPanel(); - - EventSystem.current.SetSelectedGameObject(GetInitialSelected()); + OpenPanel(); + + var initialSelected = GetInitialSelected(); + if (initialSelected != null) + { + EventSystem.current.SetSelectedGameObject(initialSelected); + } + + transform.SetAsLastSibling(); } public virtual void Close() @@ -43,5 +72,7 @@ public virtual void Close() evt.UiType = GetType(); EventBus.Broadcast(evt); } + + protected abstract GameObject GetInitialSelected(); } } \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/PopupUi/PopupUi.cs b/Assets/_DDD/_Scripts/GameUi/PopupUi/PopupUi.cs index 2e2d7fffc..38be27a27 100644 --- a/Assets/_DDD/_Scripts/GameUi/PopupUi/PopupUi.cs +++ b/Assets/_DDD/_Scripts/GameUi/PopupUi/PopupUi.cs @@ -18,26 +18,55 @@ public static IEnumerable GetFlags(this T input) where T : Enum } } } - - public abstract class PopupUi : BasePopupUi where T : Enum + + public abstract class PopupUi : BasePopupUi + where TInputEnum : Enum + where TViewModel : SimpleViewModel { - [SerializeField, Required] protected BaseUiActionsInputBinding _uiActionsInputBinding; + [SerializeField, Required] protected BaseUiActionsInputBinding _uiActionsInputBinding; + protected readonly List<(InputAction action, Action handler)> _registeredHandlers = new(); - public override InputActionMaps InputActionMaps => _uiActionsInputBinding.InputActionMaps; + protected TViewModel _viewModel; + + protected override void Awake() + { + base.Awake(); + + _viewModel = GetComponent(); + } - private bool _isTopPopup => UiManager.Instance.PopupUiState.IsTopPopup(this); + protected override void OnEnable() + { + base.OnEnable(); + + if (_viewModel && _bindingContext != null) + { + _bindingContext.SetDataContext(_viewModel); + _viewModel.PropertyChanged += OnViewModelPropertyChanged; + } + } + protected override void OnDisable() + { + base.OnDisable(); + + if (_viewModel != null) + { + _viewModel.PropertyChanged -= OnViewModelPropertyChanged; + } + } + protected override void TryRegister() { base.TryRegister(); - UiManager.Instance.PopupUiState.RegisterPopupUI(this); - + // PopupUi의 입력 바인딩 등록 foreach (var actionEnum in _uiActionsInputBinding.BindingActions.GetFlags()) { - if (actionEnum.Equals(default(T))) continue; - - var inputAction = InputManager.Instance.GetAction(_uiActionsInputBinding.InputActionMaps, actionEnum.ToString()); + if (actionEnum.Equals(default(TInputEnum))) continue; + + var inputAction = + InputManager.Instance.GetAction(_uiActionsInputBinding.InputActionMaps, actionEnum.ToString()); if (inputAction == null) continue; var startedHandler = new Action(context => @@ -62,13 +91,15 @@ protected override void TryRegister() _registeredHandlers.Add((inputAction, performedHandler)); _registeredHandlers.Add((inputAction, canceledHandler)); } + + InputActionMaps = _uiActionsInputBinding.InputActionMaps; } - + protected override void TryUnregister() { base.TryUnregister(); - UiManager.Instance?.PopupUiState?.UnregisterPopupUI(this); - + + // 입력 핸들러 해제 foreach (var (action, handler) in _registeredHandlers) { if (action != null) @@ -81,32 +112,29 @@ protected override void TryUnregister() _registeredHandlers.Clear(); } - + public override void Open(OpenPopupUiEvent evt) { base.Open(evt); - transform.SetAsLastSibling(); + _viewModel?.Initialize(); - if (UiManager.Instance.PopupUiState.IsTopPopup(this)) + if (IsTopPopup) { InputManager.Instance.SwitchCurrentActionMap(_uiActionsInputBinding.InputActionMaps); } } - protected virtual bool OnInputStarted(T actionEnum, InputAction.CallbackContext context) + public override void Close() { - return _isTopPopup; + base.Close(); + + _viewModel?.Cleanup(); } - protected virtual bool OnInputPerformed(T actionEnum, InputAction.CallbackContext context) - { - return _isTopPopup; - } - - protected virtual bool OnInputCanceled(T actionEnum, InputAction.CallbackContext context) - { - return _isTopPopup; - } + // 입력 처리 메서드들 + protected virtual bool OnInputStarted(TInputEnum actionEnum, InputAction.CallbackContext context) => IsTopPopup; + protected virtual bool OnInputPerformed(TInputEnum actionEnum, InputAction.CallbackContext context) => IsTopPopup; + protected virtual bool OnInputCanceled(TInputEnum actionEnum, InputAction.CallbackContext context) => IsTopPopup; } } \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/PopupUi/RestaurantManagementUi/RestaurantManagementUi.cs b/Assets/_DDD/_Scripts/GameUi/PopupUi/RestaurantManagementUi/RestaurantManagementUi.cs index 573a78fe6..c65d6b376 100644 --- a/Assets/_DDD/_Scripts/GameUi/PopupUi/RestaurantManagementUi/RestaurantManagementUi.cs +++ b/Assets/_DDD/_Scripts/GameUi/PopupUi/RestaurantManagementUi/RestaurantManagementUi.cs @@ -1,114 +1,108 @@ -using System; -using System.Collections.Generic; -using System.Linq; using UnityEngine; +using UnityEngine.UI; using UnityEngine.EventSystems; using UnityEngine.InputSystem; -using UnityEngine.UI; +using DDD.MVVM; namespace DDD { - public class RestaurantManagementUi : PopupUi, IEventHandler + /// + /// MVVM 패턴을 적용한 새로운 레스토랑 관리 UI + /// 기존 RestaurantManagementUi의 기능을 ViewModel과 분리하여 구현 + /// + [RequireComponent(typeof(RestaurantManagementViewModel))] + public class RestaurantManagementUi : PopupUi { + [Header("Sub Views")] [SerializeField] private ChecklistView _checklistView; [SerializeField] private InventoryView _inventoryView; [SerializeField] private ItemDetailView _itemDetailView; [SerializeField] private TodayMenuView _todayMenuView; [SerializeField] private TodayRestaurantStateView _todayRestaurantStateView; + + [Header("Tab Groups")] [SerializeField] private TabGroupUi _sectionTabs; [SerializeField] private TabGroupUi _menuCategoryTabs; [SerializeField] private TabGroupUi _cookwareCategoryTabs; - [SerializeField] private Image _completeBatchFilledImage; - [SerializeField] private float _holdCompleteTime = 1f; - private float _elapsedTime; - private bool _isHolding; + [Header("Hold Progress UI")] + [SerializeField, BindTo(nameof(RestaurantManagementViewModel.NormalizedHoldProgress))] + private Image _completeBatchFilledImage; - private const string ChecklistFailedMessageKey = "checklist_failed_message"; + protected override void Awake() + { + base.Awake(); + + SetupViewModelEvents(); + } protected override void Update() { base.Update(); - - if (_isHolding) + + if (_viewModel != null && _viewModel.IsHolding) { - UpdateHoldProgress(); + _viewModel.UpdateHoldProgress(); } } - private void UpdateHoldProgress() - { - if (_holdCompleteTime <= 0f) - { - HandleInteract2Canceled(); - ProcessCompleteBatchAction(); - return; - } - - _completeBatchFilledImage.fillAmount = _elapsedTime; - - var multiply = 1f / _holdCompleteTime; - if (_elapsedTime >= 1f) - { - HandleInteract2Canceled(); - ProcessCompleteBatchAction(); - return; - } - - _elapsedTime += Time.deltaTime * multiply; - } - - private void ProcessCompleteBatchAction() - { - if (RestaurantState.Instance.ManagementState.GetChecklistStates().Any(state => state == false)) - { - ShowChecklistFailedPopup(); - } - else - { - Close(); - } - } - - private void ShowChecklistFailedPopup() - { - var evt = GameEvents.OpenPopupUiEvent; - evt.UiType = typeof(ConfirmUi); - evt.IsCancelButtonVisible = true; - evt.NewMessageKey = ChecklistFailedMessageKey; - evt.OnConfirm = ClosePanel; - EventBus.Broadcast(evt); - } - - protected override GameObject GetInitialSelected() - { - var inventoryViewInitialSelectedObject = _inventoryView.GetInitialSelected(); - if (inventoryViewInitialSelectedObject) return inventoryViewInitialSelectedObject; - - var menuCategoryFirstButton = _menuCategoryTabs.GetFirstInteractableButton(); - if (menuCategoryFirstButton != null && menuCategoryFirstButton.activeInHierarchy) - { - return menuCategoryFirstButton; - } - - var cookwareCategoryFirstButton = _cookwareCategoryTabs.GetFirstInteractableButton(); - if (cookwareCategoryFirstButton != null && cookwareCategoryFirstButton.activeInHierarchy) - { - return cookwareCategoryFirstButton; - } - - return null; - } - public override void Open(OpenPopupUiEvent evt) { base.Open(evt); - + InitializeViews(); - SetupCategoryTabs(); - InitializeTabGroups(); - SelectInitialTabs(); - RegisterEventHandlers(); + SetupTabs(); + } + + protected override GameObject GetInitialSelected() + { + // ViewModel의 현재 상태에 따라 초기 선택 UI 결정 + var inventoryInitialSelected = _inventoryView.GetInitialSelected(); + if (inventoryInitialSelected) return inventoryInitialSelected; + + var menuCategoryButton = _menuCategoryTabs.GetFirstInteractableButton(); + if (menuCategoryButton != null && menuCategoryButton.activeInHierarchy) + return menuCategoryButton; + + var cookwareCategoryButton = _cookwareCategoryTabs.GetFirstInteractableButton(); + if (cookwareCategoryButton != null && cookwareCategoryButton.activeInHierarchy) + return cookwareCategoryButton; + + return null; + } + + protected override void SetupBindings() + { + // Attribute 기반 자동 바인딩이 처리됨 + // 추가적인 수동 바인딩이 필요한 경우 여기에 구현 + } + + protected override void HandleCustomPropertyChanged(string propertyName) + { + switch (propertyName) + { + case nameof(RestaurantManagementViewModel.CurrentSection): + UpdateSectionTabs(); + break; + case nameof(RestaurantManagementViewModel.CurrentCategory): + UpdateCategoryTabs(); + break; + } + } + + private void SetupViewModelEvents() + { + if (!_viewModel) return; + + _viewModel.OnBatchCompleted = HandleBatchCompleted; + _viewModel.OnChecklistFailed = HandleChecklistFailed; + _viewModel.OnMenuSectionSelected = HandleMenuSectionSelected; + _viewModel.OnCookwareSectionSelected = HandleCookwareSectionSelected; + _viewModel.OnCategoryChanged = HandleCategoryChanged; + _viewModel.OnTabMoved = HandleTabMoved; + _viewModel.OnInteractRequested = HandleInteractRequested; + _viewModel.OnCloseRequested = HandleCloseRequested; + _viewModel.OnMenuCategorySelected = HandleMenuCategorySelected; } private void InitializeViews() @@ -120,12 +114,15 @@ private void InitializeViews() _todayRestaurantStateView.Initialize(); } - /// - /// 각 그룹별로 허용된 카테고리를 설정합니다. - /// + private void SetupTabs() + { + SetupCategoryTabs(); + InitializeTabGroups(); + SelectInitialTabs(); + } + private void SetupCategoryTabs() { - // 각 그룹별로 기본 허용 값 사용 (자동으로 적절한 카테고리 필터링) _menuCategoryTabs.UseDefaultAllowedValues(); _cookwareCategoryTabs.UseDefaultAllowedValues(); } @@ -143,113 +140,137 @@ private void SelectInitialTabs() _menuCategoryTabs.SelectFirstTab(); } - private void RegisterEventHandlers() + private void UpdateSectionTabs() { - EventBus.Register(this); + if (_viewModel == null) return; + _sectionTabs.SelectTab((int)_viewModel.CurrentSection); } - public override void Close() + private void UpdateCategoryTabs() { - base.Close(); - - EventBus.Unregister(this); - } - - protected override bool OnInputPerformed(RestaurantUiActions actionEnum, InputAction.CallbackContext context) - { - if (base.OnInputPerformed(actionEnum, context) == false) return false; - - switch (actionEnum) + if (_viewModel == null) return; + + switch (_viewModel.CurrentSection) { - case RestaurantUiActions.Cancel: - HandleCancelPerformed(); + case SectionButtonType.Menu: + _menuCategoryTabs.SelectTab((int)_viewModel.CurrentCategory); break; - case RestaurantUiActions.PreviousTab: - HandleMoveTabPerformed(-1); - break; - case RestaurantUiActions.NextTab: - HandleMoveTabPerformed(1); - break; - case RestaurantUiActions.Interact1: - HandleInteract1Performed(); - break; - case RestaurantUiActions.Interact2: - HandleInteract2Performed(); + case SectionButtonType.Cookware: + _cookwareCategoryTabs.SelectTab((int)_viewModel.CurrentCategory); break; } - - return true; } - protected override bool OnInputCanceled(RestaurantUiActions actionEnum, InputAction.CallbackContext context) - { - if (base.OnInputPerformed(actionEnum, context) == false) return false; - - switch (actionEnum) - { - case RestaurantUiActions.Interact2: - HandleInteract2Canceled(); - break; - } - - return true; - } - - private void HandleCancelPerformed() + // ViewModel 이벤트 핸들러들 + private void HandleBatchCompleted() { Close(); } - private void HandleMoveTabPerformed(int direction) + private void HandleChecklistFailed() + { + var evt = GameEvents.OpenPopupUiEvent; + evt.UiType = typeof(ConfirmUi); + evt.IsCancelButtonVisible = true; + evt.NewMessageKey = "checklist_failed_message"; + evt.OnConfirm = ClosePanel; + EventBus.Broadcast(evt); + } + + private void HandleMenuSectionSelected() + { + _menuCategoryTabs.SelectFirstTab(); + } + + private void HandleCookwareSectionSelected() + { + _cookwareCategoryTabs.SelectFirstTab(); + } + + private void HandleCategoryChanged(InventoryCategoryType category) + { + _inventoryView.UpdateCategoryView(category); + _itemDetailView.UpdateCategory(category); + } + + private void HandleTabMoved(int direction) { _sectionTabs.Move(direction); } - private void HandleInteract1Performed() + private void HandleInteractRequested() { var selected = EventSystem.current.currentSelectedGameObject; var interactable = selected?.GetComponent(); interactable?.OnInteract(); } - private void HandleInteract2Performed() + private void HandleCloseRequested() { - _isHolding = true; + Close(); } - private void HandleInteract2Canceled() + private void HandleMenuCategorySelected(InventoryCategoryType category) { - _isHolding = false; - _elapsedTime = 0f; - _completeBatchFilledImage.fillAmount = 0f; + _menuCategoryTabs.SelectTab((int)category); } + // UI 이벤트 핸들러들 (TabGroupUi 콜백) private void OnSectionTabSelected(int sectionValue) { - var section = (SectionButtonType)sectionValue; - switch (section) - { - case SectionButtonType.Menu: - _menuCategoryTabs.SelectFirstTab(); - break; - case SectionButtonType.Cookware: - _cookwareCategoryTabs.SelectFirstTab(); - break; - default: - throw new ArgumentOutOfRangeException(nameof(section), section, null); - } + _viewModel?.SetSection((SectionButtonType)sectionValue); } private void OnCategoryTabSelected(int categoryValue) { - var category = (InventoryCategoryType)categoryValue; - _inventoryView.UpdateCategoryView(category); - _itemDetailView.UpdateCategory(category); + _viewModel?.SetCategory((InventoryCategoryType)categoryValue); } - public void Invoke(TodayMenuRemovedEvent evt) + // 입력 처리 - ViewModel로 위임 + protected override bool OnInputPerformed(RestaurantUiActions actionEnum, InputAction.CallbackContext context) { - _menuCategoryTabs.SelectTab((int)evt.InventoryCategoryType); + var isHandled = base.OnInputPerformed(actionEnum, context); + + if (isHandled && _viewModel != null) + { + switch (actionEnum) + { + case RestaurantUiActions.Cancel: + _viewModel.CloseUi(); + break; + case RestaurantUiActions.PreviousTab: + _viewModel.MoveTab(-1); + break; + case RestaurantUiActions.NextTab: + _viewModel.MoveTab(1); + break; + case RestaurantUiActions.Interact1: + _viewModel.InteractWithSelected(); + break; + case RestaurantUiActions.Interact2: + _viewModel.StartHold(); + break; + } + } + + return isHandled; + } + + protected override bool OnInputCanceled(RestaurantUiActions actionEnum, InputAction.CallbackContext context) + { + var isHandled = base.OnInputCanceled(actionEnum, context); + + if (isHandled && _viewModel != null) + { + switch (actionEnum) + { + case RestaurantUiActions.Interact2: + _viewModel.CancelHold(); + break; + } + } + + return isHandled; } } } \ No newline at end of file From 4a18a239e88ebd5645d60e9f7e4d12afd46c0f7b Mon Sep 17 00:00:00 2001 From: NTG Date: Wed, 20 Aug 2025 18:16:57 +0900 Subject: [PATCH 10/36] =?UTF-8?q?=EC=9E=84=EC=8B=9C=20=EA=B0=80=EC=9D=B4?= =?UTF-8?q?=EB=93=9C=EB=9D=BC=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/_DDD/_Scripts/GameUi/UiGuideline | 545 +++++++++++++++++++ Assets/_DDD/_Scripts/GameUi/UiGuideline.meta | 3 + 2 files changed, 548 insertions(+) create mode 100644 Assets/_DDD/_Scripts/GameUi/UiGuideline create mode 100644 Assets/_DDD/_Scripts/GameUi/UiGuideline.meta diff --git a/Assets/_DDD/_Scripts/GameUi/UiGuideline b/Assets/_DDD/_Scripts/GameUi/UiGuideline new file mode 100644 index 000000000..f6e909b2b --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/UiGuideline @@ -0,0 +1,545 @@ +# 새로운 UI 개발을 위한 단계별 가이드라인 + +Unity 프로젝트에서 RestaurantManagementUi를 기반으로 새로운 UI를 만들 때 따라야 할 구체적인 단계별 가이드라인을 제공합니다. + +## 개발 순서 및 클래스 생성 가이드 + +### 1단계: 요구사항 분석 및 설계 + +#### 먼저 결정해야 할 사항들 +- **UI 타입**: 일반 UI인가? 팝업 UI인가? +- **입력 처리**: 키보드/게임패드 입력이 필요한가? +- **데이터 복잡도**: 단순한 표시용인가? 복잡한 상태 관리가 필요한가? +- **재사용성**: 다른 곳에서도 사용될 가능성이 있는가? + +#### 예시: ShopUi를 만든다고 가정 +``` +요구사항: +- 상품 목록 표시 (카테고리별 필터링) +- 상품 구매 기능 +- 소지금 표시 +- 키보드 입력 지원 +- 팝업 형태로 동작 +``` + +### 2단계: Service 클래스 생성 (비즈니스 로직) + +**가장 먼저 Service부터 시작하는 이유:** +- 비즈니스 로직이 명확해야 UI 설계가 가능 +- 테스트 가능한 코드 작성 +- ViewModel에서 사용할 데이터와 기능 정의 + +#### ShopService.cs +```csharp +// Assets/_DDD/_Scripts/GameUi/New/Services/ShopService.cs +namespace DDD.MVVM +{ + public class ShopService : IUiService + { + private ShopData _shopData; + private PlayerInventoryData _playerData; + + public void Initialize() + { + _shopData = DataManager.Instance.GetDataSo(); + _playerData = PlayerState.Instance.InventoryData; + } + + public void Cleanup() { } + public void UpdateUiState() { } + + /// + /// 카테고리별 상품 목록 가져오기 + /// + public IEnumerable GetItemsByCategory(ShopCategoryType category) + { + return _shopData.Items + .Where(item => MatchesCategory(item, category)) + .Select(item => new ShopItemViewModel(item)) + .ToList(); + } + + /// + /// 상품 구매 가능 여부 확인 + /// + public bool CanPurchaseItem(string itemId, int quantity = 1) + { + var item = _shopData.GetItemById(itemId); + return _playerData.Money >= (item.Price * quantity); + } + + /// + /// 상품 구매 처리 + /// + public bool PurchaseItem(string itemId, int quantity = 1) + { + if (!CanPurchaseItem(itemId, quantity)) return false; + + var item = _shopData.GetItemById(itemId); + var totalCost = item.Price * quantity; + + _playerData.Money -= totalCost; + _playerData.AddItem(itemId, quantity); + + return true; + } + + private bool MatchesCategory(ShopItemData item, ShopCategoryType category) + { + // 카테고리 매칭 로직 + return category switch + { + ShopCategoryType.Food => item.Type == ItemType.Food, + ShopCategoryType.Equipment => item.Type == ItemType.Equipment, + _ => true + }; + } + } +} +``` + +### 3단계: ViewModel 클래스 생성 (상태 관리) + +Service가 준비되었으니 이를 활용하는 ViewModel을 생성합니다. + +#### ShopViewModel.cs +```csharp +// Assets/_DDD/_Scripts/GameUi/New/ViewModels/ShopViewModel.cs +namespace DDD.MVVM +{ + public class ShopViewModel : SimpleViewModel, IEventHandler + { + [Header("Services")] + [SerializeField] private ShopService _shopService; + + // Private fields for properties + private ShopCategoryType _currentCategory = ShopCategoryType.All; + private List _visibleItems = new(); + private ShopItemViewModel _selectedItem; + private int _playerMoney; + + /// + /// 현재 선택된 카테고리 + /// + public ShopCategoryType CurrentCategory + { + get => _currentCategory; + set + { + if (SetField(ref _currentCategory, value)) + { + UpdateVisibleItems(); + OnPropertyChanged(nameof(CategoryDisplayText)); + } + } + } + + /// + /// 보이는 상품 목록 + /// + public List VisibleItems + { + get => _visibleItems; + private set => SetField(ref _visibleItems, value); + } + + /// + /// 현재 선택된 상품 + /// + public ShopItemViewModel SelectedItem + { + get => _selectedItem; + set => SetField(ref _selectedItem, value); + } + + /// + /// 플레이어 소지금 + /// + public int PlayerMoney + { + get => _playerMoney; + set => SetField(ref _playerMoney, value); + } + + // 계산된 속성들 + public string CategoryDisplayText => CurrentCategory switch + { + ShopCategoryType.Food => "음식", + ShopCategoryType.Equipment => "장비", + _ => "전체" + }; + + public string MoneyDisplayText => $"{PlayerMoney:N0} G"; + public bool HasVisibleItems => VisibleItems.Any(); + public bool CanPurchaseSelected => + SelectedItem != null && _shopService.CanPurchaseItem(SelectedItem.Id); + + protected override void Awake() + { + base.Awake(); + + if (_shopService == null) + _shopService = new ShopService(); + } + + public override void Initialize() + { + base.Initialize(); + + _shopService.Initialize(); + LoadShopData(); + RegisterEvents(); + } + + public override void Cleanup() + { + base.Cleanup(); + + UnregisterEvents(); + _shopService?.Cleanup(); + } + + private void RegisterEvents() + { + EventBus.Register(this); + } + + private void UnregisterEvents() + { + EventBus.Unregister(this); + } + + private void LoadShopData() + { + PlayerMoney = PlayerState.Instance.InventoryData.Money; + UpdateVisibleItems(); + } + + private void UpdateVisibleItems() + { + BeginUpdate(); + + var items = _shopService.GetItemsByCategory(CurrentCategory); + VisibleItems = items.ToList(); + + OnPropertyChanged(nameof(HasVisibleItems)); + OnPropertyChanged(nameof(CanPurchaseSelected)); + + EndUpdate(); + } + + /// + /// 카테고리 변경 + /// + public void SetCategory(ShopCategoryType category) + { + CurrentCategory = category; + } + + /// + /// 상품 선택 + /// + public void SelectItem(ShopItemViewModel item) + { + SelectedItem = item; + OnPropertyChanged(nameof(CanPurchaseSelected)); + } + + /// + /// 상품 구매 + /// + public bool PurchaseSelectedItem(int quantity = 1) + { + if (SelectedItem == null || !CanPurchaseSelected) return false; + + var success = _shopService.PurchaseItem(SelectedItem.Id, quantity); + if (success) + { + // 구매 성공 시 UI 업데이트 + LoadShopData(); + } + + return success; + } + + // 이벤트 핸들러 + public void Invoke(MoneyChangedEvent evt) + { + PlayerMoney = evt.NewAmount; + OnPropertyChanged(nameof(MoneyDisplayText)); + OnPropertyChanged(nameof(CanPurchaseSelected)); + } + } +} +``` + +### 4단계: ItemViewModel 클래스 생성 (필요시) + +복잡한 아이템이 있다면 별도의 ItemViewModel을 생성합니다. + +#### ShopItemViewModel.cs +```csharp +// Assets/_DDD/_Scripts/GameUi/New/ViewModels/ShopItemViewModel.cs +namespace DDD.MVVM +{ + public class ShopItemViewModel : SimpleViewModel + { + private ShopItemData _itemData; + private bool _isSelected; + + public string Id => _itemData?.Id ?? ""; + public string Name => _itemData?.Name ?? ""; + public string Description => _itemData?.Description ?? ""; + public int Price => _itemData?.Price ?? 0; + public Sprite Icon => _itemData?.Icon; + + public bool IsSelected + { + get => _isSelected; + set => SetField(ref _isSelected, value); + } + + public string PriceDisplayText => $"{Price:N0} G"; + + public ShopItemViewModel(ShopItemData itemData) + { + _itemData = itemData; + } + } +} +``` + +### 5단계: View 클래스 생성 + +마지막으로 UI View를 생성합니다. RestaurantManagementUi를 참고하여 구조를 만듭니다. + +#### ShopUi.cs +```csharp +// Assets/_DDD/_Scripts/GameUi/PopupUi/ShopUi/ShopUi.cs +namespace DDD +{ + /// + /// 상점 UI - RestaurantManagementUi 구조를 참고하여 구현 + /// + [RequireComponent(typeof(ShopViewModel))] + public class ShopUi : IntegratedBasePopupUi + { + [Header("UI References")] + // Attribute 기반 자동 바인딩 + [SerializeField, BindTo(nameof(ShopViewModel.CategoryDisplayText))] + private Text _categoryLabel; + + [SerializeField, BindTo(nameof(ShopViewModel.MoneyDisplayText))] + private Text _moneyLabel; + + [SerializeField, BindTo(nameof(ShopViewModel.HasVisibleItems), typeof(InvertBoolConverter))] + private GameObject _emptyMessage; + + // 수동 처리가 필요한 UI 요소들 + [SerializeField] private Transform _itemSlotParent; + [SerializeField] private GameObject _itemSlotPrefab; + [SerializeField] private Button[] _categoryButtons; + [SerializeField] private Button _purchaseButton; + + public override InputActionMaps InputActionMaps => InputActionMaps.Shop; + + protected override GameObject GetInitialSelected() + { + // 첫 번째 상품 슬롯 또는 카테고리 버튼 반환 + var firstSlot = _itemSlotParent.childCount > 0 ? + _itemSlotParent.GetChild(0).gameObject : null; + + return firstSlot ?? (_categoryButtons.Length > 0 ? _categoryButtons[0].gameObject : null); + } + + protected override void SetupBindings() + { + // Attribute로 처리하기 어려운 복잡한 바인딩들은 여기서 수동 설정 + } + + protected override void HandleCustomPropertyChanged(string propertyName) + { + switch (propertyName) + { + case nameof(ShopViewModel.VisibleItems): + UpdateItemSlots(); + break; + + case nameof(ShopViewModel.CurrentCategory): + UpdateCategoryButtons(); + break; + + case nameof(ShopViewModel.CanPurchaseSelected): + _purchaseButton.interactable = ViewModel.CanPurchaseSelected; + break; + } + } + + private void UpdateItemSlots() + { + // 기존 슬롯 정리 + foreach (Transform child in _itemSlotParent) + { + Destroy(child.gameObject); + } + + // 새 슬롯 생성 + if (ViewModel?.VisibleItems != null) + { + foreach (var item in ViewModel.VisibleItems) + { + var slotGO = Instantiate(_itemSlotPrefab, _itemSlotParent); + var slotComponent = slotGO.GetComponent(); + slotComponent.Initialize(item, OnItemClicked); + } + } + } + + private void UpdateCategoryButtons() + { + if (_categoryButtons == null || ViewModel == null) return; + + for (int i = 0; i < _categoryButtons.Length; i++) + { + var isSelected = (int)ViewModel.CurrentCategory == i; + _categoryButtons[i].interactable = !isSelected; + } + } + + // UI 이벤트 핸들러들 + public void OnCategoryButtonClicked(int categoryIndex) + { + ViewModel?.SetCategory((ShopCategoryType)categoryIndex); + } + + public void OnPurchaseButtonClicked() + { + if (ViewModel?.PurchaseSelectedItem() == true) + { + // 구매 성공 피드백 + ShowPurchaseSuccessEffect(); + } + } + + private void OnItemClicked(ShopItemViewModel item) + { + ViewModel?.SelectItem(item); + } + + private void ShowPurchaseSuccessEffect() + { + // 구매 성공 시 시각적 피드백 + } + + // 입력 처리 + protected override bool OnInputPerformed(ShopUiActions actionEnum, UnityEngine.InputSystem.InputAction.CallbackContext context) + { + var isHandled = base.OnInputPerformed(actionEnum, context); + + if (isHandled && ViewModel != null) + { + switch (actionEnum) + { + case ShopUiActions.Purchase: + OnPurchaseButtonClicked(); + break; + case ShopUiActions.Cancel: + Close(); + break; + } + } + + return isHandled; + } + } +} +``` + +### 6단계: 입력 액션 정의 (필요시) + +새로운 UI에 특별한 입력이 필요하다면 입력 액션을 정의합니다. + +#### ShopUiActions.cs +```csharp +// Assets/_DDD/_Scripts/Input/ShopUiActions.cs +namespace DDD +{ + [System.Flags] + public enum ShopUiActions + { + None = 0, + Navigate = 1 << 0, + Submit = 1 << 1, + Cancel = 1 << 2, + Purchase = 1 << 3, + PreviousCategory = 1 << 4, + NextCategory = 1 << 5, + } +} +``` + +## 개발 체크리스트 + +### Service 개발 체크리스트 +- [ ] IService 인터페이스 구현 +- [ ] 데이터 접근 로직 캡슐화 +- [ ] 비즈니스 로직 구현 +- [ ] 에러 처리 및 검증 로직 +- [ ] 단위 테스트 가능한 구조 + +### ViewModel 개발 체크리스트 +- [ ] SimpleViewModel 상속 +- [ ] 모든 상태를 속성으로 정의 +- [ ] SetField를 통한 PropertyChanged 알림 +- [ ] 계산된 속성 구현 +- [ ] Service 의존성 주입 +- [ ] 이벤트 등록/해제 +- [ ] 생명주기 메서드 구현 + +### View 개발 체크리스트 +- [ ] 적절한 Base 클래스 상속 선택 + - 일반 UI: IntegratedBaseUi + - 팝업 UI: IntegratedBasePopupUi + - 입력 필요 팝업: IntegratedBasePopupUi +- [ ] BindTo Attribute 적용 +- [ ] GetInitialSelected() 구현 +- [ ] SetupBindings() 구현 (필요시) +- [ ] HandleCustomPropertyChanged() 구현 +- [ ] UI 이벤트 핸들러 ViewModel로 연결 + +## 폴더 구조 권장사항 + +``` +Assets/_DDD/_Scripts/GameUi/PopupUi/ShopUi/ +├── ShopUi.cs # View 클래스 +├── ShopItemSlot.cs # 하위 UI 컴포넌트 +└── ShopUiData.cs # UI 설정 데이터 (필요시) + +Assets/_DDD/_Scripts/GameUi/New/ +├── Services/ +│ └── ShopService.cs # Service 클래스 +├── ViewModels/ +│ ├── ShopViewModel.cs # 메인 ViewModel +│ └── ShopItemViewModel.cs # 아이템 ViewModel +└── Converters/ + └── PriceConverter.cs # 커스텀 컨버터 (필요시) +``` + +## 개발 팁 + +### 1. 기존 코드 활용 +- RestaurantManagementUi의 패턴을 최대한 재활용 +- InventoryView의 필터링/정렬 로직 참고 +- 기존 Service 클래스들의 구조 패턴 따라하기 + +### 2. 점진적 개발 +1. 먼저 기본 기능만 구현 (Service → ViewModel → View) +2. UI 바인딩은 단순한 것부터 시작 +3. 복잡한 기능은 나중에 추가 + +### 3. 테스트 우선 +- Service 로직은 반드시 단위 테스트 +- ViewModel은 Mock Service로 테스트 +- View는 수동 테스트로 검증 + +이 가이드라인을 따라하면 일관성 있고 유지보수가 쉬운 UI를 효율적으로 개발할 수 있습니다. \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/GameUi/UiGuideline.meta b/Assets/_DDD/_Scripts/GameUi/UiGuideline.meta new file mode 100644 index 000000000..dd529aea6 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/UiGuideline.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 31ff6b5920be49feb652a1b614d62c15 +timeCreated: 1755681370 \ No newline at end of file From f895464e22ebfe70eb88d4887e07fdd4cb56cdfb Mon Sep 17 00:00:00 2001 From: Jeonghyeon Ha Date: Wed, 20 Aug 2025 18:42:07 +0900 Subject: [PATCH 11/36] =?UTF-8?q?=EC=BB=A4=EC=8A=A4=ED=84=B0=EB=A8=B8=20?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EB=B8=94=EC=97=90=20=EB=A0=88=EC=8A=A4?= =?UTF-8?q?=ED=86=A0=EB=9E=91=20=EC=98=A4=EB=8D=94=20=EC=9D=B8=ED=84=B0?= =?UTF-8?q?=EB=9E=99=EC=85=98=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Environments/Interactables/Common.meta | 8 + .../Common/RestaurantOrder.prefab | 156 ++++++++ .../Common/RestaurantOrder.prefab.meta | 7 + .../CustomerTable.meta | 0 .../CustomerTable/CustomerTable.mat | 0 .../CustomerTable/CustomerTable.mat.meta | 0 .../CustomerTable/CustomerTable.png | 0 .../CustomerTable/CustomerTable.png.meta | 0 .../Interactables/Prop_CustomerTable.prefab | 238 +++++++++++ .../Prop_CustomerTable.prefab.meta | 0 .../Props/Prop_CustomerTable.prefab | 377 ------------------ .../_DDD/_Scripts/GameEvent/IInteractable.cs | 2 +- .../RestaurantOrderInteractionSubsystem.cs | 13 +- .../Solvers/RestaurantOrderSolver.cs | 1 + .../RestaurantOrderSolver_Reserved.cs | 19 + .../RestaurantOrderSolver_Reserved.cs.meta | 3 + 16 files changed, 438 insertions(+), 386 deletions(-) create mode 100644 Assets/_DDD/Restaurant/Environments/Interactables/Common.meta create mode 100644 Assets/_DDD/Restaurant/Environments/Interactables/Common/RestaurantOrder.prefab create mode 100644 Assets/_DDD/Restaurant/Environments/Interactables/Common/RestaurantOrder.prefab.meta rename Assets/_DDD/Restaurant/Environments/{Props => Interactables}/CustomerTable.meta (100%) rename Assets/_DDD/Restaurant/Environments/{Props => Interactables}/CustomerTable/CustomerTable.mat (100%) rename Assets/_DDD/Restaurant/Environments/{Props => Interactables}/CustomerTable/CustomerTable.mat.meta (100%) rename Assets/_DDD/Restaurant/Environments/{Props => Interactables}/CustomerTable/CustomerTable.png (100%) rename Assets/_DDD/Restaurant/Environments/{Props => Interactables}/CustomerTable/CustomerTable.png.meta (100%) create mode 100644 Assets/_DDD/Restaurant/Environments/Interactables/Prop_CustomerTable.prefab rename Assets/_DDD/Restaurant/Environments/{Props => Interactables}/Prop_CustomerTable.prefab.meta (100%) delete mode 100644 Assets/_DDD/Restaurant/Environments/Props/Prop_CustomerTable.prefab create mode 100644 Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Reserved.cs create mode 100644 Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Reserved.cs.meta diff --git a/Assets/_DDD/Restaurant/Environments/Interactables/Common.meta b/Assets/_DDD/Restaurant/Environments/Interactables/Common.meta new file mode 100644 index 000000000..56795ff88 --- /dev/null +++ b/Assets/_DDD/Restaurant/Environments/Interactables/Common.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8b8b2fde5b3b345e292e0b6b951a4abd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_DDD/Restaurant/Environments/Interactables/Common/RestaurantOrder.prefab b/Assets/_DDD/Restaurant/Environments/Interactables/Common/RestaurantOrder.prefab new file mode 100644 index 000000000..24036b0a6 --- /dev/null +++ b/Assets/_DDD/Restaurant/Environments/Interactables/Common/RestaurantOrder.prefab @@ -0,0 +1,156 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &3857692527302447930 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1664322405549350652} + - component: {fileID: 8522104897182006738} + m_Layer: 0 + m_Name: InteractionPoint + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1664322405549350652 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3857692527302447930} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: -0.4, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 3697702677815423220} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!212 &8522104897182006738 +SpriteRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3857692527302447930} + m_Enabled: 1 + m_CastShadows: 0 + m_ReceiveShadows: 0 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 0 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 9dfc825aed78fcd4ba02077103263b40, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 0 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_Sprite: {fileID: 21300000, guid: c607300554a0c44469620484fccbf239, type: 3} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_FlipX: 0 + m_FlipY: 0 + m_DrawMode: 0 + m_Size: {x: 5.12, y: 5.12} + m_AdaptiveModeThreshold: 0.5 + m_SpriteTileMode: 0 + m_WasSpriteAssigned: 1 + m_MaskInteraction: 0 + m_SpriteSortPoint: 0 +--- !u!1 &4103096974375017811 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3697702677815423220} + - component: {fileID: 3591347921553422000} + - component: {fileID: 4456475204957017828} + m_Layer: 7 + m_Name: RestaurantOrder + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &3697702677815423220 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4103096974375017811} + serializedVersion: 2 + 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: 0 + m_Children: + - {fileID: 1664322405549350652} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &3591347921553422000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4103096974375017811} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 201f9e6d7ca7404baa9945950292a392, type: 3} + m_Name: + m_EditorClassIdentifier: + _interactionType: 4 + _executionParameters: + _holdTime: 0 + _displayParameters: + _messageKey: + _interactionAvailableFlows: 2 + _aiInteractionPoints: + - {fileID: 1664322405549350652} +--- !u!114 &4456475204957017828 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4103096974375017811} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c0b1e0992510498b8d33d5b6094b8f4b, type: 3} + m_Name: + m_EditorClassIdentifier: + orderType: 0 diff --git a/Assets/_DDD/Restaurant/Environments/Interactables/Common/RestaurantOrder.prefab.meta b/Assets/_DDD/Restaurant/Environments/Interactables/Common/RestaurantOrder.prefab.meta new file mode 100644 index 000000000..f06bbbf9f --- /dev/null +++ b/Assets/_DDD/Restaurant/Environments/Interactables/Common/RestaurantOrder.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2bbe3f26765344e6097aa9fd0f020fbf +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_DDD/Restaurant/Environments/Props/CustomerTable.meta b/Assets/_DDD/Restaurant/Environments/Interactables/CustomerTable.meta similarity index 100% rename from Assets/_DDD/Restaurant/Environments/Props/CustomerTable.meta rename to Assets/_DDD/Restaurant/Environments/Interactables/CustomerTable.meta diff --git a/Assets/_DDD/Restaurant/Environments/Props/CustomerTable/CustomerTable.mat b/Assets/_DDD/Restaurant/Environments/Interactables/CustomerTable/CustomerTable.mat similarity index 100% rename from Assets/_DDD/Restaurant/Environments/Props/CustomerTable/CustomerTable.mat rename to Assets/_DDD/Restaurant/Environments/Interactables/CustomerTable/CustomerTable.mat diff --git a/Assets/_DDD/Restaurant/Environments/Props/CustomerTable/CustomerTable.mat.meta b/Assets/_DDD/Restaurant/Environments/Interactables/CustomerTable/CustomerTable.mat.meta similarity index 100% rename from Assets/_DDD/Restaurant/Environments/Props/CustomerTable/CustomerTable.mat.meta rename to Assets/_DDD/Restaurant/Environments/Interactables/CustomerTable/CustomerTable.mat.meta diff --git a/Assets/_DDD/Restaurant/Environments/Props/CustomerTable/CustomerTable.png b/Assets/_DDD/Restaurant/Environments/Interactables/CustomerTable/CustomerTable.png similarity index 100% rename from Assets/_DDD/Restaurant/Environments/Props/CustomerTable/CustomerTable.png rename to Assets/_DDD/Restaurant/Environments/Interactables/CustomerTable/CustomerTable.png diff --git a/Assets/_DDD/Restaurant/Environments/Props/CustomerTable/CustomerTable.png.meta b/Assets/_DDD/Restaurant/Environments/Interactables/CustomerTable/CustomerTable.png.meta similarity index 100% rename from Assets/_DDD/Restaurant/Environments/Props/CustomerTable/CustomerTable.png.meta rename to Assets/_DDD/Restaurant/Environments/Interactables/CustomerTable/CustomerTable.png.meta diff --git a/Assets/_DDD/Restaurant/Environments/Interactables/Prop_CustomerTable.prefab b/Assets/_DDD/Restaurant/Environments/Interactables/Prop_CustomerTable.prefab new file mode 100644 index 000000000..718e7e94e --- /dev/null +++ b/Assets/_DDD/Restaurant/Environments/Interactables/Prop_CustomerTable.prefab @@ -0,0 +1,238 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1001 &2565741073186224478 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 8881739536043914635} + m_Modifications: + - target: {fileID: 3697702677815423220, guid: 2bbe3f26765344e6097aa9fd0f020fbf, type: 3} + propertyPath: m_LocalPosition.x + value: 0.2 + objectReference: {fileID: 0} + - target: {fileID: 3697702677815423220, guid: 2bbe3f26765344e6097aa9fd0f020fbf, type: 3} + propertyPath: m_LocalPosition.y + value: 0.25 + objectReference: {fileID: 0} + - target: {fileID: 3697702677815423220, guid: 2bbe3f26765344e6097aa9fd0f020fbf, type: 3} + propertyPath: m_LocalPosition.z + value: 0.2 + objectReference: {fileID: 0} + - target: {fileID: 3697702677815423220, guid: 2bbe3f26765344e6097aa9fd0f020fbf, type: 3} + propertyPath: m_LocalRotation.w + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3697702677815423220, guid: 2bbe3f26765344e6097aa9fd0f020fbf, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3697702677815423220, guid: 2bbe3f26765344e6097aa9fd0f020fbf, type: 3} + propertyPath: m_LocalRotation.y + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 3697702677815423220, guid: 2bbe3f26765344e6097aa9fd0f020fbf, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3697702677815423220, guid: 2bbe3f26765344e6097aa9fd0f020fbf, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3697702677815423220, guid: 2bbe3f26765344e6097aa9fd0f020fbf, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 180 + objectReference: {fileID: 0} + - target: {fileID: 3697702677815423220, guid: 2bbe3f26765344e6097aa9fd0f020fbf, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4103096974375017811, guid: 2bbe3f26765344e6097aa9fd0f020fbf, type: 3} + propertyPath: m_Name + value: RestaurantOrder_001 + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 2bbe3f26765344e6097aa9fd0f020fbf, type: 3} +--- !u!4 &1210262234305268138 stripped +Transform: + m_CorrespondingSourceObject: {fileID: 3697702677815423220, guid: 2bbe3f26765344e6097aa9fd0f020fbf, type: 3} + m_PrefabInstance: {fileID: 2565741073186224478} + m_PrefabAsset: {fileID: 0} +--- !u!1001 &4510296826299878002 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 8881739536043914635} + m_Modifications: + - target: {fileID: 3697702677815423220, guid: 2bbe3f26765344e6097aa9fd0f020fbf, type: 3} + propertyPath: m_LocalPosition.x + value: -0.2 + objectReference: {fileID: 0} + - target: {fileID: 3697702677815423220, guid: 2bbe3f26765344e6097aa9fd0f020fbf, type: 3} + propertyPath: m_LocalPosition.y + value: 0.25 + objectReference: {fileID: 0} + - target: {fileID: 3697702677815423220, guid: 2bbe3f26765344e6097aa9fd0f020fbf, type: 3} + propertyPath: m_LocalPosition.z + value: 0.2 + objectReference: {fileID: 0} + - target: {fileID: 3697702677815423220, guid: 2bbe3f26765344e6097aa9fd0f020fbf, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 3697702677815423220, guid: 2bbe3f26765344e6097aa9fd0f020fbf, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3697702677815423220, guid: 2bbe3f26765344e6097aa9fd0f020fbf, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3697702677815423220, guid: 2bbe3f26765344e6097aa9fd0f020fbf, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3697702677815423220, guid: 2bbe3f26765344e6097aa9fd0f020fbf, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3697702677815423220, guid: 2bbe3f26765344e6097aa9fd0f020fbf, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3697702677815423220, guid: 2bbe3f26765344e6097aa9fd0f020fbf, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4103096974375017811, guid: 2bbe3f26765344e6097aa9fd0f020fbf, type: 3} + propertyPath: m_Name + value: RestaurantOrder + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 2bbe3f26765344e6097aa9fd0f020fbf, type: 3} +--- !u!4 &992810551159552646 stripped +Transform: + m_CorrespondingSourceObject: {fileID: 3697702677815423220, guid: 2bbe3f26765344e6097aa9fd0f020fbf, type: 3} + m_PrefabInstance: {fileID: 4510296826299878002} + m_PrefabAsset: {fileID: 0} +--- !u!1001 &4777358697124966162 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 2204914584875671904, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + propertyPath: m_LocalScale.x + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 2204914584875671904, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + propertyPath: m_LocalScale.y + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 2204914584875671904, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + propertyPath: m_LocalScale.z + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 2204914584875671904, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 2204914584875671904, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 2204914584875671904, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 2204914584875671904, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 2204914584875671904, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 2204914584875671904, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 2204914584875671904, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 2204914584875671904, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 2204914584875671904, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 2204914584875671904, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 2204914584875671904, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + propertyPath: m_ConstrainProportionsScale + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4438924429928472453, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + propertyPath: m_Name + value: Prop_CustomerTable + objectReference: {fileID: 0} + - target: {fileID: 4438924429928472453, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + propertyPath: m_StaticEditorFlags + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7856941568993672895, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + propertyPath: m_StaticEditorFlags + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8282162905857597943, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + propertyPath: m_StaticEditorFlags + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8467019391491472137, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + propertyPath: 'm_Materials.Array.data[0]' + value: + objectReference: {fileID: 2100000, guid: 56d0a02ae31152e45ba2da46f7694378, type: 2} + - target: {fileID: 8516969404588314361, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + propertyPath: m_Size.x + value: 0.93 + objectReference: {fileID: 0} + - target: {fileID: 8516969404588314361, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + propertyPath: m_Size.z + value: 0.31 + objectReference: {fileID: 0} + - target: {fileID: 8516969404588314361, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + propertyPath: m_Center.z + value: 0.14 + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: + - targetCorrespondingSourceObject: {fileID: 4111453722694982297, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + insertIndex: -1 + addedObject: {fileID: 992810551159552646} + - targetCorrespondingSourceObject: {fileID: 4111453722694982297, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + insertIndex: -1 + addedObject: {fileID: 1210262234305268138} + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} +--- !u!4 &8881739536043914635 stripped +Transform: + m_CorrespondingSourceObject: {fileID: 4111453722694982297, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + m_PrefabInstance: {fileID: 4777358697124966162} + m_PrefabAsset: {fileID: 0} diff --git a/Assets/_DDD/Restaurant/Environments/Props/Prop_CustomerTable.prefab.meta b/Assets/_DDD/Restaurant/Environments/Interactables/Prop_CustomerTable.prefab.meta similarity index 100% rename from Assets/_DDD/Restaurant/Environments/Props/Prop_CustomerTable.prefab.meta rename to Assets/_DDD/Restaurant/Environments/Interactables/Prop_CustomerTable.prefab.meta diff --git a/Assets/_DDD/Restaurant/Environments/Props/Prop_CustomerTable.prefab b/Assets/_DDD/Restaurant/Environments/Props/Prop_CustomerTable.prefab deleted file mode 100644 index 7d256654c..000000000 --- a/Assets/_DDD/Restaurant/Environments/Props/Prop_CustomerTable.prefab +++ /dev/null @@ -1,377 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!1001 &4777358697124966162 -PrefabInstance: - m_ObjectHideFlags: 0 - serializedVersion: 2 - m_Modification: - serializedVersion: 3 - m_TransformParent: {fileID: 0} - m_Modifications: - - target: {fileID: 2204914584875671904, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} - propertyPath: m_LocalScale.x - value: 2 - objectReference: {fileID: 0} - - target: {fileID: 2204914584875671904, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} - propertyPath: m_LocalScale.y - value: 2 - objectReference: {fileID: 0} - - target: {fileID: 2204914584875671904, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} - propertyPath: m_LocalScale.z - value: 2 - objectReference: {fileID: 0} - - target: {fileID: 2204914584875671904, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} - propertyPath: m_LocalPosition.x - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 2204914584875671904, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} - propertyPath: m_LocalPosition.y - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 2204914584875671904, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} - propertyPath: m_LocalPosition.z - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 2204914584875671904, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} - propertyPath: m_LocalRotation.w - value: 1 - objectReference: {fileID: 0} - - target: {fileID: 2204914584875671904, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} - propertyPath: m_LocalRotation.x - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 2204914584875671904, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} - propertyPath: m_LocalRotation.y - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 2204914584875671904, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} - propertyPath: m_LocalRotation.z - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 2204914584875671904, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} - propertyPath: m_LocalEulerAnglesHint.x - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 2204914584875671904, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} - propertyPath: m_LocalEulerAnglesHint.y - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 2204914584875671904, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} - propertyPath: m_LocalEulerAnglesHint.z - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 2204914584875671904, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} - propertyPath: m_ConstrainProportionsScale - value: 1 - objectReference: {fileID: 0} - - target: {fileID: 4438924429928472453, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} - propertyPath: m_Name - value: Prop_CustomerTable - objectReference: {fileID: 0} - - target: {fileID: 4438924429928472453, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} - propertyPath: m_StaticEditorFlags - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 7856941568993672895, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} - propertyPath: m_StaticEditorFlags - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 8282162905857597943, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} - propertyPath: m_StaticEditorFlags - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 8467019391491472137, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} - propertyPath: 'm_Materials.Array.data[0]' - value: - objectReference: {fileID: 2100000, guid: 56d0a02ae31152e45ba2da46f7694378, type: 2} - - target: {fileID: 8516969404588314361, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} - propertyPath: m_Size.x - value: 0.93 - objectReference: {fileID: 0} - - target: {fileID: 8516969404588314361, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} - propertyPath: m_Size.z - value: 0.31 - objectReference: {fileID: 0} - - target: {fileID: 8516969404588314361, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} - propertyPath: m_Center.z - value: 0.14 - objectReference: {fileID: 0} - m_RemovedComponents: [] - m_RemovedGameObjects: [] - m_AddedGameObjects: [] - m_AddedComponents: - - targetCorrespondingSourceObject: {fileID: 4438924429928472453, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} - insertIndex: -1 - addedObject: {fileID: 8605899758048842936} - - targetCorrespondingSourceObject: {fileID: 4438924429928472453, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} - insertIndex: -1 - addedObject: {fileID: 6282952769554945552} - - targetCorrespondingSourceObject: {fileID: 4438924429928472453, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} - insertIndex: -1 - addedObject: {fileID: 4598203232635129220} - m_SourcePrefab: {fileID: 100100000, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} ---- !u!1 &9211739394093953175 stripped -GameObject: - m_CorrespondingSourceObject: {fileID: 4438924429928472453, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} - m_PrefabInstance: {fileID: 4777358697124966162} - m_PrefabAsset: {fileID: 0} ---- !u!114 &8605899758048842936 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 9211739394093953175} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: c0b1e0992510498b8d33d5b6094b8f4b, type: 3} - m_Name: - m_EditorClassIdentifier: - _interactionType: 4 - _executionParameters: - _holdTime: 1 - _displayParameters: - _messageKey: - _interactionAvailableFlows: 1 - _aiInteractionPoints: [] - _initialOrderInteractionType: 0 ---- !u!114 &6282952769554945552 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 9211739394093953175} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 888380afc233049ce9e618f9f36c8ba8, type: 3} - m_Name: - m_EditorClassIdentifier: - profile: {fileID: 0} - profileSync: 0 - camerasLayerMask: - serializedVersion: 2 - m_Bits: 4294967295 - effectGroup: 0 - effectTarget: {fileID: 0} - effectGroupLayer: - serializedVersion: 2 - m_Bits: 4294967295 - effectNameFilter: - effectNameUseRegEx: 0 - combineMeshes: 0 - alphaCutOff: 0 - cullBackFaces: 1 - padding: 0 - ignoreObjectVisibility: 0 - reflectionProbes: 0 - GPUInstancing: 1 - sortingPriority: 0 - optimizeSkinnedMesh: 1 - depthClip: 0 - cameraDistanceFade: 0 - cameraDistanceFadeNear: 0 - cameraDistanceFadeFar: 1000 - normalsOption: 0 - ignore: 0 - _highlighted: 0 - fadeInDuration: 0 - fadeOutDuration: 0 - flipY: 0 - constantWidth: 1 - extraCoveragePixels: 0 - minimumWidth: 0 - subMeshMask: -1 - overlay: 0 - overlayMode: 0 - overlayColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1} - overlayAnimationSpeed: 1 - overlayMinIntensity: 0.5 - overlayBlending: 1 - overlayTexture: {fileID: 0} - overlayTextureUVSpace: 0 - overlayTextureScale: 1 - overlayTextureScrolling: {x: 0, y: 0} - overlayVisibility: 0 - outline: 1 - outlineColor: {r: 0, g: 0, b: 0, a: 1} - outlineColorStyle: 0 - outlineGradient: - serializedVersion: 2 - key0: {r: 1, g: 1, b: 1, a: 1} - key1: {r: 1, g: 1, b: 1, a: 1} - key2: {r: 0, g: 0, b: 0, a: 0} - key3: {r: 0, g: 0, b: 0, a: 0} - key4: {r: 0, g: 0, b: 0, a: 0} - key5: {r: 0, g: 0, b: 0, a: 0} - key6: {r: 0, g: 0, b: 0, a: 0} - key7: {r: 0, g: 0, b: 0, a: 0} - ctime0: 0 - ctime1: 65535 - ctime2: 0 - ctime3: 0 - ctime4: 0 - ctime5: 0 - ctime6: 0 - ctime7: 0 - atime0: 0 - atime1: 65535 - atime2: 0 - atime3: 0 - atime4: 0 - atime5: 0 - atime6: 0 - atime7: 0 - m_Mode: 0 - m_ColorSpace: -1 - m_NumColorKeys: 2 - m_NumAlphaKeys: 2 - outlineGradientInLocalSpace: 0 - outlineWidth: 0.45 - outlineBlurPasses: 2 - outlineQuality: 3 - outlineEdgeMode: 0 - outlineEdgeThreshold: 0.995 - outlineSharpness: 1 - outlineDownsampling: 1 - outlineVisibility: 0 - glowBlendMode: 0 - outlineBlitDebug: 0 - outlineIndependent: 0 - outlineContourStyle: 0 - outlineMaskMode: 0 - glow: 0 - glowWidth: 0.4 - glowQuality: 3 - glowBlurMethod: 0 - glowDownsampling: 2 - glowHQColor: {r: 0.64, g: 1, b: 0, a: 1} - glowDithering: 1 - glowDitheringStyle: 0 - glowMagicNumber1: 0.75 - glowMagicNumber2: 0.5 - glowAnimationSpeed: 1 - glowVisibility: 0 - glowBlitDebug: 0 - glowBlendPasses: 1 - glowPasses: - - offset: 4 - alpha: 0.1 - color: {r: 0.64, g: 1, b: 0, a: 1} - - offset: 3 - alpha: 0.2 - color: {r: 0.64, g: 1, b: 0, a: 1} - - offset: 2 - alpha: 0.3 - color: {r: 0.64, g: 1, b: 0, a: 1} - - offset: 1 - alpha: 0.4 - color: {r: 0.64, g: 1, b: 0, a: 1} - glowMaskMode: 0 - innerGlow: 0 - innerGlowWidth: 1 - innerGlowColor: {r: 1, g: 1, b: 1, a: 1} - innerGlowBlendMode: 0 - innerGlowVisibility: 0 - targetFX: 0 - targetFXTexture: {fileID: 0} - targetFXColor: {r: 1, g: 1, b: 1, a: 1} - targetFXCenter: {fileID: 0} - targetFXRotationSpeed: 50 - targetFXInitialScale: 4 - targetFXEndScale: 1.5 - targetFXScaleToRenderBounds: 1 - targetFXUseEnclosingBounds: 0 - targetFXAlignToGround: 0 - targetFXOffset: {x: 0, y: 0, z: 0} - targetFXFadePower: 32 - targetFXGroundMaxDistance: 10 - targetFXGroundLayerMask: - serializedVersion: 2 - m_Bits: 4294967295 - targetFXTransitionDuration: 0.5 - targetFXStayDuration: 1.5 - targetFXVisibility: 1 - iconFX: 0 - iconFXMesh: {fileID: 0} - iconFXLightColor: {r: 1, g: 1, b: 1, a: 1} - iconFXDarkColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} - iconFXCenter: {fileID: 0} - iconFXRotationSpeed: 50 - iconFXAnimationOption: 0 - iconFXAnimationAmount: 0.1 - iconFXAnimationSpeed: 3 - iconFXScale: 1 - iconFXScaleToRenderBounds: 0 - iconFXOffset: {x: 0, y: 1, z: 0} - iconFXTransitionDuration: 0.5 - iconFXStayDuration: 1.5 - seeThrough: 2 - seeThroughOccluderMask: - serializedVersion: 2 - m_Bits: 4294967295 - seeThroughOccluderThreshold: 0.3 - seeThroughOccluderMaskAccurate: 0 - seeThroughOccluderCheckInterval: 1 - seeThroughOccluderCheckIndividualObjects: 0 - seeThroughDepthOffset: 0 - seeThroughMaxDepth: 0 - seeThroughIntensity: 0.8 - seeThroughTintAlpha: 0.5 - seeThroughTintColor: {r: 1, g: 0, b: 0, a: 1} - seeThroughNoise: 1 - seeThroughBorder: 0 - seeThroughBorderColor: {r: 0, g: 0, b: 0, a: 1} - seeThroughBorderOnly: 0 - seeThroughBorderWidth: 0.45 - seeThroughOrdered: 0 - seeThroughTexture: {fileID: 0} - seeThroughTextureUVSpace: 0 - seeThroughTextureScale: 1 - seeThroughChildrenSortingMode: 0 - rmsCount: 1 - hitFxInitialIntensity: 0 - hitFxMode: 0 - hitFxFadeOutDuration: 0.25 - hitFxColor: {r: 1, g: 1, b: 1, a: 1} - hitFxRadius: 0.5 ---- !u!114 &4598203232635129220 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 9211739394093953175} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: f0feb22ab60a4d1885271637838f43b9, type: 3} - m_Name: - m_EditorClassIdentifier: - _availableStyle: - Color: {r: 1, g: 1, b: 1, a: 1} - Width: 1 - Opacity: 1 - _focusedStyle: - Color: {r: 1, g: 0.92156863, b: 0.015686275, a: 1} - Width: 1 - Opacity: 1 - _unavailableStyle: - Color: {r: 0.5, g: 0.5, b: 0.5, a: 1} - Width: 0.5 - Opacity: 1 - _objectiveStyle: - Color: {r: 0, g: 1, b: 1, a: 1} - Width: 1 - Opacity: 1 - _breathingSpeed: 2 - _breathingRange: 0.3 - _enableBreathingEffect: 1 - _alphaCutOff: 0.5 - _combineMeshes: 1 - _constantWidth: 1 - _outlineQuality: 2 - _outlineIndependent: 1 - _outlineBlurPasses: 1 - _outlineSharpness: 8 - _currentOutlineType: 0 - _currentOpacityMultiplier: 1 diff --git a/Assets/_DDD/_Scripts/GameEvent/IInteractable.cs b/Assets/_DDD/_Scripts/GameEvent/IInteractable.cs index c52bd47fd..9f65a3ddc 100644 --- a/Assets/_DDD/_Scripts/GameEvent/IInteractable.cs +++ b/Assets/_DDD/_Scripts/GameEvent/IInteractable.cs @@ -20,7 +20,7 @@ public struct InteractionExecutionParameters { [SerializeField] private float _holdTime; public float HoldTime => _holdTime; - public InteractionExecutionParameters(float holdTime = 1f) + public InteractionExecutionParameters(float holdTime = 0f) { _holdTime = holdTime; } diff --git a/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteractionSubsystem.cs b/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteractionSubsystem.cs index 40b619387..97c52ac12 100644 --- a/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteractionSubsystem.cs +++ b/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteractionSubsystem.cs @@ -6,11 +6,12 @@ namespace DDD [Flags] public enum RestaurantOrderType : uint { - Wait = 0, - Order = 1u << 0, - Serve = 1u << 1, + Wait = 0u, + Reserved = 1u, + Order = 1u << 1, + Serve = 1u << 2, } - + public class RestaurantOrderInteractionSubsystem : MonoBehaviour, IInteractionSubsystemObject { [SerializeField] protected RestaurantOrderType orderType = RestaurantOrderType.Wait; @@ -25,11 +26,8 @@ private void Start() public bool CanInteract() { - // 현재 RestaurantOrderInteractionType를 수행할 수 있는지? if (GetInteractionSubsystemType() == RestaurantOrderType.Wait) { - Debug.Assert(false); // TODO - // Check WaitCustomer return true; } return false; @@ -37,7 +35,6 @@ public bool CanInteract() public bool OnInteracted(IInteractor interactor, ScriptableObject payloadSo = null) { - // _currentRestaurantOrderInteractionType에 따라 동작이 달라지겠지 if (GetInteractionSubsystemType() == RestaurantOrderType.Wait) { // DO WAIT CUSTOMER diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrderSolver.cs b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrderSolver.cs index 38bfe2d87..6fc753ec3 100644 --- a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrderSolver.cs +++ b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrderSolver.cs @@ -10,6 +10,7 @@ public static class RestaurantOrderSolvers public static Dictionary TypeToOrderSolver = new() { { RestaurantOrderType.Wait, typeof(RestaurantOrderSolver_Wait) }, + { RestaurantOrderType.Reserved, typeof(RestaurantOrderSolver_Reserved) }, { RestaurantOrderType.Order, typeof(RestaurantOrderSolver_Order) }, { RestaurantOrderType.Serve, typeof(RestaurantOrderSolver_Serve) } }; diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Reserved.cs b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Reserved.cs new file mode 100644 index 000000000..c12a9ab2b --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Reserved.cs @@ -0,0 +1,19 @@ +using UnityEngine; + +namespace DDD.RestaurantOrders +{ + public class RestaurantOrderSolver_Reserved : MonoBehaviour, IInteractionSubsystemSolver + { + public bool ExecuteInteractionSubsystem(IInteractor interactor, IInteractable interactable, ScriptableObject payloadSo = null) + { + // TODO : DO SOMETHING!!! + return true; + } + + public bool CanExecuteInteractionSubsystem(IInteractor interactor = null, IInteractable interactable = null, + ScriptableObject payloadSo = null) + { + return true; + } + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Reserved.cs.meta b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Reserved.cs.meta new file mode 100644 index 000000000..9d4d3307f --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrders/RestaurantOrderSolver_Reserved.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f62abf1706184529880b9353b63a2adc +timeCreated: 1755682089 \ No newline at end of file From 56a08801847d746bcf02a656815d0eec46e20c87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=82=B0?= Date: Wed, 20 Aug 2025 18:55:30 +0900 Subject: [PATCH 12/36] =?UTF-8?q?=EC=84=9C=EB=B8=8C=20=EC=8B=9C=EC=8A=A4?= =?UTF-8?q?=ED=85=9C=20=EC=88=98=EC=A0=95,=20=EC=9D=B8=ED=84=B0=EB=A0=89?= =?UTF-8?q?=ED=8A=B8=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EC=97=90=20?= =?UTF-8?q?=EC=84=9C=EB=B8=8C=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Environments/Props/Prop_Open.prefab | 26 ++++++++-- .../Prop/MenuBoard/PropMenuBoard.prefab | 22 ++++++++- .../Prefabs/RestaurantPlayer.prefab | 2 +- .../_DDD/_Scripts/GameEvent/IInteractable.cs | 7 ++- .../GameEvent/InteractionSubsystem.cs | 1 - .../IInteractionSubsystemOwner.cs | 10 ++++ .../IInteractionSubsystemOwner.cs.meta | 3 ++ .../RestaurantManagementInteraction.cs | 16 ------ ...estaurantManagementInteractionSubsystem.cs | 36 ++++++++++++++ ...antManagementInteractionSubsystem.cs.meta} | 0 .../RestaurantOrderInteractionSubsystem.cs | 4 +- .../RestaurantEnvironment.cs | 6 +++ .../RestaurantInteractionComponent.cs | 29 ++++++++++- .../RestaurantInteractionEvents.cs | 3 +- .../Solvers/RestaurantManagements.meta | 3 ++ .../RestaurantManagementSolver.cs | 23 +++++++++ .../RestaurantManagementSolver.cs.meta | 3 ++ .../RestaurantManagementSolver_Menu.cs} | 8 +-- .../RestaurantManagementSolver_Menu.cs.meta} | 0 .../RestaurantManagementSolver_Start.cs} | 21 ++++---- .../RestaurantManagementSolver_Start.cs.meta} | 0 .../Solvers/RestaurantOrderSolver.cs | 37 ++------------ .../Solvers/RestaurantSubsystemSolver.cs | 49 +++++++++++++++++++ .../Solvers/RestaurantSubsystemSolver.cs.meta | 3 ++ 24 files changed, 230 insertions(+), 82 deletions(-) create mode 100644 Assets/_DDD/_Scripts/RestaurantEnvironment/IInteractionSubsystemOwner.cs create mode 100644 Assets/_DDD/_Scripts/RestaurantEnvironment/IInteractionSubsystemOwner.cs.meta delete mode 100644 Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantManagementInteraction.cs create mode 100644 Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantManagementInteractionSubsystem.cs rename Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/{RestaurantManagementInteraction.cs.meta => RestaurantManagementInteractionSubsystem.cs.meta} (100%) create mode 100644 Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantManagements.meta create mode 100644 Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantManagements/RestaurantManagementSolver.cs create mode 100644 Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantManagements/RestaurantManagementSolver.cs.meta rename Assets/_DDD/_Scripts/RestaurantEvent/Solvers/{RestaurantManagementUiEventSolver.cs => RestaurantManagements/RestaurantManagementSolver_Menu.cs} (51%) rename Assets/_DDD/_Scripts/RestaurantEvent/Solvers/{RestaurantManagementUiEventSolver.cs.meta => RestaurantManagements/RestaurantManagementSolver_Menu.cs.meta} (100%) rename Assets/_DDD/_Scripts/RestaurantEvent/Solvers/{RestaurantOpenEventSolver.cs => RestaurantManagements/RestaurantManagementSolver_Start.cs} (53%) rename Assets/_DDD/_Scripts/RestaurantEvent/Solvers/{RestaurantOpenEventSolver.cs.meta => RestaurantManagements/RestaurantManagementSolver_Start.cs.meta} (100%) create mode 100644 Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantSubsystemSolver.cs create mode 100644 Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantSubsystemSolver.cs.meta diff --git a/Assets/_DDD/Restaurant/Environments/Props/Prop_Open.prefab b/Assets/_DDD/Restaurant/Environments/Props/Prop_Open.prefab index fa7374f77..e3535c2f0 100644 --- a/Assets/_DDD/Restaurant/Environments/Props/Prop_Open.prefab +++ b/Assets/_DDD/Restaurant/Environments/Props/Prop_Open.prefab @@ -113,6 +113,9 @@ PrefabInstance: - targetCorrespondingSourceObject: {fileID: 4438924429928472453, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} insertIndex: -1 addedObject: {fileID: 6899480242032072806} + - targetCorrespondingSourceObject: {fileID: 4438924429928472453, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + insertIndex: -1 + addedObject: {fileID: -4132148316966952702} m_SourcePrefab: {fileID: 100100000, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} --- !u!1 &9211739394093953175 stripped GameObject: @@ -131,10 +134,14 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 201f9e6d7ca7404baa9945950292a392, type: 3} m_Name: m_EditorClassIdentifier: - _interactionType: 2 - _holdTime: 0 - _interactionMessageKey: Test + _interactionType: 1 + _executionParameters: + _holdTime: 1 + _displayParameters: + _messageKey: _interactionAvailableFlows: 1 + _aiInteractionPoints: [] + autoInitialize: 1 --- !u!114 &3538352761187622062 MonoBehaviour: m_ObjectHideFlags: 0 @@ -375,3 +382,16 @@ MonoBehaviour: _outlineSharpness: 8 _currentOutlineType: 0 _currentOpacityMultiplier: 1 +--- !u!114 &-4132148316966952702 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9211739394093953175} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1235f6bde9304d8f85079f2777bd4b3c, type: 3} + m_Name: + m_EditorClassIdentifier: + _managementType: 1 diff --git a/Assets/_DDD/_Addressables/Environments/Prop/MenuBoard/PropMenuBoard.prefab b/Assets/_DDD/_Addressables/Environments/Prop/MenuBoard/PropMenuBoard.prefab index ddf5310b2..149bc761d 100644 --- a/Assets/_DDD/_Addressables/Environments/Prop/MenuBoard/PropMenuBoard.prefab +++ b/Assets/_DDD/_Addressables/Environments/Prop/MenuBoard/PropMenuBoard.prefab @@ -97,6 +97,9 @@ PrefabInstance: - targetCorrespondingSourceObject: {fileID: 4438924429928472453, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} insertIndex: -1 addedObject: {fileID: 5410819217098966190} + - targetCorrespondingSourceObject: {fileID: 4438924429928472453, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} + insertIndex: -1 + addedObject: {fileID: -1096657863250535257} m_SourcePrefab: {fileID: 100100000, guid: 1d634c3376e4a4684bc984ced9134847, type: 3} --- !u!1 &580268897300907643 stripped GameObject: @@ -116,9 +119,12 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: _interactionType: 1 - _holdTime: 0 - _interactionMessageKey: Test + _executionParameters: + _holdTime: 1 + _displayParameters: + _messageKey: _interactionAvailableFlows: 1 + _aiInteractionPoints: [] --- !u!114 &4545680930728379745 MonoBehaviour: m_ObjectHideFlags: 0 @@ -359,3 +365,15 @@ MonoBehaviour: _outlineSharpness: 8 _currentOutlineType: 0 _currentOpacityMultiplier: 1 +--- !u!114 &-1096657863250535257 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 580268897300907643} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1235f6bde9304d8f85079f2777bd4b3c, type: 3} + m_Name: + m_EditorClassIdentifier: diff --git a/Assets/_DDD/_Addressables/Prefabs/RestaurantPlayer.prefab b/Assets/_DDD/_Addressables/Prefabs/RestaurantPlayer.prefab index c1e3a3a75..cc9051a7a 100644 --- a/Assets/_DDD/_Addressables/Prefabs/RestaurantPlayer.prefab +++ b/Assets/_DDD/_Addressables/Prefabs/RestaurantPlayer.prefab @@ -423,7 +423,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 81e01dd8c1cc3404d805400eba1bb4ae, type: 3} m_Name: m_EditorClassIdentifier: - _interactionType: 4294967287 + _availableInteractions: 1 _nearColliders: - {fileID: 0} - {fileID: 0} diff --git a/Assets/_DDD/_Scripts/GameEvent/IInteractable.cs b/Assets/_DDD/_Scripts/GameEvent/IInteractable.cs index c52bd47fd..a0c5cbe95 100644 --- a/Assets/_DDD/_Scripts/GameEvent/IInteractable.cs +++ b/Assets/_DDD/_Scripts/GameEvent/IInteractable.cs @@ -8,10 +8,9 @@ namespace DDD public enum InteractionType : uint { None = 0u, - RestaurantManagementUi = 1u << 0, - OpenRestaurant = 1u << 1, - RestaurantOrder = 1u << 2, - RestaurantMeal = 1u << 3, + RestaurantManagement = 1u << 0, + RestaurantOrder = 1u << 1, + RestaurantMeal = 1u << 2, All = 0xFFFFFFFFu } diff --git a/Assets/_DDD/_Scripts/GameEvent/InteractionSubsystem.cs b/Assets/_DDD/_Scripts/GameEvent/InteractionSubsystem.cs index 1085117d3..655419a6e 100644 --- a/Assets/_DDD/_Scripts/GameEvent/InteractionSubsystem.cs +++ b/Assets/_DDD/_Scripts/GameEvent/InteractionSubsystem.cs @@ -5,7 +5,6 @@ namespace DDD { public interface IInteractionSubsystemObject { - Type GetSubsystemEnumType(); void InitializeSubsystem(); bool CanInteract(); bool OnInteracted(IInteractor interactor, ScriptableObject payloadSo = null); diff --git a/Assets/_DDD/_Scripts/RestaurantEnvironment/IInteractionSubsystemOwner.cs b/Assets/_DDD/_Scripts/RestaurantEnvironment/IInteractionSubsystemOwner.cs new file mode 100644 index 000000000..bd99e64be --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantEnvironment/IInteractionSubsystemOwner.cs @@ -0,0 +1,10 @@ +using System; +using UnityEngine; + +namespace DDD +{ + public interface IInteractionSubsystemOwner + { + public bool TryGetSubsystemObject(out IInteractionSubsystemObject subsystemObject) where T : Enum; + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEnvironment/IInteractionSubsystemOwner.cs.meta b/Assets/_DDD/_Scripts/RestaurantEnvironment/IInteractionSubsystemOwner.cs.meta new file mode 100644 index 000000000..d15ead482 --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantEnvironment/IInteractionSubsystemOwner.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 254d64ce6ee84b20a154931fcf04958f +timeCreated: 1755681729 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantManagementInteraction.cs b/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantManagementInteraction.cs deleted file mode 100644 index c2df3a351..000000000 --- a/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantManagementInteraction.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace DDD -{ - [Flags] - public enum RestaurantManagementType : uint - { - OpenRestaurantMenu = 0, - StartRestaurant = 1, - } - - //public class RestaurantManagementInteraction : RestaurantInteractionComponent, IInteractionSubsystemObject - //{ - // - //} -} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantManagementInteractionSubsystem.cs b/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantManagementInteractionSubsystem.cs new file mode 100644 index 000000000..8be8f9b3f --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantManagementInteractionSubsystem.cs @@ -0,0 +1,36 @@ +using System; +using UnityEngine; + +namespace DDD +{ + [Flags] + public enum RestaurantManagementType : uint + { + OpenRestaurantMenu = 0, + StartRestaurant = 1, + } + + public class RestaurantManagementInteractionSubsystem : MonoBehaviour, IInteractionSubsystemObject + { + [SerializeField] protected RestaurantManagementType _managementType = RestaurantManagementType.OpenRestaurantMenu; + public RestaurantManagementType GetInteractionSubsystemType() + { + return _managementType; + } + + public void InitializeSubsystem() + { + + } + + public bool CanInteract() + { + return true; + } + + public bool OnInteracted(IInteractor interactor, ScriptableObject payloadSo = null) + { + return true; + } + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantManagementInteraction.cs.meta b/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantManagementInteractionSubsystem.cs.meta similarity index 100% rename from Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantManagementInteraction.cs.meta rename to Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantManagementInteractionSubsystem.cs.meta diff --git a/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteractionSubsystem.cs b/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteractionSubsystem.cs index 40b619387..59ec44864 100644 --- a/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteractionSubsystem.cs +++ b/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteractionSubsystem.cs @@ -15,9 +15,7 @@ public class RestaurantOrderInteractionSubsystem : MonoBehaviour, IInteractionSu { [SerializeField] protected RestaurantOrderType orderType = RestaurantOrderType.Wait; private RestaurantOrderType currentRestaurantOrderType; - - public Type GetSubsystemEnumType() => typeof(RestaurantOrderType); - + private void Start() { currentRestaurantOrderType = orderType; diff --git a/Assets/_DDD/_Scripts/RestaurantEnvironment/RestaurantEnvironment.cs b/Assets/_DDD/_Scripts/RestaurantEnvironment/RestaurantEnvironment.cs index a2d647545..ea9252369 100644 --- a/Assets/_DDD/_Scripts/RestaurantEnvironment/RestaurantEnvironment.cs +++ b/Assets/_DDD/_Scripts/RestaurantEnvironment/RestaurantEnvironment.cs @@ -1,3 +1,4 @@ +using System; using Spine.Unity; using Unity.VisualScripting; using UnityEngine; @@ -13,6 +14,11 @@ public class RestaurantEnvironment : MonoBehaviour private Transform _visualLook; private Renderer _renderer; + private void Start() + { + Initialize(restaurantPropLocation); + } + public async void Initialize(RestaurantPropLocation location) { EnvironmentData environmentData = DataManager.Instance.GetDataSo().GetDataById(location.Id); diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionComponent.cs b/Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionComponent.cs index 72090e59d..2cf9c4102 100644 --- a/Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionComponent.cs +++ b/Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionComponent.cs @@ -11,12 +11,13 @@ public static class RestaurantInteractionSubsystems { public static Dictionary TypeToSubsystem = new() { - {InteractionType.RestaurantOrder, typeof(RestaurantOrderInteractionSubsystem)} + {InteractionType.RestaurantOrder, typeof(RestaurantOrderInteractionSubsystem)}, + {InteractionType.RestaurantManagement, typeof(RestaurantManagementInteractionSubsystem)} }; } - public class RestaurantInteractionComponent : MonoBehaviour, IInteractable + public class RestaurantInteractionComponent : MonoBehaviour, IInteractable, IInteractionSubsystemOwner { // Single interaction type [ValueDropdown("GetAllInteractionTypes")] @@ -25,9 +26,18 @@ public class RestaurantInteractionComponent : MonoBehaviour, IInteractable [SerializeField] protected InteractionDisplayParameters _displayParameters = new InteractionDisplayParameters(""); [SerializeField] protected GameFlowState _interactionAvailableFlows; [SerializeField] private Transform[] _aiInteractionPoints; + [SerializeField] private bool autoInitialize = true; private Dictionary _subsystems = new(); + private void Start() + { + if (autoInitialize) + { + InitializeInteraction(_interactionType); + } + } + private static IEnumerable GetAllInteractionTypes() { return System.Enum.GetValues(typeof(InteractionType)) @@ -153,5 +163,20 @@ public Vector3[] GetInteractionPoints() } return positions; } + + public bool TryGetSubsystemObject(out IInteractionSubsystemObject subsystemObject) where T : Enum + { + foreach (var interactionSubsystemObject in _subsystems.Values) + { + if (interactionSubsystemObject is IInteractionSubsystemObject subsystem) + { + subsystemObject = subsystem; + return true; + } + } + + subsystemObject = null; + return false; + } } } \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionEvents.cs b/Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionEvents.cs index 6d9021765..2afa9b785 100644 --- a/Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionEvents.cs +++ b/Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionEvents.cs @@ -13,8 +13,7 @@ public static class RestaurantInteractionEventSolvers { public static Dictionary TypeToSolver = new() { - {InteractionType.RestaurantManagementUi, typeof(RestaurantManagementUiEventSolver)}, - {InteractionType.OpenRestaurant, typeof(RestaurantOpenEventSolver)}, + {InteractionType.RestaurantManagement, typeof(RestaurantManagementSolver)}, {InteractionType.RestaurantOrder, typeof(RestaurantOrderSolver)} }; } diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantManagements.meta b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantManagements.meta new file mode 100644 index 000000000..da3e18e6c --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantManagements.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ad4643acb4b34f1ab27e79503ccff0dd +timeCreated: 1755677592 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantManagements/RestaurantManagementSolver.cs b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantManagements/RestaurantManagementSolver.cs new file mode 100644 index 000000000..0681cfb69 --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantManagements/RestaurantManagementSolver.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using DDD.RestaurantOrders; +using UnityEngine; + +namespace DDD +{ + public static class RestaurantManagementSolvers + { + public static Dictionary TypeToManagementSolver = new() + { + { RestaurantManagementType.OpenRestaurantMenu, typeof(RestaurantManagementSolver_Menu) }, + { RestaurantManagementType.StartRestaurant, typeof(RestaurantManagementSolver_Start) } + }; + } + public class RestaurantManagementSolver : RestaurantSubsystemSolver + { + protected override Dictionary GetSubsystemSolverTypeMappings() + { + return RestaurantManagementSolvers.TypeToManagementSolver; + } + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantManagements/RestaurantManagementSolver.cs.meta b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantManagements/RestaurantManagementSolver.cs.meta new file mode 100644 index 000000000..a9847e43f --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantManagements/RestaurantManagementSolver.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c457857b6ba0432db546ef0d8970548d +timeCreated: 1755677639 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantManagementUiEventSolver.cs b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantManagements/RestaurantManagementSolver_Menu.cs similarity index 51% rename from Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantManagementUiEventSolver.cs rename to Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantManagements/RestaurantManagementSolver_Menu.cs index c740cfb4f..4c4e3ab26 100644 --- a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantManagementUiEventSolver.cs +++ b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantManagements/RestaurantManagementSolver_Menu.cs @@ -2,11 +2,11 @@ namespace DDD { - public class RestaurantManagementUiEventSolver : MonoBehaviour, IInteractionSolver + public class RestaurantManagementSolver_Menu : MonoBehaviour, IInteractionSubsystemSolver { - public bool ExecuteInteraction(IInteractor interactor, IInteractable interactable, ScriptableObject payloadSo = null) + public bool ExecuteInteractionSubsystem(IInteractor interactor, IInteractable interactable, ScriptableObject payloadSo = null) { - if (CanExecuteInteraction() == false) return false; + if (CanExecuteInteractionSubsystem(interactor, interactable, payloadSo) == false) return false; var evt = GameEvents.OpenPopupUiEvent; evt.UiType = typeof(RestaurantManagementUi); @@ -14,7 +14,7 @@ public bool ExecuteInteraction(IInteractor interactor, IInteractable interactabl return true; } - public bool CanExecuteInteraction(IInteractor interactor = null, IInteractable interactable = null, + public bool CanExecuteInteractionSubsystem(IInteractor interactor = null, IInteractable interactable = null, ScriptableObject payloadSo = null) { GameFlowState currentGameFlowState = GameFlowManager.Instance.GameFlowDataSo.CurrentGameState; diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantManagementUiEventSolver.cs.meta b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantManagements/RestaurantManagementSolver_Menu.cs.meta similarity index 100% rename from Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantManagementUiEventSolver.cs.meta rename to Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantManagements/RestaurantManagementSolver_Menu.cs.meta diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOpenEventSolver.cs b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantManagements/RestaurantManagementSolver_Start.cs similarity index 53% rename from Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOpenEventSolver.cs rename to Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantManagements/RestaurantManagementSolver_Start.cs index ccc92fd90..fe7f96388 100644 --- a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOpenEventSolver.cs +++ b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantManagements/RestaurantManagementSolver_Start.cs @@ -3,22 +3,23 @@ namespace DDD { - public class RestaurantOpenEventSolver : MonoBehaviour, IInteractionSolver + public class RestaurantManagementSolver_Start : MonoBehaviour, IInteractionSubsystemSolver { - public bool ExecuteInteraction(IInteractor interactor, IInteractable interactable, ScriptableObject interactionPayloadSo = null) - { - if (CanExecuteInteraction() == false) return false; - - _ = GameFlowManager.Instance.ChangeFlow(GameFlowState.RunRestaurant); - return true; - } - private RestaurantManagementState GetManagementState() { return RestaurantState.Instance.ManagementState; } - public bool CanExecuteInteraction(IInteractor interactor = null, IInteractable interactable = null, ScriptableObject payloadSo = null) + public bool ExecuteInteractionSubsystem(IInteractor interactor, IInteractable interactable, ScriptableObject payloadSo = null) + { + if (CanExecuteInteractionSubsystem(interactor, interactable, payloadSo) == false) return false; + + _ = GameFlowManager.Instance.ChangeFlow(GameFlowState.RunRestaurant); + return true; + } + + public bool CanExecuteInteractionSubsystem(IInteractor interactor = null, IInteractable interactable = null, + ScriptableObject payloadSo = null) { GameFlowState currentGameFlowState = GameFlowManager.Instance.GameFlowDataSo.CurrentGameState; diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOpenEventSolver.cs.meta b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantManagements/RestaurantManagementSolver_Start.cs.meta similarity index 100% rename from Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOpenEventSolver.cs.meta rename to Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantManagements/RestaurantManagementSolver_Start.cs.meta diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrderSolver.cs b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrderSolver.cs index 38bfe2d87..b54ef3956 100644 --- a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrderSolver.cs +++ b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantOrderSolver.cs @@ -15,42 +15,11 @@ public static class RestaurantOrderSolvers }; } - public class RestaurantOrderSolver : MonoBehaviour, IInteractionSolver + public class RestaurantOrderSolver : RestaurantSubsystemSolver { - private Dictionary> _solvers = new(); - - private void Start() + protected override Dictionary GetSubsystemSolverTypeMappings() { - foreach (var orderSolver in RestaurantOrderSolvers.TypeToOrderSolver) - { - var solver = (IInteractionSubsystemSolver)gameObject.AddComponent(orderSolver.Value); - _solvers.Add(orderSolver.Key, solver); - } - } - - public bool ExecuteInteraction(IInteractor interactor, IInteractable interactable, ScriptableObject payloadSo = null) - { - return TryGetSolver(interactable, out var solver) && - solver.ExecuteInteractionSubsystem(interactor, interactable, payloadSo); - } - - public bool CanExecuteInteraction(IInteractor interactor = null, IInteractable interactable = null, - ScriptableObject payloadSo = null) - { - return TryGetSolver(interactable, out var solver) && - solver.CanExecuteInteractionSubsystem(interactor, interactable, payloadSo); - } - - // Solver를 가져오는 공통 로직 - private bool TryGetSolver(IInteractable interactable, out IInteractionSubsystemSolver solver) - { - solver = null; - - if (interactable is not IInteractionSubsystemObject subsystem) - return false; - - var type = subsystem.GetInteractionSubsystemType(); - return _solvers.TryGetValue(type, out solver); + return RestaurantOrderSolvers.TypeToOrderSolver; } } } \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantSubsystemSolver.cs b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantSubsystemSolver.cs new file mode 100644 index 000000000..6edbf5102 --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantSubsystemSolver.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace DDD +{ + public abstract class RestaurantSubsystemSolver : MonoBehaviour, IInteractionSolver where T : Enum + { + private Dictionary> _solvers = new(); + + protected abstract Dictionary GetSubsystemSolverTypeMappings(); + + private void Start() + { + foreach (var subsystemSolverType in GetSubsystemSolverTypeMappings()) + { + var solver = (IInteractionSubsystemSolver)gameObject.AddComponent(subsystemSolverType.Value); + _solvers.Add(subsystemSolverType.Key, solver); + } + } + public bool ExecuteInteraction(IInteractor interactor, IInteractable interactable, ScriptableObject payloadSo = null) + { + return TryGetSolver(interactable, out var solver) && + solver.ExecuteInteractionSubsystem(interactor, interactable, payloadSo); + } + + public bool CanExecuteInteraction(IInteractor interactor = null, IInteractable interactable = null, + ScriptableObject payloadSo = null) + { + return TryGetSolver(interactable, out var solver) && + solver.CanExecuteInteractionSubsystem(interactor, interactable, payloadSo); + } + + // Solver를 가져오는 공통 로직 + private bool TryGetSolver(IInteractable interactable, out IInteractionSubsystemSolver solver) + { + solver = null; + + var owner = interactable as IInteractionSubsystemOwner; + IInteractionSubsystemObject subsystem = null; + bool isExist = owner != null && owner.TryGetSubsystemObject(out subsystem); + + if (!isExist || subsystem == null) return false; + + var type = subsystem.GetInteractionSubsystemType(); + return _solvers.TryGetValue(type, out solver); + } + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantSubsystemSolver.cs.meta b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantSubsystemSolver.cs.meta new file mode 100644 index 000000000..a27412565 --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantEvent/Solvers/RestaurantSubsystemSolver.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 716224c78a914321b7b7e43a93860465 +timeCreated: 1755677990 \ No newline at end of file From 117ed8228b006c9a4d9293f1ed0f0a634c75ce27 Mon Sep 17 00:00:00 2001 From: Jeonghyeon Date: Wed, 20 Aug 2025 10:00:39 +0000 Subject: [PATCH 13/36] Update Assets/_DDD/_Scripts/RestaurantEnvironment/RestaurantEnvironment.cs --- .../_Scripts/RestaurantEnvironment/RestaurantEnvironment.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Assets/_DDD/_Scripts/RestaurantEnvironment/RestaurantEnvironment.cs b/Assets/_DDD/_Scripts/RestaurantEnvironment/RestaurantEnvironment.cs index ea9252369..344fd6309 100644 --- a/Assets/_DDD/_Scripts/RestaurantEnvironment/RestaurantEnvironment.cs +++ b/Assets/_DDD/_Scripts/RestaurantEnvironment/RestaurantEnvironment.cs @@ -14,11 +14,6 @@ public class RestaurantEnvironment : MonoBehaviour private Transform _visualLook; private Renderer _renderer; - private void Start() - { - Initialize(restaurantPropLocation); - } - public async void Initialize(RestaurantPropLocation location) { EnvironmentData environmentData = DataManager.Instance.GetDataSo().GetDataById(location.Id); From 0dc138ea2f7ddd243c52d2b5dc1c7dc438f0eae4 Mon Sep 17 00:00:00 2001 From: NTG Date: Wed, 20 Aug 2025 20:15:49 +0900 Subject: [PATCH 14/36] =?UTF-8?q?uibutton=20=EC=9E=84=EC=8B=9C=EC=A0=80?= =?UTF-8?q?=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/_DDD/_Scripts/GameUi/UiButton.cs | 390 ++++++++++++++++++ Assets/_DDD/_Scripts/GameUi/UiButton.cs.meta | 3 + Assets/_DDD/_Scripts/GameUi/UiButtonTest.cs | 133 ++++++ .../_DDD/_Scripts/GameUi/UiButtonTest.cs.meta | 3 + 4 files changed, 529 insertions(+) create mode 100644 Assets/_DDD/_Scripts/GameUi/UiButton.cs create mode 100644 Assets/_DDD/_Scripts/GameUi/UiButton.cs.meta create mode 100644 Assets/_DDD/_Scripts/GameUi/UiButtonTest.cs create mode 100644 Assets/_DDD/_Scripts/GameUi/UiButtonTest.cs.meta diff --git a/Assets/_DDD/_Scripts/GameUi/UiButton.cs b/Assets/_DDD/_Scripts/GameUi/UiButton.cs new file mode 100644 index 000000000..77a2799b1 --- /dev/null +++ b/Assets/_DDD/_Scripts/GameUi/UiButton.cs @@ -0,0 +1,390 @@ +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