레스토랑 식사 인터랙션 서브 시스템 및 솔버 초기 구현 추가

This commit is contained in:
김산 2025-08-26 17:37:17 +09:00
parent 24239dc138
commit 1663ff055c
29 changed files with 277 additions and 39 deletions

Binary file not shown.

Binary file not shown.

View File

@ -10,6 +10,10 @@ PrefabInstance:
m_Modifications:
- target: {fileID: 2686192822530022837, guid: ceeea618d8ee23642a0e56b3f963448c, type: 3}
propertyPath: m_IsTrigger
value: 1
objectReference: {fileID: 0}
- target: {fileID: 3540956906288785900, guid: ceeea618d8ee23642a0e56b3f963448c, type: 3}
propertyPath: m_Layer
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3854744934792897056, guid: ceeea618d8ee23642a0e56b3f963448c, type: 3}
@ -1172,6 +1176,10 @@ PrefabInstance:
propertyPath: m_Name
value: CustomerNpc
objectReference: {fileID: 0}
- target: {fileID: 7462519206451630147, guid: ceeea618d8ee23642a0e56b3f963448c, type: 3}
propertyPath: m_Layer
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7545136660434259176, guid: ceeea618d8ee23642a0e56b3f963448c, type: 3}
propertyPath: m_Constraints
value: 112
@ -1180,6 +1188,10 @@ PrefabInstance:
propertyPath: m_IsKinematic
value: 0
objectReference: {fileID: 0}
- target: {fileID: 8155105186346135386, guid: ceeea618d8ee23642a0e56b3f963448c, type: 3}
propertyPath: m_Layer
value: 0
objectReference: {fileID: 0}
- target: {fileID: 8165702938223525558, guid: ceeea618d8ee23642a0e56b3f963448c, type: 3}
propertyPath: graphMask.value
value: 2

View File

@ -426,7 +426,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 81e01dd8c1cc3404d805400eba1bb4ae, type: 3}
m_Name:
m_EditorClassIdentifier:
_availableInteractions: 1
_availableInteractions: 5
_nearColliders:
- {fileID: 0}
- {fileID: 0}

View File

@ -474,7 +474,8 @@ PrefabInstance:
propertyPath: m_Name
value: Prop_CustomerTable_002
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedComponents:
- {fileID: 551358949302262764, guid: 0b1ba2f28535d5147bc0ddf354d0712f, type: 3}
m_RemovedGameObjects: []
m_AddedGameObjects: []
m_AddedComponents: []

View File

@ -12,6 +12,7 @@ public interface IInteractionSubsystemObject
public interface IInteractionSubsystemObject<T> : IInteractionSubsystemObject where T : Enum
{
T GetInteractionSubsystemType();
void SetInteractionSubsystemType(T inValue);
}
public interface IInteractionSubsystemSolver

View File

@ -14,13 +14,13 @@ namespace DDD
public class TimeLimiter : ILogicNode, IParentNode, ITaskComponentData, IDecorator, ISavableTask
{
[Tooltip("The index of the node.")]
[SerializeField] ushort _Index;
[SerializeField, HideInInspector] ushort _Index;
[Tooltip("The parent index of the node. ushort.MaxValue indicates no parent.")]
[SerializeField] ushort _ParentIndex;
[SerializeField, HideInInspector] ushort _ParentIndex;
[Tooltip("The sibling index of the node. ushort.MaxValue indicates no sibling.")]
[SerializeField] ushort _SiblingIndex;
[SerializeField, HideInInspector] ushort _SiblingIndex;
[Tooltip("최대 실행 시간(초)")]
[SerializeField] float _timeLimit = 30.0f;
@ -28,10 +28,6 @@ public class TimeLimiter : ILogicNode, IParentNode, ITaskComponentData, IDecorat
[Tooltip("시간 초과 시 반환할 상태")]
[SerializeField] private TaskStatus _timeoutStatus = TaskStatus.Failure;
[Header("Debug")]
[Tooltip("로그 출력")]
[SerializeField] private bool _enableDebug = false;
private ushort _ComponentIndex;
public ushort Index

View File

@ -12,6 +12,16 @@ public class StartRestaurantOrder : Action
[SerializeField] private bool _requireCanInteract = true;
[Tooltip("성공 시 블랙보드에 현재 인터랙션 대상을 등록합니다")]
[SerializeField] private bool _registerOnBlackboard = true;
private IInteractor _interactor;
private bool _isGetInteractor;
public override void OnStart()
{
_isGetInteractor = gameObject.TryGetComponent(out _interactor);
if (!_isGetInteractor)
Debug.LogError($"[{GetType().Name}] IInteractor를 찾을 수 없습니다: {gameObject.name}");
}
public override TaskStatus OnUpdate()
{
@ -26,13 +36,12 @@ public override TaskStatus OnUpdate()
// TODO : 아래 상호작용 수행 로직이 우리 프로젝트의 권장하는 방식이 아님. 플레이어가 오브젝트에 인터랙션하는 것과 비슷한 흐름으로 NPC가 오브젝트에 인터랙션하게 만들 것.
// 상호작용 수행: 액션이 붙은 에이전트를 Interactor로 사용
var isGetInteractor = gameObject.TryGetComponent<IInteractor>(out var interactor);
if (!isGetInteractor || !interactor.CanInteractTo(outInteractable))
if (!_isGetInteractor || !_interactor.CanInteractTo(outInteractable))
{
return TaskStatus.Failure;
}
var interacted = outInteractable.OnInteracted(interactor);
var interacted = outInteractable.OnInteracted(_interactor);
if (!interacted)
{
return TaskStatus.Failure;
@ -41,7 +50,7 @@ public override TaskStatus OnUpdate()
if (_registerOnBlackboard)
{
// 공용 블랙보드 우선
var shared = gameObject.GetComponentInParent<IAISharedBlackboard>();
var shared = gameObject.GetComponentInChildren<IAISharedBlackboard>();
if (shared != null)
{
shared.SetCurrentInteractionTarget(outInteractable.gameObject);

View File

@ -0,0 +1,80 @@
using Opsive.BehaviorDesigner.Runtime.Tasks;
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
using UnityEngine;
namespace DDD
{
//범용적으로 사용할 수 있을 것 같음
//차후 제네릭으로 변경 가능성 있음
public class WaitForPlayerInteraction : Action
{
[Tooltip("기다릴 상호작용 타입")]
[SerializeField] private RestaurantMealType _targetOrderType = RestaurantMealType.WaitForOrder;
private IInteractionSubsystemObject<RestaurantMealType> _interactionSubsystem;
private bool _isGetInteractionSubsystem;
public override void OnStart()
{
GameObject interactionTarget = null;
var shared = gameObject.GetComponentInChildren<IAISharedBlackboard>();
if (shared != null)
{
interactionTarget = shared.GetCurrentInteractionTarget();
}
else
{
// 하위 호환: 고객 전용 블랙보드 지원
var customerBb = gameObject.GetComponentInParent<IRestaurantCustomerBlackboard>();
interactionTarget = customerBb?.GetCurrentInteractionTarget();
}
if (interactionTarget == null)
{
Debug.LogError($"[{GetType().Name}] interactionTarget을 찾을 수 없습니다: {gameObject.name}");
return;
}
if (!interactionTarget.TryGetComponent<RestaurantInteractionComponent>(out var interactionComponent))
Debug.LogError($"[{interactionTarget.name}] {nameof(interactionComponent)}를 찾을 수 없습니다: {gameObject.name}");
if (interactionComponent is IInteractionSubsystemOwner subsystemOwner)
{
if (!subsystemOwner.TryGetSubsystemObject<RestaurantMealType>(out var subsystem))
{
Debug.LogError($"[{GetType().Name}] {nameof(_targetOrderType)}의 Subsystem을 찾을 수 없습니다: {gameObject.name}");
_isGetInteractionSubsystem = false;
return;
}
_isGetInteractionSubsystem = true;
subsystem.SetInteractionSubsystemType(_targetOrderType);
if (!gameObject.TryGetComponent<IInteractor>(out var interactor))
{
Debug.LogError($"[{GetType().Name}] IInteractor를 찾을 수 없습니다: {gameObject.name}");
return;
}
interactor.CanInteractTo(interactionComponent);
_interactionSubsystem = subsystem;
}
}
public override TaskStatus OnUpdate()
{
if (!_isGetInteractionSubsystem) return TaskStatus.Failure;
TaskStatus result = CheckToSubsystemStatus();
if (result == TaskStatus.Success) Debug.Log($"[{GetType().Name}] Success");
return result;
}
private TaskStatus CheckToSubsystemStatus()
{
return _interactionSubsystem.GetInteractionSubsystemType() == _targetOrderType
? TaskStatus.Running
: TaskStatus.Success;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4bb97122045148169906d2f7b04a712e
timeCreated: 1756171540

View File

@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using Opsive.BehaviorDesigner.Runtime.Tasks;
using Opsive.BehaviorDesigner.Runtime.Tasks.Conditionals;
using UnityEngine;
@ -32,7 +34,7 @@ public override TaskStatus OnUpdate()
}
public static TaskStatus FindAvailableOrderInteractable(bool checkCanInteract, RestaurantOrderType targetOrderType, out RestaurantInteractionComponent outInteractable)
public static TaskStatus FindAvailableOrderInteractable<T>(bool checkCanInteract, T targetOrderType, out RestaurantInteractionComponent outInteractable) where T : Enum
{
outInteractable = null;
@ -49,9 +51,10 @@ public static TaskStatus FindAvailableOrderInteractable(bool checkCanInteract, R
// 서브시스템에서 RestaurantOrderType을 가져와 비교
outInteractable = interactable as RestaurantInteractionComponent;
if (outInteractable == null) continue;
if (!outInteractable.TryGetSubsystemObject<RestaurantOrderType>(out var subsystem)) continue;
if (!outInteractable.TryGetSubsystemObject<T>(out var subsystem)) continue;
if (subsystem.GetInteractionSubsystemType() == targetOrderType)
if (EqualityComparer<T>.Default.Equals(subsystem.GetInteractionSubsystemType(), targetOrderType)
)
{
// CheckCanInteract이 false면 타입만 맞으면 성공
if (!checkCanInteract)

View File

@ -18,6 +18,11 @@ public RestaurantManagementType GetInteractionSubsystemType()
return _managementType;
}
public void SetInteractionSubsystemType(RestaurantManagementType inValue)
{
_managementType = inValue;
}
public void InitializeSubsystem()
{

View File

@ -0,0 +1,57 @@
using System;
using UnityEngine;
namespace DDD
{
public enum RestaurantMealType : uint
{
None = 0u,
WaitForOrder = 1u,
WaitForServe = 1u << 1
}
public class RestaurantMealInteractionSubsystem : MonoBehaviour, IInteractionSubsystemObject<RestaurantMealType>
{
private RestaurantMealType _currentRestaurantMealType;
private void Awake()
{
_currentRestaurantMealType = RestaurantMealType.None;
}
public RestaurantMealType GetInteractionSubsystemType()
{
return _currentRestaurantMealType;
}
public void SetInteractionSubsystemType(RestaurantMealType inValue)
{
_currentRestaurantMealType = inValue;
}
public void InitializeSubsystem()
{
_currentRestaurantMealType = RestaurantMealType.None;
}
public bool CanInteract()
{
return _currentRestaurantMealType != RestaurantMealType.None;
}
public bool OnInteracted(IInteractor interactor, ScriptableObject payloadSo = null)
{
var prev = _currentRestaurantMealType;
_currentRestaurantMealType = GetNextState(prev);
return true;
}
private RestaurantMealType GetNextState(RestaurantMealType state)
{
switch (state)
{
case RestaurantMealType.None : return RestaurantMealType.WaitForOrder;
case RestaurantMealType.WaitForOrder : return RestaurantMealType.WaitForServe;
case RestaurantMealType.WaitForServe : return RestaurantMealType.None;
default: return RestaurantMealType.None;
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 31d5c600061a4f05b19824e068e0c2af
timeCreated: 1756176676

View File

@ -42,7 +42,7 @@ public bool OnInteracted(IInteractor interactor, ScriptableObject payloadSo = nu
public void InitializeSubsystem()
{
currentRestaurantOrderType = orderType;
}
public RestaurantOrderType GetInteractionSubsystemType()
@ -50,6 +50,11 @@ public RestaurantOrderType GetInteractionSubsystemType()
return currentRestaurantOrderType;
}
public void SetInteractionSubsystemType(RestaurantOrderType inValue)
{
currentRestaurantOrderType = inValue;
}
private RestaurantOrderType GetNextState(RestaurantOrderType state)
{
switch (state)

View File

@ -12,7 +12,8 @@ public static class RestaurantInteractionSubsystems
public static Dictionary<InteractionType, Type> TypeToSubsystem = new()
{
{InteractionType.RestaurantOrder, typeof(RestaurantOrderInteractionSubsystem)},
{InteractionType.RestaurantManagement, typeof(RestaurantManagementInteractionSubsystem)}
{InteractionType.RestaurantManagement, typeof(RestaurantManagementInteractionSubsystem)},
{InteractionType.RestaurantMeal, typeof(RestaurantMealInteractionSubsystem)}
};
}

View File

@ -14,7 +14,8 @@ public static class RestaurantInteractionEventSolvers
public static Dictionary<InteractionType, Type> TypeToSolver = new()
{
{InteractionType.RestaurantManagement, typeof(RestaurantManagementSolver)},
{InteractionType.RestaurantOrder, typeof(RestaurantOrderSolver)}
{InteractionType.RestaurantOrder, typeof(RestaurantOrderSolver)},
{InteractionType.RestaurantMeal, typeof(RestaurantMealSolver)}
};
}

View File

@ -5,19 +5,17 @@
namespace DDD
{
public static class RestaurantManagementSolvers
public class RestaurantManagementSolver : RestaurantSubsystemSolver<RestaurantManagementType>
{
public static Dictionary<RestaurantManagementType, Type> TypeToManagementSolver = new()
private Dictionary<RestaurantManagementType, Type> _typeToManagementSolver = new()
{
{ RestaurantManagementType.OpenRestaurantMenu, typeof(RestaurantManagementSolver_Menu) },
{ RestaurantManagementType.StartRestaurant, typeof(RestaurantManagementSolver_Start) }
};
}
public class RestaurantManagementSolver : RestaurantSubsystemSolver<RestaurantManagementType>
{
protected override Dictionary<RestaurantManagementType, Type> GetSubsystemSolverTypeMappings()
{
return RestaurantManagementSolvers.TypeToManagementSolver;
return _typeToManagementSolver;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 601164c0231c43fca9349170e1e0ccec
timeCreated: 1756176395

View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
namespace DDD
{
public class RestaurantMealSolver : RestaurantSubsystemSolver<RestaurantMealType>
{
private Dictionary<RestaurantMealType, Type> _typeToMealSolver = new()
{
{ RestaurantMealType.WaitForOrder, typeof(RestaurantMealSolver_WaitForOrder) },
{ RestaurantMealType.WaitForServe, typeof(RestaurantMealSolver_WaitForServe) }
};
protected override Dictionary<RestaurantMealType, Type> GetSubsystemSolverTypeMappings()
{
return _typeToMealSolver;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 391c551614be4f21a2e700f44569e92a
timeCreated: 1756176491

View File

@ -0,0 +1,19 @@
using UnityEngine;
namespace DDD
{
public class RestaurantMealSolver_WaitForOrder : MonoBehaviour, IInteractionSubsystemSolver<RestaurantMealType>
{
public bool ExecuteInteractionSubsystem(IInteractor interactor, IInteractable interactable, ScriptableObject payloadSo = null)
{
return true;
}
public bool CanExecuteInteractionSubsystem(IInteractor interactor = null, IInteractable interactable = null,
ScriptableObject payloadSo = null)
{
return true;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cff2611181194e4a92576bdbcead4fad
timeCreated: 1756181225

View File

@ -0,0 +1,18 @@
using UnityEngine;
namespace DDD
{
public class RestaurantMealSolver_WaitForServe : MonoBehaviour, IInteractionSubsystemSolver<RestaurantMealType>
{
public bool ExecuteInteractionSubsystem(IInteractor interactor, IInteractable interactable, ScriptableObject payloadSo = null)
{
return true;
}
public bool CanExecuteInteractionSubsystem(IInteractor interactor = null, IInteractable interactable = null,
ScriptableObject payloadSo = null)
{
return true;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e9292616267b4299a3d2e0d29c84f69b
timeCreated: 1756181667

View File

@ -5,9 +5,9 @@
namespace DDD
{
public static class RestaurantOrderSolvers
public class RestaurantOrderSolver : RestaurantSubsystemSolver<RestaurantOrderType>
{
public static Dictionary<RestaurantOrderType, Type> TypeToOrderSolver = new()
private Dictionary<RestaurantOrderType, Type> _typeToOrderSolver = new()
{
{ RestaurantOrderType.Wait, typeof(RestaurantOrderSolver_Wait) },
{ RestaurantOrderType.Reserved, typeof(RestaurantOrderSolver_Reserved) },
@ -16,13 +16,9 @@ public static class RestaurantOrderSolvers
{ RestaurantOrderType.Busy, typeof(RestaurantOrderSolver_Busy) },
{ RestaurantOrderType.Dirty, typeof(RestaurantOrderSolver_Dirty) }
};
}
public class RestaurantOrderSolver : RestaurantSubsystemSolver<RestaurantOrderType>
{
protected override Dictionary<RestaurantOrderType, Type> GetSubsystemSolverTypeMappings()
{
return RestaurantOrderSolvers.TypeToOrderSolver;
return _typeToOrderSolver;
}
}
}

BIN
ProjectSettings/TagManager.asset (Stored with Git LFS)

Binary file not shown.