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