Merge branch 'feature/customer_behavior' into develop
This commit is contained in:
commit
b0528c5486
@ -133,7 +133,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 201f9e6d7ca7404baa9945950292a392, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
_interactionType: 4
|
||||
_interactionType: 2
|
||||
_executionParameters:
|
||||
_holdTime: 0
|
||||
_displayParameters:
|
||||
@ -141,6 +141,7 @@ MonoBehaviour:
|
||||
_interactionAvailableFlows: 2
|
||||
_aiInteractionPoints:
|
||||
- {fileID: 1664322405549350652}
|
||||
autoInitialize: 1
|
||||
--- !u!114 &4456475204957017828
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
|
@ -136,6 +136,7 @@ MonoBehaviour:
|
||||
m_EditorClassIdentifier:
|
||||
_interactionType: 1
|
||||
_executionParameters:
|
||||
_holdTime: 0.1
|
||||
_holdTime: 0.5
|
||||
_displayParameters:
|
||||
_messageKey: Test
|
||||
|
BIN
Assets/_DDD/_Addressables/AI/Customer/Subtree/CustomerDefault.asset
(Stored with Git LFS)
BIN
Assets/_DDD/_Addressables/AI/Customer/Subtree/CustomerDefault.asset
(Stored with Git LFS)
Binary file not shown.
BIN
Assets/_DDD/_Addressables/AI/Customer/Subtree/OrderSubtree.asset
(Stored with Git LFS)
Normal file
BIN
Assets/_DDD/_Addressables/AI/Customer/Subtree/OrderSubtree.asset
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bab8aadc83d64f64b8280e6ebb957e65
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
File diff suppressed because it is too large
Load Diff
@ -234,10 +234,18 @@ MonoBehaviour:
|
||||
m_Data:
|
||||
m_TaskData: []
|
||||
m_EventTaskData: []
|
||||
m_SharedVariableData: []
|
||||
m_SharedVariableData:
|
||||
- m_ObjectType: 'Opsive.GraphDesigner.Runtime.Variables.SharedVariable`1[[UnityEngine.GameObject,
|
||||
UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]'
|
||||
m_ValueHashes:
|
||||
m_LongValueHashes: 0d00eb254f8d1b29baa620a07799d549a996976a4a64278927dafeacd28e0b00
|
||||
m_ValuePositions: 000000000e0000000e0000000f000000
|
||||
m_Values: 53656c6647616d654f626a65637402ffffffff
|
||||
m_UnityObjects: []
|
||||
m_Version: 3.4
|
||||
m_DisabledEventNodesData: []
|
||||
m_DisabledLogicNodesData: []
|
||||
m_UniqueID: -1885404201
|
||||
m_UniqueID: 1495981264
|
||||
m_LogicNodePropertiesData: []
|
||||
m_EventNodePropertiesData: []
|
||||
m_GroupPropertiesData: []
|
||||
|
@ -366,6 +366,9 @@ PrefabInstance:
|
||||
- targetCorrespondingSourceObject: {fileID: 5259510642736920361, guid: 3db3fc62639929c4ba6031ca4ae6600c, type: 3}
|
||||
insertIndex: -1
|
||||
addedObject: {fileID: 8993310060139522557}
|
||||
- targetCorrespondingSourceObject: {fileID: 5259510642736920361, guid: 3db3fc62639929c4ba6031ca4ae6600c, type: 3}
|
||||
insertIndex: -1
|
||||
addedObject: {fileID: -6848683434426724985}
|
||||
- targetCorrespondingSourceObject: {fileID: 6791841979869644848, guid: 3db3fc62639929c4ba6031ca4ae6600c, type: 3}
|
||||
insertIndex: -1
|
||||
addedObject: {fileID: 662634663174340165}
|
||||
@ -423,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}
|
||||
@ -461,6 +464,36 @@ MonoBehaviour:
|
||||
m_EditorClassIdentifier:
|
||||
blockOutlineAndGlow: 1
|
||||
blockOverlay: 1
|
||||
--- !u!114 &-6848683434426724985
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7316134055819320434}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 0cdaa3305fa954c45a80c9662aa6f425, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_GraphName: Behavior Tree
|
||||
m_Index: 0
|
||||
m_Data:
|
||||
m_TaskData: []
|
||||
m_EventTaskData: []
|
||||
m_SharedVariableData: []
|
||||
m_DisabledEventNodesData: []
|
||||
m_DisabledLogicNodesData: []
|
||||
m_UniqueID: 732308450
|
||||
m_LogicNodePropertiesData: []
|
||||
m_EventNodePropertiesData: []
|
||||
m_GroupPropertiesData: []
|
||||
m_StartWhenEnabled: 1
|
||||
m_PauseWhenDisabled: 0
|
||||
m_UpdateMode: 0
|
||||
m_EvaluationType: 0
|
||||
m_MaxEvaluationCount: 1
|
||||
m_Subtree: {fileID: 0}
|
||||
--- !u!4 &7511707580127947132 stripped
|
||||
Transform:
|
||||
m_CorrespondingSourceObject: {fileID: 4993183601549197863, guid: 3db3fc62639929c4ba6031ca4ae6600c, type: 3}
|
||||
|
@ -365,7 +365,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: []
|
||||
@ -10722,6 +10723,92 @@ MeshFilter:
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1771012164}
|
||||
m_Mesh: {fileID: 0}
|
||||
--- !u!1 &1775054119
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1775054121}
|
||||
- component: {fileID: 1775054120}
|
||||
m_Layer: 0
|
||||
m_Name: AstarPath
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &1775054120
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1775054119}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 78396926cbbfc4ac3b48fc5fc34a87d1, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
version: 1073741824
|
||||
data:
|
||||
dataString: UEsDBBQAAAgIAABIIewaXtoYSgIAALMEAAALACQAZ3JhcGgxLmpzb24KACAAAAAAAAEAGAAAgD7V3rGdAQCAPtXesZ0BAIA+1d6xnQF1U8tu2zAQ/BVD56awZRuOe4ydpoc8CstAgxQ9rMm1RJgiXZKy4xj59+5SjyhNeyI0s7PP0TkRBTgQAd0KpKp88mUw/Dz9NEiENcFW7g6er52zjvCUUdQ6Uy8Yw1KOO4LewUbjN1R5EZqwFlxoVW66lCU8Z9ruWTwe1t/XMsdbNHkoWBlBZVaYK2uaMmPCUKpg3VppbMBReklwaIDHD8hTh1QeWcdzBVchIXt0t3BCd2el2ioBgWox/fMXkVKVaDwhxHKhZNkC42VC/AbELne2MnLt4IDOw0ZpFU4c+qMZmuMcajyACTcO9kVWuS0I7FLaextW+LtSLsYKqzUKbiPDEJTJuZtzD26FsW3Pkn1x8kr4TKBh5vz6F5YuW1THUcHvGBgkB9AVKy5GA2YD5A0Xp3fgyQi0vwXVVpKLdWvruDv0RdznFrR/x6zROVDmH5q1w/4JQh1YW6aE/dIejYdyr2n4r2TGaLdxsxruY9UmWmIApfm6xFqzqHfUtWQqrXkuZ0O8az30MzuQ0FPzvvAb599aJ1Be8T39Ag3V+L9iNPkgadzYCMatYtRK0rqKL+yRW3yoAk2Ib3tg4p6Ou7DG1Kfu77WVNfZ5kxkEhz5k9IjiwejT41NPhoZNeA+HkrSLKjrqTZtXSr63QjKepbNhms7mczEbXc7lBU7kdLZJp7M5yOlEDJM4hTIqKNDfKb8O7WLob+7d20AZM65Q0MEG0fxsV+ngeKNeStvzgDJbmwnyhXmoc3Rj06FB02bldfztez9Fcn5NXv8AUEsDBBQAAAgIAABIIexc6sO8cwAAAIUAAAAJACQAbWV0YS5qc29uCgAgAAAAAAABABgAAIA+1d6xnQEAgD7V3rGdAQCAPtXesZ0Bq1YqSy0qzszPU7JSMNUz1rPQUVBKL0osyCgGChiBOKWZKSB2dF5pTg6Qb2xuZG5gZGRuaZlsbmhhmaKbapJiap5kZGpumZhiapJsoBQLVFVSWZDql5ibiqwzILEkIy0zLyUzL10vKDU5sbjEHWSRUmwtAFBLAQItABQAAAgIAABIIewaXtoYSgIAALMEAAALACQAAAAAAAAAAAAAAAAAAABncmFwaDEuanNvbgoAIAAAAAAAAQAYAACAPtXesZ0BAIA+1d6xnQEAgD7V3rGdAVBLAQItABQAAAgIAABIIexc6sO8cwAAAIUAAAAJACQAAAAAAAAAAAAAAJcCAABtZXRhLmpzb24KACAAAAAAAAEAGAAAgD7V3rGdAQCAPtXesZ0BAIA+1d6xnQFQSwUGAAAAAAIAAgC4AAAAVQMAAAAA
|
||||
file_cachedStartup: {fileID: 0}
|
||||
cacheStartup: 0
|
||||
showNavGraphs: 1
|
||||
showUnwalkableNodes: 1
|
||||
debugMode: 4
|
||||
debugFloor: 0
|
||||
debugRoof: 1
|
||||
manualDebugFloorRoof: 0
|
||||
showSearchTree: 0
|
||||
unwalkableNodeDebugSize: 0.3
|
||||
logPathResults: 0
|
||||
maxNearestNodeDistance: 100
|
||||
scanOnStartup: 1
|
||||
fullGetNearestSearch: 0
|
||||
prioritizeGraphs: 0
|
||||
prioritizeGraphsLimit: 1
|
||||
colorSettings:
|
||||
_SolidColor: {r: 0.11764706, g: 0.4, b: 0.7882353, a: 0.9}
|
||||
_UnwalkableNode: {r: 1, g: 0, b: 0, a: 0.5}
|
||||
_BoundsHandles: {r: 0.29, g: 0.454, b: 0.741, a: 0.9}
|
||||
_ConnectionLowLerp: {r: 0, g: 1, b: 0, a: 0.5}
|
||||
_ConnectionHighLerp: {r: 1, g: 0, b: 0, a: 0.5}
|
||||
_MeshEdgeColor: {r: 0, g: 0, b: 0, a: 0.5}
|
||||
_AreaColors: []
|
||||
tagNames: []
|
||||
heuristic: 2
|
||||
heuristicScale: 1
|
||||
threadCount: -1
|
||||
maxFrameTime: 10
|
||||
batchGraphUpdates: 0
|
||||
graphUpdateBatchingInterval: 0.2
|
||||
navmeshUpdates:
|
||||
updateInterval: 0
|
||||
euclideanEmbedding:
|
||||
mode: 0
|
||||
seed: 0
|
||||
pivotPointRoot: {fileID: 0}
|
||||
spreadOutCount: 10
|
||||
showGraphs: 1
|
||||
--- !u!4 &1775054121
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1775054119}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!4 &1784230204 stripped
|
||||
Transform:
|
||||
m_CorrespondingSourceObject: {fileID: 1061695247072719575, guid: 70f56d7d65d2e7842b5bd517ae7fe7fe, type: 3}
|
||||
@ -15146,3 +15233,4 @@ SceneRoots:
|
||||
- {fileID: 852575416}
|
||||
- {fileID: 1311760301}
|
||||
- {fileID: 504708576}
|
||||
- {fileID: 1775054121}
|
||||
|
BIN
Assets/_DDD/_Addressables/So/RestaurantData/DataObjects/RestaurantPlayerData.asset
(Stored with Git LFS)
BIN
Assets/_DDD/_Addressables/So/RestaurantData/DataObjects/RestaurantPlayerData.asset
(Stored with Git LFS)
Binary file not shown.
3
Assets/_DDD/_Scripts/AI.meta
Normal file
3
Assets/_DDD/_Scripts/AI.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c31792c3491f44878a9a5e8ee59504cf
|
||||
timeCreated: 1755770768
|
3
Assets/_DDD/_Scripts/AI/Common.meta
Normal file
3
Assets/_DDD/_Scripts/AI/Common.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 49c654aa3aa94cb9928e2d161cad789a
|
||||
timeCreated: 1755770768
|
15
Assets/_DDD/_Scripts/AI/Common/IAISharedBlackboard.cs
Normal file
15
Assets/_DDD/_Scripts/AI/Common/IAISharedBlackboard.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace DDD
|
||||
{
|
||||
/// <summary>
|
||||
/// 공용 AI 블랙보드 인터페이스.
|
||||
/// - 다양한 캐릭터 AI에서 공통으로 참조하는 현재 인터랙션 타겟만 정의합니다.
|
||||
/// - 필요 시 키-값 확장을 고려하되, 현재는 최소 요구만 충족합니다.
|
||||
/// </summary>
|
||||
public interface IAISharedBlackboard
|
||||
{
|
||||
void SetCurrentInteractionTarget(GameObject targetGameObject);
|
||||
GameObject GetCurrentInteractionTarget();
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 62510fba6cb047419ca463dc523ae536
|
||||
timeCreated: 1755770768
|
@ -13,6 +13,7 @@ public interface IInteractionSubsystemObject
|
||||
public interface IInteractionSubsystemObject<T> : IInteractionSubsystemObject where T : Enum
|
||||
{
|
||||
T GetInteractionSubsystemType();
|
||||
void SetInteractionSubsystemType(T inValue);
|
||||
}
|
||||
|
||||
public interface IInteractionSubsystemSolver
|
||||
|
3
Assets/_DDD/_Scripts/RestaurantCharacter/AI.meta
Normal file
3
Assets/_DDD/_Scripts/RestaurantCharacter/AI.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1fed3b9fae5245cdbf255f627a82a1e6
|
||||
timeCreated: 1755747969
|
3
Assets/_DDD/_Scripts/RestaurantCharacter/AI/Common.meta
Normal file
3
Assets/_DDD/_Scripts/RestaurantCharacter/AI/Common.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cfc8e456b2134c4a87b9fcd0d385cf1d
|
||||
timeCreated: 1755767029
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ab824a41c52d4cca8cafb1fc96d5d8e7
|
||||
timeCreated: 1755769401
|
@ -0,0 +1,140 @@
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DDD
|
||||
{
|
||||
/// <summary>
|
||||
/// 인터랙션 타겟을 바라보도록 시각(Spine/애니메이션) 컴포넌트에 위임하는 액션의 껍데기/골격.
|
||||
/// 실제 회전/스파인 제어 로직은 별도의 Visual 컴포넌트(예: Spine/Animation Controller)에서 구현하십시오.
|
||||
/// </summary>
|
||||
public class LookAtInteractionTarget : Action
|
||||
{
|
||||
[Header("Target Settings")]
|
||||
[Tooltip("InteractionPoints를 사용해 가장 적절한 지점을 바라봄")]
|
||||
[SerializeField] private bool useInteractionPoints = true;
|
||||
[Tooltip("타겟이 없을 때 즉시 실패할지 여부")]
|
||||
[SerializeField] private bool failIfNoTarget = true;
|
||||
|
||||
[Header("Update Settings")]
|
||||
[Tooltip("프레임마다 갱신하여 지속적으로 바라볼지 (Running 반환) 여부. 비활성화 시 1회만 시도하고 성공 처리")]
|
||||
[SerializeField] private bool continuousUpdate = true;
|
||||
|
||||
// Visual 전용 컴포넌트(나중 구현)를 위한 최소 인터페이스
|
||||
// 실제 구현은 Spine/애니메이션 제어 컴포넌트에서 이 인터페이스를 구현하세요.
|
||||
public interface ILookAtVisual
|
||||
{
|
||||
// 초기 시작 시도. 성공 여부를 반환할 수 있으나, 본 액션은 성공/실패에 민감하지 않습니다.
|
||||
bool TryBeginLookAt(Vector3 worldPosition);
|
||||
// 매 프레임 갱신 시 호출됩니다.
|
||||
void UpdateLookAt(Vector3 worldPosition);
|
||||
// 액션 종료 시 호출됩니다.
|
||||
void EndLookAt();
|
||||
}
|
||||
|
||||
private ILookAtVisual visual;
|
||||
private GameObject cachedTarget;
|
||||
private bool isLooking;
|
||||
private Vector3 currentLookPosition;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
visual = gameObject.GetComponentInParent<ILookAtVisual>();
|
||||
cachedTarget = null;
|
||||
isLooking = false;
|
||||
}
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
var target = GetTarget();
|
||||
if (target == null)
|
||||
{
|
||||
if (isLooking)
|
||||
{
|
||||
// 타겟이 사라졌다면 정리
|
||||
visual?.EndLookAt();
|
||||
isLooking = false;
|
||||
}
|
||||
return failIfNoTarget ? TaskStatus.Failure : TaskStatus.Success;
|
||||
}
|
||||
|
||||
currentLookPosition = CalculateLookPosition(target);
|
||||
|
||||
if (!isLooking)
|
||||
{
|
||||
visual?.TryBeginLookAt(currentLookPosition);
|
||||
isLooking = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
visual?.UpdateLookAt(currentLookPosition);
|
||||
}
|
||||
|
||||
// 연속 업데이트면 Running, 아니면 1회만 시도 후 Success 반환
|
||||
return continuousUpdate ? TaskStatus.Running : TaskStatus.Success;
|
||||
}
|
||||
|
||||
public override void OnEnd()
|
||||
{
|
||||
if (isLooking)
|
||||
{
|
||||
visual?.EndLookAt();
|
||||
isLooking = false;
|
||||
}
|
||||
cachedTarget = null;
|
||||
}
|
||||
|
||||
private GameObject GetTarget()
|
||||
{
|
||||
// 캐시된 타겟이 유효하면 재사용
|
||||
if (IsValidTarget(cachedTarget))
|
||||
return cachedTarget;
|
||||
|
||||
// 블랙보드에서 타겟 검색
|
||||
cachedTarget = gameObject.GetComponentInParent<IAISharedBlackboard>()
|
||||
?.GetCurrentInteractionTarget();
|
||||
|
||||
if (IsValidTarget(cachedTarget))
|
||||
return cachedTarget;
|
||||
|
||||
// Interactor의 포커스된 타겟 검색
|
||||
var interactor = gameObject.GetComponentInParent<IInteractor>();
|
||||
var focusedInteractable = interactor?.GetFocusedInteractable();
|
||||
cachedTarget = focusedInteractable?.GetInteractableGameObject();
|
||||
|
||||
return cachedTarget;
|
||||
}
|
||||
|
||||
private static bool IsValidTarget(GameObject target) => target != null && target;
|
||||
|
||||
private Vector3 CalculateLookPosition(GameObject target)
|
||||
{
|
||||
if (!useInteractionPoints)
|
||||
return target.transform.position;
|
||||
|
||||
if (target.TryGetComponent<RestaurantInteractionComponent>(out var ric))
|
||||
{
|
||||
var points = ric.GetInteractionPoints();
|
||||
if (points == null || points.Length == 0)
|
||||
return target.transform.position;
|
||||
|
||||
// 가장 가까운 상호작용 지점 선택 (MoveTo와 동일한 기준)
|
||||
var agentPos = transform.position;
|
||||
var nearest = target.transform.position;
|
||||
var minSqr = float.MaxValue;
|
||||
foreach (var p in points)
|
||||
{
|
||||
var d = (p - agentPos).sqrMagnitude;
|
||||
if (d < minSqr)
|
||||
{
|
||||
minSqr = d;
|
||||
nearest = p;
|
||||
}
|
||||
}
|
||||
return nearest;
|
||||
}
|
||||
|
||||
return target.transform.position;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a973b52fbfe64f8981b2a1d33864d2eb
|
||||
timeCreated: 1755771294
|
@ -0,0 +1,173 @@
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DDD
|
||||
{
|
||||
/// <summary>
|
||||
/// IAiMovement를 이용해 인터랙션 타겟으로 이동하는 액션
|
||||
/// </summary>
|
||||
public class MoveToInteractionTarget : Action
|
||||
{
|
||||
[Header("Target Settings")]
|
||||
[Tooltip("InteractionPoints를 사용해 가장 가까운 지점으로 이동")]
|
||||
[SerializeField] private bool useInteractionPoints = true;
|
||||
[Tooltip("타겟이 없을 때 즉시 실패할지 여부")]
|
||||
[SerializeField] private bool failIfNoTarget = true;
|
||||
|
||||
[Header("Movement Settings")]
|
||||
[Tooltip("목적지 도달 거리")]
|
||||
[SerializeField] private float stoppingDistance = 0.01f;
|
||||
[Tooltip("목적지 재계산 주기(초), 0 이하면 비활성화")]
|
||||
[SerializeField] private float repathInterval = 0.5f;
|
||||
|
||||
private IAiMovement movement;
|
||||
private float repathTimer;
|
||||
private Vector3 currentDestination;
|
||||
private bool isMoving;
|
||||
private GameObject cachedTarget;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
if (cachedTarget != null) return;
|
||||
movement = gameObject.GetComponentInParent<IAiMovement>();
|
||||
repathTimer = 0f;
|
||||
isMoving = false;
|
||||
cachedTarget = null;
|
||||
}
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
if (movement == null)
|
||||
return TaskStatus.Failure;
|
||||
|
||||
var target = GetTarget();
|
||||
if (target == null)
|
||||
return failIfNoTarget ? TaskStatus.Failure : TaskStatus.Success;
|
||||
Debug.Log(target.name);
|
||||
|
||||
if (ShouldUpdateDestination())
|
||||
{
|
||||
currentDestination = CalculateDestination(target);
|
||||
StartOrUpdateMovement();
|
||||
}
|
||||
|
||||
return CheckMovementCompletion();
|
||||
}
|
||||
|
||||
public override void OnEnd()
|
||||
{
|
||||
StopMovement();
|
||||
cachedTarget = null;
|
||||
}
|
||||
|
||||
private GameObject GetTarget()
|
||||
{
|
||||
// 캐시된 타겟이 유효하면 재사용
|
||||
if (IsValidTarget(cachedTarget))
|
||||
return cachedTarget;
|
||||
|
||||
// 블랙보드에서 타겟 검색
|
||||
cachedTarget = gameObject.GetComponentInParent<IAISharedBlackboard>()
|
||||
?.GetCurrentInteractionTarget();
|
||||
|
||||
if (IsValidTarget(cachedTarget))
|
||||
return cachedTarget;
|
||||
|
||||
// Interactor의 포커스된 타겟 검색
|
||||
var interactor = gameObject.GetComponentInParent<IInteractor>();
|
||||
var focusedInteractable = interactor?.GetFocusedInteractable();
|
||||
cachedTarget = focusedInteractable?.GetInteractableGameObject();
|
||||
|
||||
return cachedTarget;
|
||||
}
|
||||
|
||||
private bool IsValidTarget(GameObject target) =>
|
||||
target != null && target;
|
||||
|
||||
private bool ShouldUpdateDestination()
|
||||
{
|
||||
repathTimer -= Time.deltaTime;
|
||||
return !isMoving || (repathInterval > 0f && repathTimer <= 0f);
|
||||
}
|
||||
|
||||
private Vector3 CalculateDestination(GameObject target)
|
||||
{
|
||||
repathTimer = repathInterval;
|
||||
|
||||
if (!useInteractionPoints)
|
||||
return target.transform.position;
|
||||
|
||||
return target.TryGetComponent<RestaurantInteractionComponent>(out var ric)
|
||||
? GetNearestInteractionPoint(ric)
|
||||
: target.transform.position;
|
||||
}
|
||||
|
||||
private Vector3 GetNearestInteractionPoint(RestaurantInteractionComponent ric)
|
||||
{
|
||||
var points = ric.GetInteractionPoints();
|
||||
if (points == null || points.Length == 0)
|
||||
return ric.transform.position;
|
||||
|
||||
var agentPosition = GetAgentPosition();
|
||||
var nearestPoint = ric.transform.position;
|
||||
var minDistanceSqr = float.MaxValue;
|
||||
|
||||
foreach (var point in points)
|
||||
{
|
||||
var distanceSqr = (point - agentPosition).sqrMagnitude;
|
||||
if (distanceSqr < minDistanceSqr)
|
||||
{
|
||||
minDistanceSqr = distanceSqr;
|
||||
nearestPoint = point;
|
||||
}
|
||||
}
|
||||
|
||||
return nearestPoint;
|
||||
}
|
||||
|
||||
private void StartOrUpdateMovement()
|
||||
{
|
||||
if (!isMoving)
|
||||
{
|
||||
if (movement.TryMoveToPosition(currentDestination))
|
||||
{
|
||||
movement.EnableMove();
|
||||
movement.PlayMove();
|
||||
isMoving = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
movement.TryMoveToPosition(currentDestination);
|
||||
}
|
||||
}
|
||||
|
||||
private TaskStatus CheckMovementCompletion()
|
||||
{
|
||||
var distanceSqr = (GetAgentPosition() - currentDestination).sqrMagnitude;
|
||||
var stoppingDistanceSqr = stoppingDistance * stoppingDistance;
|
||||
|
||||
if (distanceSqr <= stoppingDistanceSqr || movement.HasReachedDestination())
|
||||
{
|
||||
StopMovement();
|
||||
return TaskStatus.Success;
|
||||
}
|
||||
|
||||
return TaskStatus.Running;
|
||||
}
|
||||
|
||||
private void StopMovement()
|
||||
{
|
||||
if (movement != null && isMoving)
|
||||
{
|
||||
movement.StopMove();
|
||||
movement.DisableMove();
|
||||
isMoving = false;
|
||||
}
|
||||
}
|
||||
|
||||
private Vector3 GetAgentPosition() =>
|
||||
movement?.CurrentPosition ?? transform.position;
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d97cd08353334cb698807d4b526d01b6
|
||||
timeCreated: 1755769413
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f96045235fcc43c880f2e0ee857b6f2e
|
||||
timeCreated: 1756111444
|
@ -0,0 +1,231 @@
|
||||
using Opsive.BehaviorDesigner.Runtime;
|
||||
using Opsive.BehaviorDesigner.Runtime.Components;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Decorators;
|
||||
using Opsive.GraphDesigner.Runtime;
|
||||
using Opsive.Shared.Utility;
|
||||
using Unity.Burst;
|
||||
using Unity.Entities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DDD
|
||||
{
|
||||
[NodeDescription("자식 태스크의 실행 시간을 제한합니다")]
|
||||
public class TimeLimiter : ILogicNode, IParentNode, ITaskComponentData, IDecorator, ISavableTask
|
||||
{
|
||||
[Tooltip("The index of the node.")]
|
||||
[SerializeField, HideInInspector] ushort _Index;
|
||||
|
||||
[Tooltip("The parent index of the node. ushort.MaxValue indicates no parent.")]
|
||||
[SerializeField, HideInInspector] ushort _ParentIndex;
|
||||
|
||||
[Tooltip("The sibling index of the node. ushort.MaxValue indicates no sibling.")]
|
||||
[SerializeField, HideInInspector] ushort _SiblingIndex;
|
||||
|
||||
[Tooltip("최대 실행 시간(초)")]
|
||||
[SerializeField] float _timeLimit = 30.0f;
|
||||
|
||||
[Tooltip("시간 초과 시 반환할 상태")]
|
||||
[SerializeField] private TaskStatus _timeoutStatus = TaskStatus.Failure;
|
||||
|
||||
private ushort _ComponentIndex;
|
||||
|
||||
public ushort Index
|
||||
{
|
||||
get => _Index;
|
||||
set => _Index = value;
|
||||
}
|
||||
|
||||
public ushort ParentIndex
|
||||
{
|
||||
get => _ParentIndex;
|
||||
set => _ParentIndex = value;
|
||||
}
|
||||
|
||||
public ushort SiblingIndex
|
||||
{
|
||||
get => _SiblingIndex;
|
||||
set => _SiblingIndex = value;
|
||||
}
|
||||
|
||||
public ushort RuntimeIndex { get; set; }
|
||||
|
||||
public float TimeLimit
|
||||
{
|
||||
get => _timeLimit;
|
||||
set => _timeLimit = value;
|
||||
}
|
||||
|
||||
public int MaxChildCount
|
||||
{
|
||||
get { return 1; }
|
||||
}
|
||||
|
||||
public ComponentType Tag
|
||||
{
|
||||
get => typeof(TimeLimiterTag);
|
||||
}
|
||||
|
||||
public System.Type SystemType
|
||||
{
|
||||
get => typeof(TimeLimiterTaskSystem);
|
||||
}
|
||||
|
||||
|
||||
public void AddBufferElement(World world, Entity entity)
|
||||
{
|
||||
DynamicBuffer<TimeLimiterComponent> buffer;
|
||||
if (world.EntityManager.HasBuffer<TimeLimiterComponent>(entity))
|
||||
{
|
||||
buffer = world.EntityManager.GetBuffer<TimeLimiterComponent>(entity);
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer = world.EntityManager.AddBuffer<TimeLimiterComponent>(entity);
|
||||
}
|
||||
|
||||
buffer.Add(new TimeLimiterComponent()
|
||||
{
|
||||
Index = RuntimeIndex,
|
||||
TimeLimit = _timeLimit,
|
||||
TimeoutStatus = _timeoutStatus,
|
||||
});
|
||||
_ComponentIndex = (ushort)(buffer.Length - 1);
|
||||
}
|
||||
|
||||
public void ClearBufferElement(World world, Entity entity)
|
||||
{
|
||||
if (world.EntityManager.HasBuffer<TimeLimiterComponent>(entity))
|
||||
{
|
||||
var buffer = world.EntityManager.GetBuffer<TimeLimiterComponent>(entity);
|
||||
buffer.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public MemberVisibility GetSaveReflectionType(int index)
|
||||
{
|
||||
return MemberVisibility.None;
|
||||
}
|
||||
|
||||
public object Save(World world, Entity entity)
|
||||
{
|
||||
var timeLimiterComponents = world.EntityManager.GetBuffer<TimeLimiterComponent>(entity);
|
||||
var timeLimiterComponent = timeLimiterComponents[_ComponentIndex];
|
||||
|
||||
return timeLimiterComponent.StartTime;
|
||||
}
|
||||
|
||||
public void Load(object saveData, World world, Entity entity)
|
||||
{
|
||||
var timeLimiterComponents = world.EntityManager.GetBuffer<TimeLimiterComponent>(entity);
|
||||
var timeLimiterComponent = timeLimiterComponents[_ComponentIndex];
|
||||
|
||||
timeLimiterComponent.StartTime = (float)saveData;
|
||||
timeLimiterComponents[_ComponentIndex] = timeLimiterComponent;
|
||||
}
|
||||
}
|
||||
|
||||
public struct TimeLimiterComponent : IBufferElementData
|
||||
{
|
||||
[Tooltip("The index of the node.")]
|
||||
public ushort Index;
|
||||
|
||||
[Tooltip("최대 실행 시간(초)")]
|
||||
public float TimeLimit;
|
||||
|
||||
[Tooltip("실행 시작 시간(초)")]
|
||||
public float StartTime;
|
||||
|
||||
[Tooltip("Should the task end when the child returns failure?")]
|
||||
public TaskStatus TimeoutStatus;
|
||||
}
|
||||
|
||||
public struct TimeLimiterTag : IComponentData, IEnableableComponent { }
|
||||
|
||||
[DisableAutoCreation]
|
||||
public partial struct TimeLimiterTaskSystem : ISystem
|
||||
{
|
||||
[BurstCompile]
|
||||
private void OnUpdate(ref SystemState state)
|
||||
{
|
||||
var query = SystemAPI.QueryBuilder().WithAllRW<BranchComponent>().WithAllRW<TaskComponent>().WithAllRW<TimeLimiterComponent>().WithAll<TimeLimiterTag, EvaluationComponent>().Build();
|
||||
state.Dependency = new TimeLimiterJob()
|
||||
{
|
||||
CurrentTime = (float)SystemAPI.Time.ElapsedTime
|
||||
}.ScheduleParallel(query, state.Dependency);
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
private partial struct TimeLimiterJob : IJobEntity
|
||||
{
|
||||
public float CurrentTime;
|
||||
|
||||
[BurstCompile]
|
||||
public void Execute(ref DynamicBuffer<BranchComponent> branchComponents,
|
||||
ref DynamicBuffer<TaskComponent> taskComponents,
|
||||
ref DynamicBuffer<TimeLimiterComponent> timeLimiterComponents)
|
||||
{
|
||||
for (int i = 0; i < timeLimiterComponents.Length; ++i)
|
||||
{
|
||||
var timeLimiterComponent = timeLimiterComponents[i];
|
||||
var taskComponent = taskComponents[timeLimiterComponent.Index];
|
||||
var branchComponent = branchComponents[taskComponent.BranchIndex];
|
||||
TaskComponent childTaskComponent;
|
||||
|
||||
if (taskComponent.Status == TaskStatus.Queued)
|
||||
{
|
||||
taskComponent.Status = TaskStatus.Running;
|
||||
taskComponents[taskComponent.Index] = taskComponent;
|
||||
|
||||
timeLimiterComponent.StartTime = CurrentTime;
|
||||
timeLimiterComponents[i] = timeLimiterComponent;
|
||||
|
||||
childTaskComponent = taskComponents[taskComponent.Index + 1];
|
||||
childTaskComponent.Status = TaskStatus.Queued;
|
||||
taskComponents[taskComponent.Index + 1] = childTaskComponent;
|
||||
|
||||
branchComponent.NextIndex = taskComponent.Index + 1;
|
||||
branchComponents[taskComponent.BranchIndex] = branchComponent;
|
||||
continue;
|
||||
}
|
||||
else if (taskComponent.Status != TaskStatus.Running)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (timeLimiterComponent.StartTime >= 0f &&
|
||||
CurrentTime - timeLimiterComponent.StartTime >= timeLimiterComponent.TimeLimit) {
|
||||
// 시간 초과
|
||||
taskComponent.Status = timeLimiterComponent.TimeoutStatus;
|
||||
taskComponents[taskComponent.Index] = taskComponent;
|
||||
|
||||
// 자식 태스크가 실행 중이면 중단
|
||||
childTaskComponent = taskComponents[taskComponent.Index + 1];
|
||||
if (childTaskComponent.Status == TaskStatus.Running ||
|
||||
childTaskComponent.Status == TaskStatus.Queued) {
|
||||
childTaskComponent.Status = timeLimiterComponent.TimeoutStatus;
|
||||
taskComponents[taskComponent.Index + 1] = childTaskComponent;
|
||||
}
|
||||
|
||||
branchComponent.NextIndex = taskComponent.ParentIndex;
|
||||
branchComponents[taskComponent.BranchIndex] = branchComponent;
|
||||
continue;
|
||||
}
|
||||
|
||||
childTaskComponent = taskComponents[taskComponent.Index + 1];
|
||||
if (childTaskComponent.Status == TaskStatus.Queued ||
|
||||
childTaskComponent.Status == TaskStatus.Running) {
|
||||
// The child should keep running.
|
||||
continue;
|
||||
}
|
||||
|
||||
taskComponent.Status = childTaskComponent.Status;
|
||||
taskComponents[taskComponent.Index] = taskComponent;
|
||||
|
||||
branchComponent.NextIndex = taskComponent.ParentIndex;
|
||||
branchComponents[taskComponent.BranchIndex] = branchComponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d5da50af45c8438eb9677fd18378f9b4
|
||||
timeCreated: 1756111556
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6f1cc55df1da4604a2a7f445527710de
|
||||
timeCreated: 1755765247
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6777cf4d7cf4408fafe4bc9097c32b01
|
||||
timeCreated: 1755767888
|
@ -0,0 +1,7 @@
|
||||
namespace DDD
|
||||
{
|
||||
public class ContinueRestaurantOrder
|
||||
{
|
||||
// 이미 있는 인터랙션 타겟을 대상으로 진행함
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d0b954edf40f4172964dbd6e4fe22b1a
|
||||
timeCreated: 1755772289
|
@ -0,0 +1,70 @@
|
||||
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;
|
||||
|
||||
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()
|
||||
{
|
||||
// TODO : 아래 타겟 찾기가 RestaurantOrderAvailable과 동일해야 함, 동일하면 중복될 필요 없으니 스태틱 유틸 함수정도로 만들어서 공유하기.
|
||||
// 레스토랑 주문 인터랙션 후보를 가져옴
|
||||
TaskStatus targetSearchSuccess = RestaurantOrderAvailable.FindAvailableOrderInteractable(_requireCanInteract, _targetOrderType, out var
|
||||
outInteractable);
|
||||
if (targetSearchSuccess == TaskStatus.Failure)
|
||||
{
|
||||
return TaskStatus.Failure;
|
||||
}
|
||||
|
||||
// TODO : 아래 상호작용 수행 로직이 우리 프로젝트의 권장하는 방식이 아님. 플레이어가 오브젝트에 인터랙션하는 것과 비슷한 흐름으로 NPC가 오브젝트에 인터랙션하게 만들 것.
|
||||
// 상호작용 수행: 액션이 붙은 에이전트를 Interactor로 사용
|
||||
if (!_isGetInteractor || !_interactor.CanInteractTo(outInteractable))
|
||||
{
|
||||
return TaskStatus.Failure;
|
||||
}
|
||||
|
||||
// TODO : 이벤트 통해서 인터랙션. 직접 호출하지 말 것!
|
||||
var interacted = outInteractable.OnInteracted(_interactor);
|
||||
if (!interacted)
|
||||
{
|
||||
return TaskStatus.Failure;
|
||||
}
|
||||
|
||||
if (_registerOnBlackboard)
|
||||
{
|
||||
// 공용 블랙보드 우선
|
||||
var shared = gameObject.GetComponentInChildren<IAISharedBlackboard>();
|
||||
if (shared != null)
|
||||
{
|
||||
shared.SetCurrentInteractionTarget(outInteractable.gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 하위 호환: 고객 전용 블랙보드 지원
|
||||
var customerBb = gameObject.GetComponentInParent<IRestaurantCustomerBlackboard>();
|
||||
customerBb?.SetCurrentInteractionTarget(outInteractable.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
return TaskStatus.Success;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84b6c26acd1e41afa6b07ed4c6caf860
|
||||
timeCreated: 1755767930
|
@ -0,0 +1,102 @@
|
||||
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;
|
||||
// }
|
||||
|
||||
if (!gameObject.TryGetComponent<RestaurantInteractionComponent>(out var interactionComponent))
|
||||
{
|
||||
Debug.LogError($"[{GetType().Name}]에서 interactionComponent를 찾을 수 없습니다: {gameObject.name}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (interactionComponent is not IInteractionSubsystemOwner subsystemOwner)
|
||||
{
|
||||
Debug.LogError($"[{GetType().Name}]에서 {nameof(IInteractionSubsystemOwner)}를 찾을 수 없습니다: {gameObject.name}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!subsystemOwner.TryGetSubsystemObject(out _interactionSubsystem))
|
||||
{
|
||||
Debug.LogError($"[{GetType().Name}]에서 {nameof(IInteractionSubsystemObject)}를 찾을 수 없습니다: {gameObject.name}");
|
||||
return;
|
||||
}
|
||||
|
||||
_interactionSubsystem.SetInteractionSubsystemType(_targetOrderType);
|
||||
_isGetInteractionSubsystem = true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4bb97122045148169906d2f7b04a712e
|
||||
timeCreated: 1756171540
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c4f20081a3974c08b60b9efdbe1568a7
|
||||
timeCreated: 1755765256
|
@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
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<T>(bool checkCanInteract, T targetOrderType, out RestaurantInteractionComponent outInteractable) where T : Enum
|
||||
{
|
||||
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<T>(out var subsystem)) continue;
|
||||
|
||||
if (EqualityComparer<T>.Default.Equals(subsystem.GetInteractionSubsystemType(), targetOrderType)
|
||||
)
|
||||
{
|
||||
// CheckCanInteract이 false면 타입만 맞으면 성공
|
||||
if (!checkCanInteract)
|
||||
{
|
||||
return TaskStatus.Success;
|
||||
}
|
||||
|
||||
// CheckCanInteract이 true면 실제 인터랙션 가능 여부까지 확인
|
||||
if (interactable.CanInteract())
|
||||
{
|
||||
return TaskStatus.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TaskStatus.Failure;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bef354fd7cae4273b4cb4e7ff9e270f5
|
||||
timeCreated: 1755765519
|
@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime;
|
||||
using Unity.Entities;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AddressableAssets;
|
||||
|
||||
namespace DDD
|
||||
{
|
||||
[RequireComponent(typeof(BehaviorTree))]
|
||||
[RequireComponent(typeof(RestaurantCustomerBlackboardComponent))]
|
||||
public class RestaurantCustomerAiComponent : MonoBehaviour, IRestaurantCustomerAi
|
||||
{
|
||||
protected BehaviorTree _behaviorTree;
|
||||
protected RestaurantCustomerBlackboardComponent _blackboardComponent;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_behaviorTree = GetComponent<BehaviorTree>();
|
||||
_blackboardComponent = GetComponent<RestaurantCustomerBlackboardComponent>();
|
||||
}
|
||||
|
||||
public void InitializeAi(CustomerData inCustomerData)
|
||||
{
|
||||
try
|
||||
{
|
||||
InitializeAiInternal(inCustomerData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Log
|
||||
Debug.LogError(e);
|
||||
throw; // TODO 예외 처리
|
||||
}
|
||||
}
|
||||
|
||||
private async Task InitializeAiInternal(CustomerData inCustomerData)
|
||||
{
|
||||
var customerState = RestaurantState.Instance.CustomerState;
|
||||
var subtree = customerState.GetLoadedSubtree(inCustomerData.CustomerType);
|
||||
|
||||
if (subtree == null)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"[CustomerCharacter] No preloaded subtree found for CustomerType: {inCustomerData.CustomerType}. Make sure CustomerBehaviorData is loaded.");
|
||||
subtree = await customerState.GetOrLoadSubtree(inCustomerData.CustomerType);
|
||||
}
|
||||
|
||||
_behaviorTree.Subgraph = subtree;
|
||||
_blackboardComponent.InitializeWithBehaviorTree(subtree);
|
||||
_blackboardComponent.SetCustomerData(inCustomerData);
|
||||
// TODO : 1. Subtree - Action, Condition
|
||||
// TODO : 2. Blackboard
|
||||
_behaviorTree.StartBehavior();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: af69e82818254bfa9cabb2dbf9430850
|
||||
timeCreated: 1755748000
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 784c770c13244dc0a0804056065eaf92
|
||||
timeCreated: 1755748886
|
@ -14,10 +14,6 @@ protected virtual void Awake()
|
||||
{
|
||||
_interactionComponent = GetComponent<RestaurantCharacterInteraction>();
|
||||
_spineController = GetComponent<SpineController>();
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
foreach (var typeToSolver in RestaurantInteractionEventSolvers.TypeToSolver)
|
||||
{
|
||||
var flag = typeToSolver.Key;
|
||||
@ -32,6 +28,11 @@ protected virtual void Start()
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public GameObject GetInteractorGameObject()
|
||||
{
|
||||
return _interactionComponent.GetInteractorGameObject();
|
||||
|
@ -0,0 +1,7 @@
|
||||
namespace DDD
|
||||
{
|
||||
public interface IRestaurantCustomerAi
|
||||
{
|
||||
void InitializeAi(CustomerData inCustomerData);
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 308488f2a02448d3853514eff04711fa
|
||||
timeCreated: 1755748296
|
@ -0,0 +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();
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a4f20d91da7045e4bc226be60254ef2b
|
||||
timeCreated: 1755748894
|
@ -2,54 +2,28 @@
|
||||
using Opsive.BehaviorDesigner.Runtime;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AddressableAssets;
|
||||
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||
|
||||
namespace DDD
|
||||
{
|
||||
[RequireComponent(typeof(RestaurantCustomerAiComponent))]
|
||||
public class CustomerCharacter : RestaurantNpcCharacter, ICustomerInitializer
|
||||
{
|
||||
private CustomerData _customerData;
|
||||
|
||||
private AsyncOperationHandle<Subtree> _subtreeHandle;
|
||||
|
||||
public async void Initialize(CustomerData customerData)
|
||||
protected IRestaurantCustomerAi restaurantCustomerAi;
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
restaurantCustomerAi = GetComponent<IRestaurantCustomerAi>();
|
||||
}
|
||||
|
||||
public void Initialize(CustomerData customerData)
|
||||
{
|
||||
_customerData = customerData;
|
||||
restaurantCustomerAi.InitializeAi(_customerData);
|
||||
|
||||
// 스킨 설정
|
||||
_spineController.SetSkin(_customerData.SpineSkinKey);
|
||||
|
||||
// CustomerType에 따른 behavior tree subtree 할당
|
||||
await InitializeBehaviorTree();
|
||||
}
|
||||
|
||||
private async Task InitializeBehaviorTree()
|
||||
{
|
||||
var customerData = RestaurantData.Instance.CustomerData;
|
||||
|
||||
if (customerData?.CustomerBehaviorData?.TryGetValue(_customerData.CustomerType, out var subtreeReference) != true)
|
||||
{
|
||||
Debug.LogError($"[CustomerCharacter] No behavior data found for CustomerType: {_customerData.CustomerType}");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var subtree = await AssetManager.LoadAsset<Subtree>(subtreeReference.AssetGUID);
|
||||
if (subtree != null)
|
||||
{
|
||||
_behaviorTree.Subgraph = subtree;
|
||||
_behaviorTree.StartBehavior();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"[CustomerCharacter] Failed to load subtree for CustomerType: {_customerData.CustomerType}");
|
||||
}
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Debug.LogError($"[CustomerCharacter] Error loading subtree for CustomerType {_customerData.CustomerType}: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,16 +3,12 @@
|
||||
|
||||
namespace DDD
|
||||
{
|
||||
[RequireComponent(typeof(BehaviorTree))]
|
||||
[RequireComponent(typeof(RestaurantNpcMovement))]
|
||||
public class RestaurantNpcCharacter : RestaurantCharacter
|
||||
{
|
||||
protected BehaviorTree _behaviorTree;
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
_behaviorTree = GetComponent<BehaviorTree>();
|
||||
}
|
||||
}
|
||||
}
|
@ -101,6 +101,7 @@ public bool HasReachedDestination()
|
||||
|
||||
public bool IsPositionMovable(Vector3 endPosition)
|
||||
{
|
||||
|
||||
var nearestNode = AstarPath.active.GetNearest(endPosition).node;
|
||||
return nearestNode != null && nearestNode.Walkable;
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace DDD
|
||||
{
|
||||
[RequireComponent(typeof(RestaurantPlayerMovement))]
|
||||
public class RestaurantPlayerCharacter : RestaurantCharacter
|
||||
{
|
||||
protected override async void Awake()
|
||||
|
@ -27,6 +27,9 @@ public override Task InitializeController()
|
||||
{
|
||||
_restaurantCustomerStateSo = RestaurantState.Instance.CustomerState;
|
||||
_restaurantRunStateSo = RestaurantState.Instance.RunState;
|
||||
|
||||
_iCustomerFactory ??= new CustomerFactory();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@ -41,6 +44,9 @@ public override async Task OnReadyNewFlow(GameFlowState newFlowState)
|
||||
{
|
||||
if (newFlowState == GameFlowState.RunRestaurant)
|
||||
{
|
||||
_iCustomerFactory.LoadAssets();
|
||||
await _restaurantCustomerStateSo.LoadCustomerBehaviorData();
|
||||
|
||||
_cts?.Cancel();
|
||||
_cts?.Dispose();
|
||||
_cts = new CancellationTokenSource();
|
||||
@ -52,6 +58,9 @@ public override Task OnExitCurrentFlow(GameFlowState exitingFlowState)
|
||||
{
|
||||
if (exitingFlowState == GameFlowState.RunRestaurant)
|
||||
{
|
||||
_iCustomerFactory.UnloadAssets();
|
||||
_restaurantCustomerStateSo.UnloadCustomerBehaviorData();
|
||||
|
||||
_cts?.Cancel();
|
||||
_cts?.Dispose();
|
||||
_cts = null;
|
||||
@ -61,8 +70,6 @@ public override Task OnExitCurrentFlow(GameFlowState exitingFlowState)
|
||||
|
||||
private async Task StartSpawnLoopAsync(CancellationToken token)
|
||||
{
|
||||
_iCustomerFactory ??= new CustomerFactory();
|
||||
|
||||
var currentGameLevel = GameState.Instance.LevelState.Level;
|
||||
_levelDataSo ??= DataManager.Instance.GetDataSo<LevelDataSo>();
|
||||
_customerDataSo ??= DataManager.Instance.GetDataSo<CustomerDataSo>();
|
||||
@ -115,7 +122,7 @@ SpawnSchedule MakeSchedule() => scheduleBuilder.Build(new SpawnScheduleBuildArgs
|
||||
{
|
||||
var rotation = Quaternion.identity;
|
||||
|
||||
_ = _iCustomerFactory.CreateAsync(new CustomerSpawnArgs
|
||||
await _iCustomerFactory.CreateAsync(new CustomerSpawnArgs
|
||||
{
|
||||
CustomerData = customerData,
|
||||
Position = _restaurantRunStateSo.SpawnPoint,
|
||||
|
@ -1,11 +1,15 @@
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AddressableAssets;
|
||||
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||
|
||||
namespace DDD
|
||||
{
|
||||
public interface ICustomerFactory
|
||||
{
|
||||
Task<GameObject> CreateAsync(CustomerSpawnArgs args);
|
||||
void LoadAssets();
|
||||
void UnloadAssets();
|
||||
}
|
||||
|
||||
public interface ICustomerInitializer
|
||||
@ -24,26 +28,14 @@ public struct CustomerSpawnArgs
|
||||
public class CustomerFactory : ICustomerFactory
|
||||
{
|
||||
private GameObject _customerPrefab;
|
||||
private AsyncOperationHandle<GameObject> _customerPrefabHandle;
|
||||
|
||||
public async Task<GameObject> CreateAsync(CustomerSpawnArgs args)
|
||||
{
|
||||
if (!_customerPrefab)
|
||||
{
|
||||
var customerDataAsset = RestaurantData.Instance ? RestaurantData.Instance.CustomerData : null;
|
||||
if (customerDataAsset == null || customerDataAsset.CustomerPrefab == null)
|
||||
{
|
||||
Debug.LogError("[CustomerFactory] RestaurantCustomerData or its CustomerPrefab reference is not set or not loaded.");
|
||||
return null;
|
||||
}
|
||||
|
||||
var handle = customerDataAsset.CustomerPrefab.LoadAssetAsync<GameObject>();
|
||||
await handle.Task;
|
||||
if (handle.Result == null)
|
||||
{
|
||||
Debug.LogError("[CustomerFactory] Failed to load customer prefab from AssetReference.");
|
||||
return null;
|
||||
}
|
||||
_customerPrefab = handle.Result;
|
||||
Debug.LogError("[CustomerFactory] Customer prefab is not loaded. Call LoadAssets() first.");
|
||||
await LoadCustomerAsset();
|
||||
}
|
||||
|
||||
var newCustomer = Object.Instantiate(_customerPrefab, args.Position, args.Rotation, args.Parent);
|
||||
@ -54,5 +46,47 @@ public async Task<GameObject> CreateAsync(CustomerSpawnArgs args)
|
||||
}
|
||||
return newCustomer;
|
||||
}
|
||||
|
||||
public async void LoadAssets()
|
||||
{
|
||||
await LoadCustomerAsset();
|
||||
}
|
||||
|
||||
private async Task LoadCustomerAsset()
|
||||
{
|
||||
if (_customerPrefab != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var customerDataAsset = RestaurantData.Instance ? RestaurantData.Instance.CustomerData : null;
|
||||
if (customerDataAsset == null || customerDataAsset.CustomerPrefab == null)
|
||||
{
|
||||
Debug.LogError("[CustomerFactory] RestaurantCustomerData or its CustomerPrefab reference is not set.");
|
||||
return;
|
||||
}
|
||||
|
||||
_customerPrefabHandle = customerDataAsset.CustomerPrefab.LoadAssetAsync<GameObject>();
|
||||
await _customerPrefabHandle.Task;
|
||||
|
||||
if (_customerPrefabHandle.Result == null)
|
||||
{
|
||||
Debug.LogError("[CustomerFactory] Failed to load customer prefab from AssetReference.");
|
||||
return;
|
||||
}
|
||||
|
||||
_customerPrefab = _customerPrefabHandle.Result;
|
||||
Debug.Log("[CustomerFactory] Customer prefab loaded successfully.");
|
||||
}
|
||||
|
||||
public void UnloadAssets()
|
||||
{
|
||||
if (_customerPrefabHandle.IsValid())
|
||||
{
|
||||
Addressables.Release(_customerPrefabHandle);
|
||||
}
|
||||
_customerPrefab = null;
|
||||
Debug.Log("[CustomerFactory] Customer prefab unloaded.");
|
||||
}
|
||||
}
|
||||
}
|
@ -37,17 +37,22 @@ public RestaurantManagementType GetInteractionSubsystemType()
|
||||
return _managementType;
|
||||
}
|
||||
|
||||
public virtual void InitializeSubsystem()
|
||||
public void SetInteractionSubsystemType(RestaurantManagementType inValue)
|
||||
{
|
||||
_managementType = inValue;
|
||||
}
|
||||
|
||||
public void InitializeSubsystem()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public virtual bool CanInteract()
|
||||
public bool CanInteract()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool OnInteracted(IInteractor interactor, ScriptableObject payloadSo = null)
|
||||
public bool OnInteracted(IInteractor interactor, ScriptableObject payloadSo = null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -0,0 +1,62 @@
|
||||
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;
|
||||
}
|
||||
|
||||
public ScriptableObject GetPayload()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 31d5c600061a4f05b19824e068e0c2af
|
||||
timeCreated: 1756176676
|
@ -3,13 +3,14 @@
|
||||
|
||||
namespace DDD
|
||||
{
|
||||
[Flags]
|
||||
public enum RestaurantOrderType : uint
|
||||
{
|
||||
Wait = 0u,
|
||||
Reserved = 1u,
|
||||
Order = 1u << 1,
|
||||
Serve = 1u << 2,
|
||||
Busy = 1u << 3,
|
||||
Dirty = 1u << 4,
|
||||
}
|
||||
|
||||
public class RestaurantOrderInteractionSubsystem : MonoBehaviour, IInteractionSubsystemObject<RestaurantOrderType>
|
||||
@ -24,20 +25,18 @@ private void Start()
|
||||
|
||||
public bool CanInteract()
|
||||
{
|
||||
if (GetInteractionSubsystemType() == RestaurantOrderType.Wait)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
//if (GetInteractionSubsystemType() == RestaurantOrderType.Wait)
|
||||
//{
|
||||
// return true;
|
||||
//}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool OnInteracted(IInteractor interactor, ScriptableObject payloadSo = null)
|
||||
{
|
||||
if (GetInteractionSubsystemType() == RestaurantOrderType.Wait)
|
||||
{
|
||||
// DO WAIT CUSTOMER
|
||||
}
|
||||
|
||||
// 간단한 상태 전이: 현재 상태에서 다음 상태로 이동
|
||||
var prev = currentRestaurantOrderType;
|
||||
currentRestaurantOrderType = GetNextState(prev);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -48,12 +47,31 @@ public ScriptableObject GetPayload()
|
||||
|
||||
public void InitializeSubsystem()
|
||||
{
|
||||
|
||||
currentRestaurantOrderType = orderType;
|
||||
}
|
||||
|
||||
public RestaurantOrderType GetInteractionSubsystemType()
|
||||
{
|
||||
return currentRestaurantOrderType;
|
||||
}
|
||||
|
||||
public void SetInteractionSubsystemType(RestaurantOrderType inValue)
|
||||
{
|
||||
currentRestaurantOrderType = inValue;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)}
|
||||
};
|
||||
}
|
||||
|
||||
@ -27,12 +28,35 @@ public class RestaurantInteractionComponent : MonoBehaviour, IInteractable, IInt
|
||||
[SerializeField] protected GameFlowState _interactionAvailableFlows;
|
||||
[SerializeField] private Transform[] _aiInteractionPoints;
|
||||
[SerializeField] private bool autoInitialize = true;
|
||||
private bool _isInitialized = false;
|
||||
|
||||
private Dictionary<InteractionType, IInteractionSubsystemObject> _subsystems = new();
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
// Register this interactable to environment state
|
||||
var environmentState = RestaurantState.Instance?.EnvironmentState;
|
||||
environmentState?.RegisterInteractable(this);
|
||||
|
||||
if (autoInitialize && !_isInitialized)
|
||||
{
|
||||
InitializeInteraction(_interactionType);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
var environmentState = RestaurantState.Instance?.EnvironmentState;
|
||||
environmentState?.UnregisterInteractable(this);
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (autoInitialize)
|
||||
// 보수적으로 Start에서도 등록 시도 (OnEnable 시점에 EnvironmentState가 없었을 경우 대비)
|
||||
var environmentState = RestaurantState.Instance?.EnvironmentState;
|
||||
environmentState?.RegisterInteractable(this);
|
||||
|
||||
if (autoInitialize && !_isInitialized)
|
||||
{
|
||||
InitializeInteraction(_interactionType);
|
||||
}
|
||||
@ -92,6 +116,7 @@ public GameObject GetInteractableGameObject()
|
||||
public virtual void InitializeInteraction(InteractionType interactionType)
|
||||
{
|
||||
_interactionType = interactionType;
|
||||
_isInitialized = true;
|
||||
|
||||
InitializeSubsystems();
|
||||
}
|
||||
|
@ -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)}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -3,20 +3,18 @@
|
||||
|
||||
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) },
|
||||
{ RestaurantManagementType.OpenCookUi, typeof(RestaurantManagementSolver_Cook) },
|
||||
};
|
||||
}
|
||||
public class RestaurantManagementSolver : RestaurantSubsystemSolver<RestaurantManagementType>
|
||||
{
|
||||
protected override Dictionary<RestaurantManagementType, Type> GetSubsystemSolverTypeMappings()
|
||||
{
|
||||
return RestaurantManagementSolvers.TypeToManagementSolver;
|
||||
return _typeToManagementSolver;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 601164c0231c43fca9349170e1e0ccec
|
||||
timeCreated: 1756176395
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 391c551614be4f21a2e700f44569e92a
|
||||
timeCreated: 1756176491
|
@ -0,0 +1,19 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace DDD
|
||||
{
|
||||
public class RestaurantMealSolver_WaitForOrder : MonoBehaviour, IInteractionSubsystemSolver<RestaurantMealType>
|
||||
{
|
||||
public bool ExecuteInteractionSubsystem(IInteractor interactor, IInteractable interactable, ScriptableObject causerPayload = null, ScriptableObject targetPayloadSo = null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanExecuteInteractionSubsystem(IInteractor interactor = null, IInteractable interactable = null,
|
||||
ScriptableObject causerPayload = null, ScriptableObject targetPayloadSo = null)
|
||||
{
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cff2611181194e4a92576bdbcead4fad
|
||||
timeCreated: 1756181225
|
@ -0,0 +1,18 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace DDD
|
||||
{
|
||||
public class RestaurantMealSolver_WaitForServe : MonoBehaviour, IInteractionSubsystemSolver<RestaurantMealType>
|
||||
{
|
||||
public bool ExecuteInteractionSubsystem(IInteractor interactor, IInteractable interactable, ScriptableObject causerPayload = null, ScriptableObject targetPayloadSo = null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanExecuteInteractionSubsystem(IInteractor interactor = null, IInteractable interactable = null,
|
||||
ScriptableObject causerPayload = null, ScriptableObject targetPayloadSo = null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9292616267b4299a3d2e0d29c84f69b
|
||||
timeCreated: 1756181667
|
@ -5,22 +5,20 @@
|
||||
|
||||
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) },
|
||||
{ RestaurantOrderType.Order, typeof(RestaurantOrderSolver_Order) },
|
||||
{ RestaurantOrderType.Serve, typeof(RestaurantOrderSolver_Serve) }
|
||||
{ RestaurantOrderType.Serve, typeof(RestaurantOrderSolver_Serve) },
|
||||
{ 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace DDD.RestaurantOrders
|
||||
{
|
||||
public class RestaurantOrderSolver_Busy : MonoBehaviour, IInteractionSubsystemSolver<RestaurantOrderType>
|
||||
{
|
||||
public bool ExecuteInteractionSubsystem(IInteractor interactor, IInteractable interactable,
|
||||
ScriptableObject causerPayload = null, ScriptableObject targetPayloadSo = null)
|
||||
{
|
||||
// TODO : DO SOMETHING!!!
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanExecuteInteractionSubsystem(IInteractor interactor = null, IInteractable interactable = null,
|
||||
ScriptableObject causerPayload = null, ScriptableObject targetPayloadSo = null)
|
||||
{
|
||||
// TODO : DO SOMETHING!!!
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c185b3957ffe47088703be10e709ff66
|
||||
timeCreated: 1755761370
|
@ -0,0 +1,19 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace DDD.RestaurantOrders
|
||||
{
|
||||
public class RestaurantOrderSolver_Dirty : MonoBehaviour, IInteractionSubsystemSolver<RestaurantOrderType>
|
||||
{
|
||||
public bool ExecuteInteractionSubsystem(IInteractor interactor, IInteractable interactable, ScriptableObject causerPayload = null, ScriptableObject targetPayloadSo = null)
|
||||
{
|
||||
// TODO : DO SOMETHING!!!
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanExecuteInteractionSubsystem(IInteractor interactor = null, IInteractable interactable = null,
|
||||
ScriptableObject causerPayload = null, ScriptableObject targetPayloadSo = null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b97bc6ce36df4e05a4d329f11daef43f
|
||||
timeCreated: 1755761294
|
@ -12,7 +12,8 @@ public bool ExecuteInteractionSubsystem(IInteractor interactor, IInteractable in
|
||||
|
||||
public bool CanExecuteInteractionSubsystem(IInteractor interactor = null, IInteractable interactable = null, ScriptableObject causerPayload = null, ScriptableObject targetPayloadSo = null)
|
||||
{
|
||||
return true;
|
||||
// Interactable's CurrentInteractor is me? => Can execute
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DDD.RestaurantOrders
|
||||
@ -6,7 +7,12 @@ public class RestaurantOrderSolver_Wait : MonoBehaviour, IInteractionSubsystemSo
|
||||
{
|
||||
public bool ExecuteInteractionSubsystem(IInteractor interactor, IInteractable interactable, ScriptableObject causerPayload = null, ScriptableObject targetPayloadSo = null)
|
||||
{
|
||||
if (CanExecuteInteractionSubsystem(interactor, interactable, causerPayload, targetPayloadSo) == false) return false;
|
||||
// TODO : DO SOMETHING!!!
|
||||
/* TODO
|
||||
* OnInteracted에서 상태를 바꾸는 대신, 여기서 직접 바꿔주고, 현재 나에게 점유되어 있다는 사실을 알려주기? 그리고 CanInteractTo에서 이게 일치해야만 참 반환하게?
|
||||
* 필요하다면 IInteractable 인터페이스에 CurrentInteractor를 등록하는 메소드를 추가해야 할수도?
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ public abstract class RestaurantSubsystemSolver<T> : MonoBehaviour, IInteraction
|
||||
|
||||
protected abstract Dictionary<T, Type> GetSubsystemSolverTypeMappings();
|
||||
|
||||
private void Start()
|
||||
private void Awake()
|
||||
{
|
||||
foreach (var subsystemSolverType in GetSubsystemSolverTypeMappings())
|
||||
{
|
||||
|
@ -1,9 +1,106 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AddressableAssets;
|
||||
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||
|
||||
namespace DDD
|
||||
{
|
||||
public class RestaurantCustomerState : ScriptableObject
|
||||
{
|
||||
private Dictionary<CustomerType, Subtree> _loadedSubtrees = new Dictionary<CustomerType, Subtree>();
|
||||
private Dictionary<CustomerType, AsyncOperationHandle<Subtree>> _subtreeHandles = new Dictionary<CustomerType, AsyncOperationHandle<Subtree>>();
|
||||
|
||||
public async Task LoadCustomerBehaviorData()
|
||||
{
|
||||
var customerData = RestaurantData.Instance?.CustomerData;
|
||||
if (customerData?.CustomerBehaviorData == null)
|
||||
{
|
||||
Debug.LogError("[RestaurantCustomerState] RestaurantCustomerData or CustomerBehaviorData is null");
|
||||
return;
|
||||
}
|
||||
|
||||
var loadTasks = new List<Task>();
|
||||
foreach (var behaviorPair in customerData.CustomerBehaviorData)
|
||||
{
|
||||
var customerType = behaviorPair.Key;
|
||||
var subtreeReference = behaviorPair.Value;
|
||||
|
||||
if (_loadedSubtrees.ContainsKey(customerType))
|
||||
continue; // Already loaded
|
||||
|
||||
loadTasks.Add(LoadSubtreeAsync(customerType, subtreeReference));
|
||||
}
|
||||
|
||||
await Task.WhenAll(loadTasks);
|
||||
Debug.Log($"[RestaurantCustomerState] Loaded {_loadedSubtrees.Count} customer behavior subtrees");
|
||||
}
|
||||
|
||||
private async Task LoadSubtreeAsync(CustomerType customerType, AssetReference subtreeReference)
|
||||
{
|
||||
var handle = Addressables.LoadAssetAsync<Subtree>(subtreeReference);
|
||||
_subtreeHandles[customerType] = handle;
|
||||
|
||||
await handle.Task;
|
||||
|
||||
if (handle.Result != null)
|
||||
{
|
||||
_loadedSubtrees[customerType] = handle.Result;
|
||||
Debug.Log($"[RestaurantCustomerState] Loaded subtree for {customerType}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"[RestaurantCustomerState] Failed to load subtree for {customerType}");
|
||||
}
|
||||
}
|
||||
|
||||
public void UnloadCustomerBehaviorData()
|
||||
{
|
||||
foreach (var handle in _subtreeHandles.Values)
|
||||
{
|
||||
if (handle.IsValid())
|
||||
{
|
||||
Addressables.Release(handle);
|
||||
}
|
||||
}
|
||||
|
||||
_loadedSubtrees.Clear();
|
||||
_subtreeHandles.Clear();
|
||||
Debug.Log("[RestaurantCustomerState] Unloaded all customer behavior subtrees");
|
||||
}
|
||||
|
||||
public Subtree GetLoadedSubtree(CustomerType customerType)
|
||||
{
|
||||
_loadedSubtrees.TryGetValue(customerType, out var subtree);
|
||||
return subtree;
|
||||
}
|
||||
|
||||
public async Task<Subtree> GetOrLoadSubtree(CustomerType customerType)
|
||||
{
|
||||
if (IsSubtreeLoaded(customerType))
|
||||
{
|
||||
return GetLoadedSubtree(customerType);
|
||||
}
|
||||
else
|
||||
{
|
||||
var customerData = RestaurantData.Instance?.CustomerData;
|
||||
if (customerData?.CustomerBehaviorData == null ||
|
||||
!customerData.CustomerBehaviorData.TryGetValue(customerType, out var subtreeReference))
|
||||
{
|
||||
Debug.LogError($"[RestaurantCustomerState] No behavior data found for {customerType}");
|
||||
return null;
|
||||
}
|
||||
|
||||
await LoadSubtreeAsync(customerType, subtreeReference);
|
||||
return GetLoadedSubtree(customerType);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSubtreeLoaded(CustomerType customerType)
|
||||
{
|
||||
return _loadedSubtrees.ContainsKey(customerType);
|
||||
}
|
||||
}
|
||||
}
|
@ -21,5 +21,54 @@ public class RestaurantEnvironmentState : ScriptableObject
|
||||
{
|
||||
public List<RestaurantPropLocation> Props = new List<RestaurantPropLocation>();
|
||||
public List<RestaurantPropLocation> Objects = new List<RestaurantPropLocation>();
|
||||
|
||||
// 인터랙션 가능한 객체(IInteractable)를 관리하기 위한 리스트 (런타임 전용)
|
||||
private readonly List<IInteractable> _registeredInteractables = new List<IInteractable>();
|
||||
|
||||
/// <summary>
|
||||
/// 인터랙션 가능한 객체를 등록합니다
|
||||
/// </summary>
|
||||
public void RegisterInteractable(IInteractable interactable)
|
||||
{
|
||||
if (interactable == null) return;
|
||||
if (_registeredInteractables.Contains(interactable)) return;
|
||||
_registeredInteractables.Add(interactable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 인터랙션 가능한 객체를 해제합니다
|
||||
/// </summary>
|
||||
public void UnregisterInteractable(IInteractable interactable)
|
||||
{
|
||||
if (interactable == null) return;
|
||||
_registeredInteractables.Remove(interactable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 InteractionType에 해당하는 인터랙션 객체들을 반환합니다
|
||||
/// </summary>
|
||||
public List<IInteractable> GetInteractablesByType(InteractionType interactionType)
|
||||
{
|
||||
var result = new List<IInteractable>();
|
||||
// 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모든 등록된 인터랙션 객체들을 반환합니다
|
||||
/// </summary>
|
||||
public List<IInteractable> GetAllInteractables()
|
||||
{
|
||||
_registeredInteractables.RemoveAll(item => item == null || (item as UnityEngine.Object) == null);
|
||||
return new List<IInteractable>(_registeredInteractables);
|
||||
}
|
||||
}
|
||||
}
|
BIN
ProjectSettings/EditorBuildSettings.asset
(Stored with Git LFS)
BIN
ProjectSettings/EditorBuildSettings.asset
(Stored with Git LFS)
Binary file not shown.
BIN
ProjectSettings/TagManager.asset
(Stored with Git LFS)
BIN
ProjectSettings/TagManager.asset
(Stored with Git LFS)
Binary file not shown.
Loading…
Reference in New Issue
Block a user