diff --git a/Assets/_DDD/_Addressables/AI/Customer/Subtree/CustomerDefault.asset b/Assets/_DDD/_Addressables/AI/Customer/Subtree/CustomerDefault.asset index fa318032c..5b6b58011 100644 --- a/Assets/_DDD/_Addressables/AI/Customer/Subtree/CustomerDefault.asset +++ b/Assets/_DDD/_Addressables/AI/Customer/Subtree/CustomerDefault.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:948390052ce3eddb7f00b5d0f3286b789c8298f4f327a7843304e44f8a2e6513 -size 87077 +oid sha256:103256172ac6f8b232d1029ec42596c1b546dccf332b0b877b643f77f13d9b42 +size 86840 diff --git a/Assets/_DDD/_Scripts/AI.meta b/Assets/_DDD/_Scripts/AI.meta new file mode 100644 index 000000000..488015a6b --- /dev/null +++ b/Assets/_DDD/_Scripts/AI.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c31792c3491f44878a9a5e8ee59504cf +timeCreated: 1755770768 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/AI/Common.meta b/Assets/_DDD/_Scripts/AI/Common.meta new file mode 100644 index 000000000..8b9f60e70 --- /dev/null +++ b/Assets/_DDD/_Scripts/AI/Common.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 49c654aa3aa94cb9928e2d161cad789a +timeCreated: 1755770768 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/AI/Common/IAISharedBlackboard.cs b/Assets/_DDD/_Scripts/AI/Common/IAISharedBlackboard.cs new file mode 100644 index 000000000..154eebd47 --- /dev/null +++ b/Assets/_DDD/_Scripts/AI/Common/IAISharedBlackboard.cs @@ -0,0 +1,15 @@ +using UnityEngine; + +namespace DDD +{ + /// + /// 공용 AI 블랙보드 인터페이스. + /// - 다양한 캐릭터 AI에서 공통으로 참조하는 현재 인터랙션 타겟만 정의합니다. + /// - 필요 시 키-값 확장을 고려하되, 현재는 최소 요구만 충족합니다. + /// + public interface IAISharedBlackboard + { + void SetCurrentInteractionTarget(GameObject targetGameObject); + GameObject GetCurrentInteractionTarget(); + } +} diff --git a/Assets/_DDD/_Scripts/AI/Common/IAISharedBlackboard.cs.meta b/Assets/_DDD/_Scripts/AI/Common/IAISharedBlackboard.cs.meta new file mode 100644 index 000000000..271230278 --- /dev/null +++ b/Assets/_DDD/_Scripts/AI/Common/IAISharedBlackboard.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 62510fba6cb047419ca463dc523ae536 +timeCreated: 1755770768 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Common.meta b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Common.meta new file mode 100644 index 000000000..52bf270e4 --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Common.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: cfc8e456b2134c4a87b9fcd0d385cf1d +timeCreated: 1755767029 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Common/Actions.meta b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Common/Actions.meta new file mode 100644 index 000000000..b497afe8e --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Common/Actions.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ab824a41c52d4cca8cafb1fc96d5d8e7 +timeCreated: 1755769401 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Common/Actions/MoveToInteractionTarget.cs b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Common/Actions/MoveToInteractionTarget.cs new file mode 100644 index 000000000..953f6a5d4 --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Common/Actions/MoveToInteractionTarget.cs @@ -0,0 +1,165 @@ +using System; +using Opsive.BehaviorDesigner.Runtime.Tasks; +using Opsive.BehaviorDesigner.Runtime.Tasks.Actions; +using UnityEngine; + +namespace DDD +{ + /// + /// IAiMovement를 이용해 인터랙션 타겟(주로 RestaurantInteractionComponent가 붙은 오브젝트)으로 이동하는 액션. + /// - 타겟은 우선 블랙보드(IRestaurantCustomerBlackboard)에서 가져오고, 없으면 Interactor의 FocusedInteractable에서 가져옵니다. + /// - 타겟에 RestaurantInteractionComponent가 있다면 가장 가까운 InteractionPoint를 목적지로 사용합니다. + /// + public class MoveToInteractionTarget : Opsive.BehaviorDesigner.Runtime.Tasks.Actions.Action + { + [Header("Target")] + [Tooltip("타겟에 RestaurantInteractionComponent가 있을 때 InteractionPoints를 사용해 가장 가까운 지점으로 이동합니다.")] + [SerializeField] private bool _useInteractionPoints = true; + [Tooltip("타겟이 없을 때 즉시 실패할지 여부")] + [SerializeField] private bool _failIfNoTarget = true; + + [Header("Movement")] + [Tooltip("목적지에 도달했다고 간주할 거리")] + [SerializeField] private float _stoppingDistance = 0.5f; + [Tooltip("이동 중 목적지를 재계산하는 주기(초). 0 이하면 재계산하지 않음")] + [SerializeField] private float _repathInterval = 0.5f; + + private IAiMovement _movement; + private float _repathTimer; + private Vector3 _currentDestination; + private bool _started; + private GameObject _resolvedTarget; + + public override void OnStart() + { + _movement = gameObject.GetComponentInParent(); + _repathTimer = 0f; + _started = false; + _resolvedTarget = ResolveTargetFromContext(); + } + + public override TaskStatus OnUpdate() + { + if (_movement == null) + { + return TaskStatus.Failure; + } + + // 매 프레임 타겟이 갱신될 수 있으므로 재해결 (필요 시 비용 줄일 수 있음) + _resolvedTarget = _resolvedTarget ?? ResolveTargetFromContext(); + + if (_resolvedTarget == null) + { + if (_failIfNoTarget) return TaskStatus.Failure; + return TaskStatus.Success; + } + + // 타겟이 파괴되었는지 확인 + if (_resolvedTarget == null || (_resolvedTarget as UnityEngine.Object) == null) + { + return TaskStatus.Failure; + } + + // 목적지 계산 및 이동 시작/유지 + _repathTimer -= Time.deltaTime; + if (!_started || _repathInterval > 0f && _repathTimer <= 0f) + { + _currentDestination = GetBestDestination(_resolvedTarget); + _repathTimer = _repathInterval; + + if (!_started) + { + if (!_movement.TryMoveToPosition(_currentDestination)) + { + return TaskStatus.Failure; + } + _movement.EnableMove(); + _movement.PlayMove(); + _started = true; + } + else + { + // 이동 중 목적지 갱신 시도 + _movement.TryMoveToPosition(_currentDestination); + } + } + + // 도달 판정 + var sqrDist = (GetAgentPosition() - _currentDestination).sqrMagnitude; + if (sqrDist <= _stoppingDistance * _stoppingDistance || _movement.HasReachedDestination()) + { + _movement.StopMove(); + _movement.DisableMove(); + return TaskStatus.Success; + } + + return TaskStatus.Running; + } + + public override void OnEnd() + { + // 액션 종료 시 이동 중지(안전장치) + if (_movement != null) + { + _movement.StopMove(); + _movement.DisableMove(); + } + _resolvedTarget = null; + } + + private GameObject ResolveTargetFromContext() + { + // 1) 공용 블랙보드에서 가져오기 + var sharedBlackboard = gameObject.GetComponentInParent(); + var bbTarget = sharedBlackboard?.GetCurrentInteractionTarget(); + if (bbTarget != null) return bbTarget; + + // 2) Interactor의 포커싱 대상에서 가져오기 + var interactor = gameObject.GetComponentInParent(); + var focused = interactor?.GetFocusedInteractable(); + if (focused != null) + { + return focused.GetInteractableGameObject(); + } + + return null; + } + + private Vector3 GetAgentPosition() + { + return _movement != null ? _movement.CurrentPosition : transform.position; + } + + private Vector3 GetBestDestination(GameObject target) + { + if (!_useInteractionPoints) + { + return target.transform.position; + } + + if (target.TryGetComponent(out var ric)) + { + var points = ric.GetInteractionPoints(); + if (points != null && points.Length > 0) + { + var from = GetAgentPosition(); + float bestSqr = float.MaxValue; + Vector3 best = target.transform.position; + for (int i = 0; i < points.Length; i++) + { + var p = points[i]; + var sqr = (p - from).sqrMagnitude; + if (sqr < bestSqr) + { + bestSqr = sqr; + best = p; + } + } + return best; + } + } + + return target.transform.position; + } + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Common/Actions/MoveToInteractionTarget.cs.meta b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Common/Actions/MoveToInteractionTarget.cs.meta new file mode 100644 index 000000000..92e078ee6 --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Common/Actions/MoveToInteractionTarget.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d97cd08353334cb698807d4b526d01b6 +timeCreated: 1755769413 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer.meta b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer.meta new file mode 100644 index 000000000..20e141632 --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6f1cc55df1da4604a2a7f445527710de +timeCreated: 1755765247 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer/Actions.meta b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer/Actions.meta new file mode 100644 index 000000000..471d824db --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer/Actions.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6777cf4d7cf4408fafe4bc9097c32b01 +timeCreated: 1755767888 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer/Actions/StartRestaurantOrder.cs b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer/Actions/StartRestaurantOrder.cs new file mode 100644 index 000000000..004f9111b --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer/Actions/StartRestaurantOrder.cs @@ -0,0 +1,60 @@ +using Opsive.BehaviorDesigner.Runtime.Tasks; +using Opsive.BehaviorDesigner.Runtime.Tasks.Actions; +using UnityEngine; + +namespace DDD +{ + public class StartRestaurantOrder : Action + { + [Tooltip("상호작용할 RestaurantOrderType")] + [SerializeField] private RestaurantOrderType _targetOrderType = RestaurantOrderType.Wait; + [Tooltip("실제 상호작용 가능 여부를 확인하고 수행합니다")] + [SerializeField] private bool _requireCanInteract = true; + [Tooltip("성공 시 블랙보드에 현재 인터랙션 대상을 등록합니다")] + [SerializeField] private bool _registerOnBlackboard = true; + + public override TaskStatus OnUpdate() + { + // TODO : 아래 타겟 찾기가 RestaurantOrderAvailable과 동일해야 함, 동일하면 중복될 필요 없으니 스태틱 유틸 함수정도로 만들어서 공유하기. + // 레스토랑 주문 인터랙션 후보를 가져옴 + TaskStatus targetSearchSuccess = RestaurantOrderAvailable.FindAvailableOrderInteractable(_requireCanInteract, _targetOrderType, out var + outInteractable); + if (targetSearchSuccess == TaskStatus.Failure) + { + return TaskStatus.Failure; + } + + // TODO : 아래 상호작용 수행 로직이 우리 프로젝트의 권장하는 방식이 아님. 플레이어가 오브젝트에 인터랙션하는 것과 비슷한 흐름으로 NPC가 오브젝트에 인터랙션하게 만들 것. + // 상호작용 수행: 액션이 붙은 에이전트를 Interactor로 사용 + var interactor = gameObject.GetComponentInParent(); + if (interactor == null) + { + return TaskStatus.Failure; + } + + var interacted = outInteractable.OnInteracted(interactor); + if (!interacted) + { + return TaskStatus.Failure; + } + + if (_registerOnBlackboard) + { + // 공용 블랙보드 우선 + var shared = gameObject.GetComponentInParent(); + if (shared != null) + { + shared.SetCurrentInteractionTarget(outInteractable.gameObject); + } + else + { + // 하위 호환: 고객 전용 블랙보드 지원 + var customerBb = gameObject.GetComponentInParent(); + customerBb?.SetCurrentInteractionTarget(outInteractable.gameObject); + } + } + + return TaskStatus.Success; + } + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer/Actions/StartRestaurantOrder.cs.meta b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer/Actions/StartRestaurantOrder.cs.meta new file mode 100644 index 000000000..b24f479a5 --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer/Actions/StartRestaurantOrder.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 84b6c26acd1e41afa6b07ed4c6caf860 +timeCreated: 1755767930 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer/Conditionals.meta b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer/Conditionals.meta new file mode 100644 index 000000000..0df5af835 --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer/Conditionals.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c4f20081a3974c08b60b9efdbe1568a7 +timeCreated: 1755765256 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer/Conditionals/RestaurantOrderAvailable.cs b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer/Conditionals/RestaurantOrderAvailable.cs new file mode 100644 index 000000000..d6f49b45d --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer/Conditionals/RestaurantOrderAvailable.cs @@ -0,0 +1,73 @@ +using Opsive.BehaviorDesigner.Runtime.Tasks; +using Opsive.BehaviorDesigner.Runtime.Tasks.Conditionals; +using UnityEngine; +using DDD; + +namespace DDD +{ + public class RestaurantOrderAvailable : Conditional + { + [Tooltip("검사할 RestaurantOrderType")] + [SerializeField] private RestaurantOrderType _targetOrderType = RestaurantOrderType.Wait; + [Tooltip("인터랙션 가능 여부까지 확인할지 선택")] + [SerializeField] private bool _checkCanInteract = true; + + public RestaurantOrderType TargetOrderType + { + get => _targetOrderType; + set => _targetOrderType = value; + } + + public bool CheckCanInteract + { + get => _checkCanInteract; + set => _checkCanInteract = value; + } + + public override TaskStatus OnUpdate() + { + TaskStatus targetSearchSuccess = FindAvailableOrderInteractable(_checkCanInteract, _targetOrderType, out var + outInteractable); + return targetSearchSuccess; + + } + + public static TaskStatus FindAvailableOrderInteractable(bool checkCanInteract, RestaurantOrderType targetOrderType, out RestaurantInteractionComponent outInteractable) + { + outInteractable = null; + + var environmentState = RestaurantState.Instance?.EnvironmentState; + if (environmentState == null) + { + return TaskStatus.Failure; + } + + var interactables = environmentState.GetInteractablesByType(InteractionType.RestaurantOrder); + + foreach (var interactable in interactables) + { + // 서브시스템에서 RestaurantOrderType을 가져와 비교 + outInteractable = interactable as RestaurantInteractionComponent; + if (outInteractable == null) continue; + if (!outInteractable.TryGetSubsystemObject(out var subsystem)) continue; + + if (subsystem.GetInteractionSubsystemType() == targetOrderType) + { + // CheckCanInteract이 false면 타입만 맞으면 성공 + if (!checkCanInteract) + { + return TaskStatus.Success; + } + + // CheckCanInteract이 true면 실제 인터랙션 가능 여부까지 확인 + if (interactable.CanInteract()) + { + return TaskStatus.Success; + } + } + } + + return TaskStatus.Failure; + } + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer/Conditionals/RestaurantOrderAvailable.cs.meta b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer/Conditionals/RestaurantOrderAvailable.cs.meta new file mode 100644 index 000000000..04186450b --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer/Conditionals/RestaurantOrderAvailable.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: bef354fd7cae4273b4cb4e7ff9e270f5 +timeCreated: 1755765519 \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantCharacter/AI/RestaurantCustomerAiComponent.cs b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer/RestaurantCustomerAiComponent.cs similarity index 100% rename from Assets/_DDD/_Scripts/RestaurantCharacter/AI/RestaurantCustomerAiComponent.cs rename to Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer/RestaurantCustomerAiComponent.cs diff --git a/Assets/_DDD/_Scripts/RestaurantCharacter/AI/RestaurantCustomerAiComponent.cs.meta b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer/RestaurantCustomerAiComponent.cs.meta similarity index 100% rename from Assets/_DDD/_Scripts/RestaurantCharacter/AI/RestaurantCustomerAiComponent.cs.meta rename to Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer/RestaurantCustomerAiComponent.cs.meta diff --git a/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer/RestaurantCustomerBlackboardComponent.cs b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer/RestaurantCustomerBlackboardComponent.cs new file mode 100644 index 000000000..98112000e --- /dev/null +++ b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer/RestaurantCustomerBlackboardComponent.cs @@ -0,0 +1,39 @@ +using Opsive.BehaviorDesigner.Runtime; +using UnityEngine; + +namespace DDD +{ + public class RestaurantCustomerBlackboardComponent : MonoBehaviour, IRestaurantCustomerBlackboard, IAISharedBlackboard + { + private Subtree _subtree; + private GameObject _currentInteractionTarget; + + public void InitializeWithBehaviorTree(Subtree subtree) + { + _subtree = subtree; + if (_subtree != null) + { + _subtree.SetVariableValue(nameof(RestaurantCustomerBlackboardKey.SelfGameObject), gameObject); + } + } + + public void SetCustomerData(CustomerData inCustomerData) + { + if (_subtree == null) return; + _subtree.SetVariableValue(nameof(RestaurantCustomerBlackboardKey.CustomerData), inCustomerData); + } + + public void SetCurrentInteractionTarget(GameObject targetGameObject) + { + _currentInteractionTarget = targetGameObject; + if (_subtree == null) return; + _subtree.SetVariableValue(nameof(RestaurantCustomerBlackboardKey.CurrentInteractionTarget), targetGameObject); + } + + public GameObject GetCurrentInteractionTarget() + { + // 캐시 우선 반환. 필요 시 Subtree에서 직접 조회하도록 확장 가능. + return _currentInteractionTarget; + } + } +} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantCharacter/AI/RestaurantCustomerBlackboardComponent.cs.meta b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer/RestaurantCustomerBlackboardComponent.cs.meta similarity index 100% rename from Assets/_DDD/_Scripts/RestaurantCharacter/AI/RestaurantCustomerBlackboardComponent.cs.meta rename to Assets/_DDD/_Scripts/RestaurantCharacter/AI/Customer/RestaurantCustomerBlackboardComponent.cs.meta diff --git a/Assets/_DDD/_Scripts/RestaurantCharacter/AI/RestaurantCustomerBlackboardComponent.cs b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/RestaurantCustomerBlackboardComponent.cs deleted file mode 100644 index f042b9227..000000000 --- a/Assets/_DDD/_Scripts/RestaurantCharacter/AI/RestaurantCustomerBlackboardComponent.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Opsive.BehaviorDesigner.Runtime; -using UnityEngine; - -namespace DDD -{ - public class RestaurantCustomerBlackboardComponent : MonoBehaviour, IRestaurantCustomerBlackboard - { - private Subtree _subtree; - - public void InitializeWithBehaviorTree(Subtree subtree) - { - _subtree = subtree; - _subtree.SetVariableValue(nameof(RestaurantCustomerBlackboardKey.SelfGameObject), gameObject); - } - - public void SetCustomerData(CustomerData inCustomerData) - { - _subtree.SetVariableValue(nameof(RestaurantCustomerBlackboardKey.CustomerData), inCustomerData);; - } - } -} \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantCharacter/Interfaces/IRestaurantCustomerBlackboard.cs b/Assets/_DDD/_Scripts/RestaurantCharacter/Interfaces/IRestaurantCustomerBlackboard.cs index bc261391b..51859cddf 100644 --- a/Assets/_DDD/_Scripts/RestaurantCharacter/Interfaces/IRestaurantCustomerBlackboard.cs +++ b/Assets/_DDD/_Scripts/RestaurantCharacter/Interfaces/IRestaurantCustomerBlackboard.cs @@ -1,13 +1,18 @@ +using UnityEngine; + namespace DDD { public enum RestaurantCustomerBlackboardKey { SelfGameObject, CustomerData, + CurrentInteractionTarget, } public interface IRestaurantCustomerBlackboard { void SetCustomerData(CustomerData inCustomerData); + void SetCurrentInteractionTarget(GameObject targetGameObject); + GameObject GetCurrentInteractionTarget(); } } \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteractionSubsystem.cs b/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteractionSubsystem.cs index 26b80cdb9..90bca043f 100644 --- a/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteractionSubsystem.cs +++ b/Assets/_DDD/_Scripts/RestaurantEnvironment/Interactions/RestaurantOrderInteractionSubsystem.cs @@ -3,7 +3,6 @@ namespace DDD { - [Flags] public enum RestaurantOrderType : uint { Wait = 0u, @@ -35,11 +34,9 @@ public bool CanInteract() public bool OnInteracted(IInteractor interactor, ScriptableObject payloadSo = null) { - if (GetInteractionSubsystemType() == RestaurantOrderType.Wait) - { - // DO WAIT CUSTOMER - } - + // 간단한 상태 전이: 현재 상태에서 다음 상태로 이동 + var prev = currentRestaurantOrderType; + currentRestaurantOrderType = GetNextState(prev); return true; } @@ -52,5 +49,19 @@ public RestaurantOrderType GetInteractionSubsystemType() { return currentRestaurantOrderType; } + + private RestaurantOrderType GetNextState(RestaurantOrderType state) + { + switch (state) + { + case RestaurantOrderType.Wait: return RestaurantOrderType.Reserved; + case RestaurantOrderType.Reserved: return RestaurantOrderType.Order; + case RestaurantOrderType.Order: return RestaurantOrderType.Serve; + case RestaurantOrderType.Serve: return RestaurantOrderType.Busy; + case RestaurantOrderType.Busy: return RestaurantOrderType.Dirty; + case RestaurantOrderType.Dirty: return RestaurantOrderType.Wait; + default: return RestaurantOrderType.Wait; + } + } } } \ No newline at end of file diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionComponent.cs b/Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionComponent.cs index 2cf9c4102..7921054b4 100644 --- a/Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionComponent.cs +++ b/Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionComponent.cs @@ -30,14 +30,31 @@ public class RestaurantInteractionComponent : MonoBehaviour, IInteractable, IInt private Dictionary _subsystems = new(); - private void Start() + private void OnEnable() { + // Register this interactable to environment state + var environmentState = RestaurantState.Instance?.EnvironmentState; + environmentState?.RegisterInteractable(this); + if (autoInitialize) { InitializeInteraction(_interactionType); } } + private void OnDisable() + { + var environmentState = RestaurantState.Instance?.EnvironmentState; + environmentState?.UnregisterInteractable(this); + } + + private void Start() + { + // 보수적으로 Start에서도 등록 시도 (OnEnable 시점에 EnvironmentState가 없었을 경우 대비) + var environmentState = RestaurantState.Instance?.EnvironmentState; + environmentState?.RegisterInteractable(this); + } + private static IEnumerable GetAllInteractionTypes() { return System.Enum.GetValues(typeof(InteractionType)) diff --git a/Assets/_DDD/_Scripts/RestaurantState/FlowStates/RestaurantEnvironmentState.cs b/Assets/_DDD/_Scripts/RestaurantState/FlowStates/RestaurantEnvironmentState.cs index 75313179a..afb94720d 100644 --- a/Assets/_DDD/_Scripts/RestaurantState/FlowStates/RestaurantEnvironmentState.cs +++ b/Assets/_DDD/_Scripts/RestaurantState/FlowStates/RestaurantEnvironmentState.cs @@ -21,5 +21,54 @@ public class RestaurantEnvironmentState : ScriptableObject { public List Props = new List(); public List Objects = new List(); + + // 인터랙션 가능한 객체(IInteractable)를 관리하기 위한 리스트 (런타임 전용) + private readonly List _registeredInteractables = new List(); + + /// + /// 인터랙션 가능한 객체를 등록합니다 + /// + public void RegisterInteractable(IInteractable interactable) + { + if (interactable == null) return; + if (_registeredInteractables.Contains(interactable)) return; + _registeredInteractables.Add(interactable); + } + + /// + /// 인터랙션 가능한 객체를 해제합니다 + /// + public void UnregisterInteractable(IInteractable interactable) + { + if (interactable == null) return; + _registeredInteractables.Remove(interactable); + } + + /// + /// 특정 InteractionType에 해당하는 인터랙션 객체들을 반환합니다 + /// + public List GetInteractablesByType(InteractionType interactionType) + { + var result = new List(); + // null 또는 Destroyed 오브젝트 정리 + _registeredInteractables.RemoveAll(item => item == null || (item as UnityEngine.Object) == null); + foreach (var interactable in _registeredInteractables) + { + if (interactable.GetInteractionType() == interactionType) + { + result.Add(interactable); + } + } + return result; + } + + /// + /// 모든 등록된 인터랙션 객체들을 반환합니다 + /// + public List GetAllInteractables() + { + _registeredInteractables.RemoveAll(item => item == null || (item as UnityEngine.Object) == null); + return new List(_registeredInteractables); + } } } \ No newline at end of file