This commit is contained in:
Jeonghyeon Ha 2025-09-01 17:55:30 +09:00
commit f174fabd34
42 changed files with 379 additions and 94 deletions

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 71fd4c1fc79e83a4b9af326332323c28
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -1,3 +1,4 @@
using System;
using UnityEngine; using UnityEngine;
namespace DDD namespace DDD
@ -7,11 +8,11 @@ namespace DDD
/// - 다양한 캐릭터 AI에서 공통으로 참조하는 현재 인터랙션 타겟만 정의합니다. /// - 다양한 캐릭터 AI에서 공통으로 참조하는 현재 인터랙션 타겟만 정의합니다.
/// - 필요 시 키-값 확장을 고려하되, 현재는 최소 요구만 충족합니다. /// - 필요 시 키-값 확장을 고려하되, 현재는 최소 요구만 충족합니다.
/// </summary> /// </summary>
public interface IAISharedBlackboard public interface IAISharedBlackboard<T> where T : Enum
{ {
void SetBlackboardValue<T>(string key, T inValue); void SetBlackboardValue<T1>(T key, T1 inValue);
T GetBlackboardValue<T>(string key); T1 GetBlackboardValue<T1>(T key);
} }
} }

View File

@ -1,7 +1,8 @@
using System;
using Opsive.BehaviorDesigner.Runtime.Tasks; using Opsive.BehaviorDesigner.Runtime.Tasks;
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
using Unity.VisualScripting; using Unity.VisualScripting;
using UnityEngine; using UnityEngine;
using Action = Opsive.BehaviorDesigner.Runtime.Tasks.Actions.Action;
namespace DDD.Restaurant namespace DDD.Restaurant
{ {
@ -11,7 +12,7 @@ public enum EmotionType
Satisfied, Satisfied,
} }
public class ExpressEmotion : Action public class ExpressEmotion<T> : Action where T : Enum
{ {
public interface IEmotionVisual public interface IEmotionVisual
{ {
@ -21,13 +22,14 @@ public interface IEmotionVisual
} }
//이를 파생해서 기본값을 주거나, 바로 사용하면 될 듯 //이를 파생해서 기본값을 주거나, 바로 사용하면 될 듯
[SerializeField] protected string _emotionBlackboardKey; [SerializeField] protected T _emotionBlackboardKey;
private IEmotionVisual _emotionVisual; private IEmotionVisual _emotionVisual;
public override void OnStart() public override void OnStart()
{ {
var currentEmotion = (EmotionType)m_BehaviorTree.GetVariable<int>(_emotionBlackboardKey).Value; var blackboard = gameObject.GetComponent<IAISharedBlackboard<T>>();
var currentEmotion = blackboard.GetBlackboardValue<EmotionType>(_emotionBlackboardKey);
_emotionVisual = gameObject.GetComponentInChildren<IEmotionVisual>(); _emotionVisual = gameObject.GetComponentInChildren<IEmotionVisual>();
if (_emotionVisual == null) if (_emotionVisual == null)

View File

@ -1,6 +1,7 @@
using System;
using Opsive.BehaviorDesigner.Runtime.Tasks; using Opsive.BehaviorDesigner.Runtime.Tasks;
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
using UnityEngine; using UnityEngine;
using Action = Opsive.BehaviorDesigner.Runtime.Tasks.Actions.Action;
namespace DDD.Restaurant namespace DDD.Restaurant
{ {
@ -8,7 +9,7 @@ namespace DDD.Restaurant
/// 인터랙션 타겟을 바라보도록 시각(Spine/애니메이션) 컴포넌트에 위임하는 액션의 껍데기/골격. /// 인터랙션 타겟을 바라보도록 시각(Spine/애니메이션) 컴포넌트에 위임하는 액션의 껍데기/골격.
/// 실제 회전/스파인 제어 로직은 별도의 Visual 컴포넌트(예: Spine/Animation Controller)에서 구현하십시오. /// 실제 회전/스파인 제어 로직은 별도의 Visual 컴포넌트(예: Spine/Animation Controller)에서 구현하십시오.
/// </summary> /// </summary>
public class LookAtInteractionTarget : Action public abstract class LookAtInteractionTarget<T> : Action where T : Enum
{ {
[Header("Target Settings")] [Header("Target Settings")]
[Tooltip("InteractionPoints를 사용해 가장 적절한 지점을 바라봄")] [Tooltip("InteractionPoints를 사용해 가장 적절한 지점을 바라봄")]
@ -27,20 +28,22 @@ public interface ILookAtVisual
} }
private ILookAtVisual _visual; private ILookAtVisual _visual;
private GameObject _cachedTarget; protected GameObject _cachedTarget;
private bool _isLooking; private bool _isLooking;
private Vector3 _currentLookPosition; private Vector3 _currentLookPosition;
public override void OnStart() public sealed override void OnStart()
{ {
_visual = gameObject.GetComponentInParent<ILookAtVisual>(); _visual = gameObject.GetComponentInParent<ILookAtVisual>();
_isLooking = false; _isLooking = false;
var blackboard = gameObject.GetComponent<IAISharedBlackboard>(); var blackboard = gameObject.GetComponent<IAISharedBlackboard<T>>();
InitializeBlackboard(blackboard);
_cachedTarget = blackboard.GetBlackboardValue<GameObject>(nameof(RestaurantCustomerBlackboardKey.CurrentTargetGameObject));
} }
protected abstract void InitializeBlackboard(IAISharedBlackboard<T> blackboard);
public override TaskStatus OnUpdate() public override TaskStatus OnUpdate()
{ {
if (_cachedTarget == null) if (_cachedTarget == null)

View File

@ -1,14 +1,15 @@
using System;
using Opsive.BehaviorDesigner.Runtime.Tasks; using Opsive.BehaviorDesigner.Runtime.Tasks;
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
using Unity.VisualScripting; using Unity.VisualScripting;
using UnityEngine; using UnityEngine;
using Action = Opsive.BehaviorDesigner.Runtime.Tasks.Actions.Action;
namespace DDD.Restaurant namespace DDD.Restaurant
{ {
/// <summary> /// <summary>
/// IAiMovement를 이용해 인터랙션 타겟으로 이동하는 액션 /// IAiMovement를 이용해 인터랙션 타겟으로 이동하는 액션
/// </summary> /// </summary>
public class MoveToTargetPoint : Action public abstract class MoveToTargetPoint<T> : Action where T : Enum
{ {
[Header("Target Settings")] [Header("Target Settings")]
[Tooltip("InteractionPoints를 사용해 가장 가까운 지점으로 이동")] [Tooltip("InteractionPoints를 사용해 가장 가까운 지점으로 이동")]
@ -32,18 +33,20 @@ public class MoveToTargetPoint : Action
private float _repathTimer; private float _repathTimer;
private Vector3 _currentDestination; private Vector3 _currentDestination;
private bool _isMoving; private bool _isMoving;
private GameObject _target; protected GameObject _target;
public override void OnStart() public sealed override void OnStart()
{ {
_movement = gameObject.GetComponent<IAiMovement>(); _movement = gameObject.GetComponent<IAiMovement>();
_repathTimer = 0f; _repathTimer = 0f;
_isMoving = false; _isMoving = false;
var blackboard = gameObject.GetComponent<IAISharedBlackboard>(); var blackboard = gameObject.GetComponent<IAISharedBlackboard<T>>();
_target = blackboard.GetBlackboardValue<GameObject>(nameof(RestaurantCustomerBlackboardKey.CurrentTargetGameObject)); InitializeBlackboard(blackboard);
} }
protected abstract void InitializeBlackboard(IAISharedBlackboard<T> blackboard);
public override TaskStatus OnUpdate() public override TaskStatus OnUpdate()
{ {
if (_movement == null) if (_movement == null)

View File

@ -0,0 +1,7 @@
namespace DDD.Restaurant
{
public class RestaurantExpressEmotion : ExpressEmotion<RestaurantCustomerBlackboardKey>
{
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 12b0bf82aef64d9d82a77a82943d7878
timeCreated: 1756692031

View File

@ -0,0 +1,12 @@
using UnityEngine;
namespace DDD.Restaurant
{
public class RestaurantLookAtInteractionTarget : LookAtInteractionTarget<RestaurantCustomerBlackboardKey>
{
protected override void InitializeBlackboard(IAISharedBlackboard<RestaurantCustomerBlackboardKey> blackboard)
{
_cachedTarget = blackboard.GetBlackboardValue<GameObject>(RestaurantCustomerBlackboardKey.CurrentTargetGameObject);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0d73d4725548490da952ac2079f98497
timeCreated: 1756692248

View File

@ -0,0 +1,12 @@
using UnityEngine;
namespace DDD.Restaurant
{
public class RestaurantMoveToTargetPoint : MoveToTargetPoint<RestaurantCustomerBlackboardKey>
{
protected override void InitializeBlackboard(IAISharedBlackboard<RestaurantCustomerBlackboardKey> blackboard)
{
_target = blackboard.GetBlackboardValue<GameObject>(RestaurantCustomerBlackboardKey.CurrentTargetGameObject);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e02da400458a473d9131601fc09192c0
timeCreated: 1756692564

View File

@ -0,0 +1,7 @@
namespace DDD.Restaurant
{
public class RestaurantSearchAndRegisterMarker : SearchAndRegisterMarker<RestaurantCustomerBlackboardKey>
{
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e62500af8d764eac9b14024f155a168f
timeCreated: 1756693121

View File

@ -1,13 +1,14 @@
using System;
using Opsive.BehaviorDesigner.Runtime.Tasks; using Opsive.BehaviorDesigner.Runtime.Tasks;
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
using UnityEngine; using UnityEngine;
using Action = Opsive.BehaviorDesigner.Runtime.Tasks.Actions.Action;
namespace DDD.Restaurant namespace DDD.Restaurant
{ {
public class SearchAndRegisterMarker : Action public class SearchAndRegisterMarker<T> : Action where T : Enum
{ {
[SerializeField] private PointType _pointType; [SerializeField] private PointType _pointType;
[SerializeField] private RestaurantCustomerBlackboardKey _markerBlackboardKey; [SerializeField] private T _markerBlackboardKey;
private bool _isRegistered; private bool _isRegistered;
public override void OnStart() public override void OnStart()
@ -15,13 +16,12 @@ public override void OnStart()
var environmentState = RestaurantState.Instance?.EnvironmentState; var environmentState = RestaurantState.Instance?.EnvironmentState;
if (environmentState == null) return; if (environmentState == null) return;
var pointProviders = environmentState.GetPointProviderByType(_pointType); var pointProviders = environmentState.GetPointProviderByType(_pointType);
var blackboard = gameObject.GetComponent<IAISharedBlackboard>(); var blackboard = gameObject.GetComponent<IAISharedBlackboard<T>>();
if (blackboard == null) return; if (blackboard == null) return;
foreach (var pointProvider in pointProviders) foreach (var pointProvider in pointProviders)
{ {
if (!pointProvider.IsSupportsType(_pointType)) continue; blackboard.SetBlackboardValue(_markerBlackboardKey, pointProvider.GetGameObject());
blackboard.SetBlackboardValue(nameof(_markerBlackboardKey), pointProvider.GetGameObject());
_isRegistered = true; _isRegistered = true;
break; break;
} }

View File

@ -1,12 +1,15 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Opsive.GraphDesigner.Runtime.Variables; using Opsive.GraphDesigner.Runtime.Variables;
using Sirenix.OdinInspector;
using Sirenix.Serialization;
using UnityEngine; using UnityEngine;
namespace DDD.Restaurant namespace DDD.Restaurant
{ {
public class BlackboardSo<T> : ScriptableObject where T : Enum public class BlackboardSo<T> : ScriptableObject where T : Enum
{ {
[OdinSerialize, ShowInInspector]
private Dictionary<T, SharedVariable> _variables = new(); private Dictionary<T, SharedVariable> _variables = new();
public SharedVariable<T1> GetVariable<T1>(T key) public SharedVariable<T1> GetVariable<T1>(T key)
@ -25,6 +28,7 @@ public void SetVariable<T1>(T key, T1 value)
if (outVariable != null) if (outVariable != null)
{ {
outVariable.Value = value; outVariable.Value = value;
_variables[key] = outVariable;
} }
else else
{ {

View File

@ -238,7 +238,7 @@ public void Execute(ref DynamicBuffer<BranchComponent> branchComponents,
} }
[NodeDescription("자식 태스크의 실행 시간을 제한합니다 (GameObject 워크플로우)")] [NodeDescription("자식 태스크의 실행 시간을 제한합니다 (GameObject 워크플로우)")]
public class SharedTimeLimiter : DecoratorNode public class SharedTimeLimiter<T> : DecoratorNode
{ {
[Tooltip("최대 실행 시간(초)")] [Tooltip("최대 실행 시간(초)")]
[SerializeField] protected SharedVariable<float> _timeLimit = 30f; [SerializeField] protected SharedVariable<float> _timeLimit = 30f;
@ -248,7 +248,7 @@ public class SharedTimeLimiter : DecoratorNode
[Tooltip("하단 블랙보드 키에 현재 시간을 저장할 지")] [Tooltip("하단 블랙보드 키에 현재 시간을 저장할 지")]
[SerializeField] protected bool _isBlackboardWriteEnabled = false; [SerializeField] protected bool _isBlackboardWriteEnabled = false;
[SerializeField] protected string _blackboardKey = "CurrentTime"; [SerializeField] protected T _blackboardKey;
public SharedVariable<float> TimeLimit public SharedVariable<float> TimeLimit
{ {

View File

@ -0,0 +1,20 @@
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
using UnityEngine;
namespace DDD.Restaurant
{
public class IncrementOrderCount : Action
{
public override void OnStart()
{
var blackboard = gameObject.GetComponent<IAISharedBlackboard<RestaurantCustomerBlackboardKey>>();
if (blackboard == null)
{
Debug.LogWarning($"블랙보드가 존재하지 않음 해시코드: {gameObject.GetHashCode()}");
return;
}
var cumulativeOrderCount = blackboard.GetBlackboardValue<int>(RestaurantCustomerBlackboardKey.CumulativeOrderCount);
blackboard.SetBlackboardValue(RestaurantCustomerBlackboardKey.CumulativeOrderCount, ++cumulativeOrderCount);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f3f7e66da71042a5b5a679261d1bbf0b
timeCreated: 1756700443

View File

@ -0,0 +1,15 @@
using Opsive.BehaviorDesigner.Runtime.Tasks;
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
using UnityEngine;
namespace DDD.Restaurant
{
public class LeaveRestaurant : Action
{
public override TaskStatus OnUpdate()
{
Object.Destroy(gameObject);
return TaskStatus.Running;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e39046ffc9bb46d39d2a5c4a9899cd68
timeCreated: 1756702274

View File

@ -0,0 +1,33 @@
using Opsive.BehaviorDesigner.Runtime.Tasks;
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
using UnityEngine;
namespace DDD.Restaurant
{
public class PublishRestaurantInterruptEvent : Action
{
[SerializeField] private RestaurantOrderType _targetInterruptType;
public override TaskStatus OnUpdate()
{
var blackboard = gameObject.GetComponent<IAISharedBlackboard<RestaurantCustomerBlackboardKey>>();
if (blackboard == null) return TaskStatus.Failure;
var currentTarget = blackboard.GetBlackboardValue<GameObject>(RestaurantCustomerBlackboardKey.CurrentTargetGameObject);
if (currentTarget == null) return TaskStatus.Failure;
var orderObject = currentTarget.GetComponent<IRestaurantOrderObject>();
if (orderObject == null) return TaskStatus.Failure;
RestaurantOrderInterrupt evt = new()
{
Table = currentTarget,
OrderObject = orderObject.GetOrderObjectState(),
TransitionType = _targetInterruptType
};
EventBus.Broadcast(evt);
return TaskStatus.Success;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: aaaf550a60264509a1ebd35506000e2b
timeCreated: 1756706679

View File

@ -14,13 +14,13 @@ public enum EvaluationStep
[NodeDescription("만족도 평가 테스크")] [NodeDescription("만족도 평가 테스크")]
public class SatisfactionEvaluator : Action public class SatisfactionEvaluator : Action
{ {
[SerializeField] private string _satisfactionBlackboardKey; [SerializeField] private RestaurantCustomerBlackboardKey _satisfactionBlackboardKey;
[SerializeField] private EvaluationStep _evaluationStep; [SerializeField] private EvaluationStep _evaluationStep;
public override void OnStart() public override void OnStart()
{ {
var currentSatisfaction = CalculateSatisfaction(); var currentSatisfaction = CalculateSatisfaction();
var blackboard = gameObject.GetComponent<IAISharedBlackboard>(); var blackboard = gameObject.GetComponent<IAISharedBlackboard<RestaurantCustomerBlackboardKey>>();
blackboard.SetBlackboardValue(_satisfactionBlackboardKey, (int)currentSatisfaction); blackboard.SetBlackboardValue(_satisfactionBlackboardKey, currentSatisfaction);
} }
//TODO 만족도 계산? //TODO 만족도 계산?

View File

@ -22,8 +22,8 @@ public override void OnStart()
public override TaskStatus OnUpdate() public override TaskStatus OnUpdate()
{ {
var blackboard = gameObject.GetComponent<IAISharedBlackboard>(); var blackboard = gameObject.GetComponent<IAISharedBlackboard<RestaurantCustomerBlackboardKey>>();
var target = blackboard?.GetBlackboardValue<GameObject>(nameof(RestaurantCustomerBlackboardKey.CurrentTargetGameObject)); var target = blackboard?.GetBlackboardValue<GameObject>(RestaurantCustomerBlackboardKey.CurrentTargetGameObject);
IInteractable currentInteractable = target?.GetComponent<IInteractable>(); IInteractable currentInteractable = target?.GetComponent<IInteractable>();
if (_targetOrderType == RestaurantOrderType.Wait) if (_targetOrderType == RestaurantOrderType.Wait)
{ {
@ -33,8 +33,8 @@ public override TaskStatus OnUpdate()
{ {
return TaskStatus.Failure; return TaskStatus.Failure;
} }
var customerBlackboard = gameObject.GetComponent<IAISharedBlackboard>(); var customerBlackboard = gameObject.GetComponent<IAISharedBlackboard<RestaurantCustomerBlackboardKey>>();
customerBlackboard?.SetBlackboardValue(nameof(RestaurantCustomerBlackboardKey.CurrentTargetGameObject), currentInteractable.GetInteractableGameObject()); customerBlackboard?.SetBlackboardValue(RestaurantCustomerBlackboardKey.CurrentTargetGameObject, currentInteractable.GetInteractableGameObject());
} }
// Check order type of the current interactable // Check order type of the current interactable
@ -70,8 +70,8 @@ public override TaskStatus OnUpdate()
if (_targetOrderType == RestaurantOrderType.Busy) if (_targetOrderType == RestaurantOrderType.Busy)
{ {
var customerBlackboard = gameObject.GetComponent<IAISharedBlackboard>(); var customerBlackboard = gameObject.GetComponent<IAISharedBlackboard<RestaurantCustomerBlackboardKey>>();
customerBlackboard?.SetBlackboardValue<GameObject>(nameof(RestaurantCustomerBlackboardKey.CurrentTargetGameObject), null); customerBlackboard?.SetBlackboardValue<GameObject>(RestaurantCustomerBlackboardKey.CurrentTargetGameObject, null);
} }
return TaskStatus.Success; return TaskStatus.Success;

View File

@ -16,10 +16,10 @@ public class WaitForPlayerInteraction : Action
public override void OnStart() public override void OnStart()
{ {
GameObject interactionTarget = null; GameObject interactionTarget = null;
if (!gameObject.TryGetComponent<IAISharedBlackboard>(out var sharedBlackboard)) return; if (!gameObject.TryGetComponent<IAISharedBlackboard<RestaurantCustomerBlackboardKey>>(out var sharedBlackboard)) return;
interactionTarget = interactionTarget =
sharedBlackboard.GetBlackboardValue<GameObject>( sharedBlackboard.GetBlackboardValue<GameObject>(
nameof(RestaurantCustomerBlackboardKey.CurrentTargetGameObject)); RestaurantCustomerBlackboardKey.CurrentTargetGameObject);
if (interactionTarget == null) if (interactionTarget == null)
{ {

View File

@ -0,0 +1,54 @@
using Opsive.BehaviorDesigner.Runtime.Tasks;
using Opsive.BehaviorDesigner.Runtime.Tasks.Conditionals;
using UnityEngine;
namespace DDD.Restaurant
{
public class CheckCumulateOrderCount : Conditional
{
public enum ComparisonType
{
Less,
LessOrEqual,
Equal,
NotEqual,
Greater,
GreaterOrEqual
}
[SerializeField] private int _compareOrderCount;
[SerializeField] private ComparisonType _comparisonType;
public override TaskStatus OnUpdate()
{
var blackboard = gameObject.GetComponent<IAISharedBlackboard<RestaurantCustomerBlackboardKey>>();
if (blackboard == null)
{
Debug.LogWarning($"블랙보드가 존재하지 않음 해시코드: {gameObject.GetHashCode()}");
return TaskStatus.Failure;
}
var cumulativeOrderCount = blackboard.GetBlackboardValue<int>(RestaurantCustomerBlackboardKey.CumulativeOrderCount);
return Evaluate(cumulativeOrderCount) ? TaskStatus.Success : TaskStatus.Failure;
}
private bool Evaluate(int compareValue)
{
switch (_comparisonType)
{
case ComparisonType.Less:
return compareValue < _compareOrderCount;
case ComparisonType.LessOrEqual:
return compareValue <= _compareOrderCount;
case ComparisonType.Equal:
return compareValue == _compareOrderCount;
case ComparisonType.NotEqual:
return compareValue != _compareOrderCount;
case ComparisonType.Greater:
return compareValue > _compareOrderCount;
case ComparisonType.GreaterOrEqual:
return compareValue >= _compareOrderCount;
default:
return false;
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4b3c5ee2ca89419394819e57dbab0bcd
timeCreated: 1756701161

View File

@ -1,36 +1,68 @@
using DDD.Restaurant;
using Opsive.BehaviorDesigner.Runtime; using Opsive.BehaviorDesigner.Runtime;
using Opsive.GraphDesigner.Runtime.Variables; using Opsive.GraphDesigner.Runtime.Variables;
using Sirenix.OdinInspector;
using UnityEngine; using UnityEngine;
namespace DDD namespace DDD
{ {
public class CustomerBlackboardComponent : MonoBehaviour, ICustomerBlackboard, IAISharedBlackboard public class CustomerBlackboardComponent : MonoBehaviour, ICustomerBlackboard, IAISharedBlackboard<RestaurantCustomerBlackboardKey>
{ {
private BehaviorTree _behaviorTree; private BehaviorTree _behaviorTree;
[SerializeField, InlineEditor]
private CustomerBlackboardSo _blackboardSo;
public void InitializeWithBehaviorTree(BehaviorTree inBehaviorTree) public void InitializeWithBehaviorTree(BehaviorTree inBehaviorTree)
{ {
_behaviorTree = inBehaviorTree; _behaviorTree = inBehaviorTree;
SetBlackboardValue(nameof(RestaurantCustomerBlackboardKey.SelfGameObject), gameObject);
_blackboardSo = ScriptableObject.CreateInstance<CustomerBlackboardSo>();
_blackboardSo.SetVariable(RestaurantCustomerBlackboardKey.SelfGameObject, gameObject);
if (!_behaviorTree) return;
_behaviorTree.SetVariableValue(nameof(BlackboardKey.RestaurantBlackboard), _blackboardSo);
} }
public void SetCustomerData(string inCustomerDataId) public void SetCustomerData(string inCustomerDataId)
{ {
SetBlackboardValue(nameof(RestaurantCustomerBlackboardKey.CustomerDataId), inCustomerDataId); _blackboardSo.SetVariable(RestaurantCustomerBlackboardKey.CustomerDataId, inCustomerDataId);
} }
public void SetBlackboardValue<T>(string key, T inValue) public void SetBlackboardValue<T>(RestaurantCustomerBlackboardKey key, T inValue)
{ {
if (!_behaviorTree) return; if (!_behaviorTree) return;
_behaviorTree.SetVariableValue(key, inValue);
var blackboardSo =
_behaviorTree.GetVariable<CustomerBlackboardSo>(nameof(BlackboardKey.RestaurantBlackboard));
if (blackboardSo == null || blackboardSo.Value == null)
{
Debug.LogWarning($"behaviorTree에 blackboardSo가 존재하지 않음 오브젝트 해시코드: {gameObject.GetHashCode()}");
return;
}
blackboardSo.Value.SetVariable(key, inValue);
} }
public T GetBlackboardValue<T>(string key) public T GetBlackboardValue<T>(RestaurantCustomerBlackboardKey key)
{ {
if (!_behaviorTree) return default; if (!_behaviorTree) return default;
SharedVariable<T> blackboardValue = _behaviorTree.GetVariable<T>(key); var blackboardSo =
_behaviorTree.GetVariable<CustomerBlackboardSo>(nameof(BlackboardKey.RestaurantBlackboard));
return blackboardValue != null ? blackboardValue.Value : default; if (blackboardSo == null || blackboardSo.Value == null)
{
Debug.LogWarning($"behaviorTree에 blackboardSo가 존재하지 않음 오브젝트 해시코드: {gameObject.GetHashCode()}");
return default;
}
var value = blackboardSo.Value.GetVariable<T>(key);
return value != null ? value.Value : default;
}
private void OnDestroy()
{
if (_blackboardSo)
{
Destroy(_blackboardSo);
}
} }
} }
} }

View File

@ -0,0 +1,12 @@
using System;
using UnityEngine;
namespace DDD.Restaurant
{
[Serializable]
public class CustomerBlackboardSo : BlackboardSo<RestaurantCustomerBlackboardKey>
{
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b5d5fca67b054314aef1ee6f3fda2b76
timeCreated: 1756689633

View File

@ -3,9 +3,9 @@
namespace DDD.Restaurant namespace DDD.Restaurant
{ {
public class CustomerTimeLimiter : SharedTimeLimiter public class CustomerTimeLimiter : SharedTimeLimiter<RestaurantCustomerBlackboardKey>
{ {
private IAISharedBlackboard _blackboard; private IAISharedBlackboard<RestaurantCustomerBlackboardKey> _blackboard;
public override void OnStart() public override void OnStart()
{ {
base.OnStart(); base.OnStart();

View File

@ -2,13 +2,18 @@
namespace DDD namespace DDD
{ {
public enum BlackboardKey
{
RestaurantBlackboard,
}
public enum RestaurantCustomerBlackboardKey public enum RestaurantCustomerBlackboardKey
{ {
SelfGameObject, SelfGameObject,
CustomerDataId, CustomerDataId,
CurrentTargetGameObject, CurrentTargetGameObject,
SatisfactionLevel, SatisfactionLevel,
OrderCount, CumulativeOrderCount,
Remaining
} }
public interface ICustomerBlackboard public interface ICustomerBlackboard

View File

@ -60,6 +60,7 @@ public bool TryMoveToPosition(Vector3 position)
_iAstarAi.destination = position; _iAstarAi.destination = position;
PlayMove(); PlayMove();
_iAstarAi.SearchPath();
return true; return true;
} }

View File

@ -2,7 +2,7 @@
using UnityEngine; using UnityEngine;
using UnityEngine.Serialization; using UnityEngine.Serialization;
namespace DDD namespace DDD.Restaurant
{ {
public enum RestaurantOrderType : uint public enum RestaurantOrderType : uint
{ {
@ -25,6 +25,16 @@ public class RestaurantOrderObjectState
public string ServedFoodId; public string ServedFoodId;
} }
public class RestaurantOrderInterrupt : IEvent
{
public GameObject Table;
public RestaurantOrderObjectState OrderObject;
public RestaurantOrderType TransitionType;
}
// RestaurantOrderInterrupt 이벤트 ? OrderObject, Worker는 여기에 등록해두기. Customer가 이벤트 호출
// InterruptResult - Clean, Dirty?
public interface IRestaurantOrderObject public interface IRestaurantOrderObject
{ {
void TransitionToNextPhase(); void TransitionToNextPhase();
@ -34,7 +44,7 @@ public interface IRestaurantOrderObject
// TODO : 도중에 이탈할 경우, 순차적으로 다음 페이즈로 넘어가는 게 아니라 바로 Wait, Dirty로 전이시킬 필요가 있음 // TODO : 도중에 이탈할 경우, 순차적으로 다음 페이즈로 넘어가는 게 아니라 바로 Wait, Dirty로 전이시킬 필요가 있음
public class InteractionSubsystem_Order : MonoBehaviour, IInteractionSubsystemObject<RestaurantOrderType>, IRestaurantOrderObject public class InteractionSubsystem_Order : MonoBehaviour, IInteractionSubsystemObject<RestaurantOrderType>, IRestaurantOrderObject, IEventHandler<RestaurantOrderInterrupt>
{ {
[SerializeField] private RestaurantOrderType _currentRestaurantOrderType = RestaurantOrderType.Wait; [SerializeField] private RestaurantOrderType _currentRestaurantOrderType = RestaurantOrderType.Wait;
[SerializeField] private RestaurantOrderObjectState _orderObjectState = new(); [SerializeField] private RestaurantOrderObjectState _orderObjectState = new();
@ -83,6 +93,7 @@ public ScriptableObject GetPayload()
public void InitializeSubsystem() public void InitializeSubsystem()
{ {
EventBus.Register(this);
} }
public RestaurantOrderType GetInteractionSubsystemType() public RestaurantOrderType GetInteractionSubsystemType()
@ -113,5 +124,17 @@ public bool CanTransitionToNextPhase()
{ {
return true; return true;
} }
public void HandleEvent(RestaurantOrderInterrupt evt)
{
if (evt.Table != gameObject) return;
Debug.Log($"Order Interrupt : {evt.TransitionType}, Current State : {GetInteractionSubsystemType()}");
SetInteractionSubsystemType(evt.TransitionType);
}
private void OnDestroy()
{
EventBus.Unregister(this);
}
} }
} }

View File

@ -1,15 +0,0 @@
using UnityEngine;
namespace DDD.Restaurant
{
//public class RestaurantEnvironmentPointQueryEvent : RestaurantEventBase<PointType>
//{
// protected override bool EventSolve(GameObject causer, GameObject target, PointType eventType, ScriptableObject payload)
// {
// if (!target.TryGetComponent(out IEnvironmentPointProvider provider)) return false;
// if (!provider.IsSupportsType(eventType)) return false;
//
// return true;
// }
//}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 17b28cfc7a5a4d43a050d247e22004dc
timeCreated: 1756453073

View File

@ -24,30 +24,20 @@ public class RestaurantInteractionEvent : RestaurantEventBase<InteractionType>
protected override bool EventSolve(GameObject causer, GameObject target, InteractionType interactionType, protected override bool EventSolve(GameObject causer, GameObject target, InteractionType interactionType,
ScriptableObject payload) ScriptableObject payload)
{ {
IInteractor interactor = causer.GetComponent<IInteractor>(); var interactor = causer.GetComponent<IInteractor>();
if (interactor != null && interactor.CanSolveInteractionType(interactionType)) if (interactor == null || !interactor.CanSolveInteractionType(interactionType)) return false;
{ if (!interactor.FetchSolverTypeForInteraction(interactionType, out var solverType)) return false;
if (interactor.FetchSolverTypeForInteraction(interactionType, out var solverType))
{
// Solve event directly. 이벤트 처리는 여기서 하고, 이벤트 호출로는 이런 이벤트가 호출되었고 결과가 어떻다는 거 전파하는 식으로. // Solve event directly. 이벤트 처리는 여기서 하고, 이벤트 호출로는 이런 이벤트가 호출되었고 결과가 어떻다는 거 전파하는 식으로.
if (solverType != null) if (solverType == null) return false;
{ var solver = causer.GetComponent(solverType) as IInteractionSolver;
IInteractionSolver solver = causer.GetComponent(solverType) as IInteractionSolver; var interactable = target.GetComponent<IInteractable>();
IInteractable interactable = target.GetComponent<IInteractable>();
if (solver is not null) if (solver is not null)
{ {
bool canExecute = solver.CanExecuteInteraction(interactor, interactable, payload); bool canExecute = solver.CanExecuteInteraction(interactor, interactable, payload);
return canExecute && solver.ExecuteInteraction(interactor, interactable, payload); return canExecute && solver.ExecuteInteraction(interactor, interactable, payload);
} }
else
{
// Should not reach here!
Debug.Assert(false, "Solver Component or Interactor is null");
}
}
}
}
Debug.Assert(false, "Solver Component or Interactor is null");
return false; return false;
} }
} }

View File

@ -0,0 +1,26 @@
using System.Collections.Generic;
using UnityEngine;
namespace DDD.Restaurant
{
public class CustomerPatienceUiComponent : MonoBehaviour
{
private IAISharedBlackboard<RestaurantCustomerBlackboardKey> _blackboard;
[SerializeField] HashSet<RestaurantOrderType> _targetOrderType;
[SerializeField] RestaurantOrderType _currentOrderType;
private void Start()
{
if (!TryGetComponent(out _blackboard))
{
Debug.LogWarning($"[{GetType().Name}] 블랙보드가 존재하지 않음 오브젝트 해시코드 {gameObject.GetHashCode()}");
return;
}
}
private void Update()
{
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e509fdc10cff4d3487080f126f32544f
timeCreated: 1756711651