Merge branch 'develop' of http://gitea.capers.co.kr:3000/iwnc2020/ProjectDDD into feature/cook_ui
# Conflicts: # Assets/_DDD/_Scripts/RestaurantEvent/RestaurantInteractionEvents.cs
This commit is contained in:
commit
62a916392d
@ -133,7 +133,7 @@ MonoBehaviour:
|
|||||||
m_Script: {fileID: 11500000, guid: 201f9e6d7ca7404baa9945950292a392, type: 3}
|
m_Script: {fileID: 11500000, guid: 201f9e6d7ca7404baa9945950292a392, type: 3}
|
||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
_interactionType: 4
|
_interactionType: 2
|
||||||
_executionParameters:
|
_executionParameters:
|
||||||
_holdTime: 0
|
_holdTime: 0
|
||||||
_displayParameters:
|
_displayParameters:
|
||||||
@ -141,6 +141,7 @@ MonoBehaviour:
|
|||||||
_interactionAvailableFlows: 2
|
_interactionAvailableFlows: 2
|
||||||
_aiInteractionPoints:
|
_aiInteractionPoints:
|
||||||
- {fileID: 1664322405549350652}
|
- {fileID: 1664322405549350652}
|
||||||
|
autoInitialize: 1
|
||||||
--- !u!114 &4456475204957017828
|
--- !u!114 &4456475204957017828
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
|
@ -136,6 +136,7 @@ MonoBehaviour:
|
|||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
_interactionType: 1
|
_interactionType: 1
|
||||||
_executionParameters:
|
_executionParameters:
|
||||||
|
_holdTime: 0.1
|
||||||
_holdTime: 0.5
|
_holdTime: 0.5
|
||||||
_displayParameters:
|
_displayParameters:
|
||||||
_messageKey: Test
|
_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_Data:
|
||||||
m_TaskData: []
|
m_TaskData: []
|
||||||
m_EventTaskData: []
|
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_DisabledEventNodesData: []
|
||||||
m_DisabledLogicNodesData: []
|
m_DisabledLogicNodesData: []
|
||||||
m_UniqueID: -1885404201
|
m_UniqueID: 1495981264
|
||||||
m_LogicNodePropertiesData: []
|
m_LogicNodePropertiesData: []
|
||||||
m_EventNodePropertiesData: []
|
m_EventNodePropertiesData: []
|
||||||
m_GroupPropertiesData: []
|
m_GroupPropertiesData: []
|
||||||
|
@ -366,6 +366,9 @@ PrefabInstance:
|
|||||||
- targetCorrespondingSourceObject: {fileID: 5259510642736920361, guid: 3db3fc62639929c4ba6031ca4ae6600c, type: 3}
|
- targetCorrespondingSourceObject: {fileID: 5259510642736920361, guid: 3db3fc62639929c4ba6031ca4ae6600c, type: 3}
|
||||||
insertIndex: -1
|
insertIndex: -1
|
||||||
addedObject: {fileID: 8993310060139522557}
|
addedObject: {fileID: 8993310060139522557}
|
||||||
|
- targetCorrespondingSourceObject: {fileID: 5259510642736920361, guid: 3db3fc62639929c4ba6031ca4ae6600c, type: 3}
|
||||||
|
insertIndex: -1
|
||||||
|
addedObject: {fileID: -6848683434426724985}
|
||||||
- targetCorrespondingSourceObject: {fileID: 6791841979869644848, guid: 3db3fc62639929c4ba6031ca4ae6600c, type: 3}
|
- targetCorrespondingSourceObject: {fileID: 6791841979869644848, guid: 3db3fc62639929c4ba6031ca4ae6600c, type: 3}
|
||||||
insertIndex: -1
|
insertIndex: -1
|
||||||
addedObject: {fileID: 662634663174340165}
|
addedObject: {fileID: 662634663174340165}
|
||||||
@ -423,7 +426,7 @@ MonoBehaviour:
|
|||||||
m_Script: {fileID: 11500000, guid: 81e01dd8c1cc3404d805400eba1bb4ae, type: 3}
|
m_Script: {fileID: 11500000, guid: 81e01dd8c1cc3404d805400eba1bb4ae, type: 3}
|
||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
_availableInteractions: 1
|
_availableInteractions: 5
|
||||||
_nearColliders:
|
_nearColliders:
|
||||||
- {fileID: 0}
|
- {fileID: 0}
|
||||||
- {fileID: 0}
|
- {fileID: 0}
|
||||||
@ -461,6 +464,36 @@ MonoBehaviour:
|
|||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
blockOutlineAndGlow: 1
|
blockOutlineAndGlow: 1
|
||||||
blockOverlay: 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
|
--- !u!4 &7511707580127947132 stripped
|
||||||
Transform:
|
Transform:
|
||||||
m_CorrespondingSourceObject: {fileID: 4993183601549197863, guid: 3db3fc62639929c4ba6031ca4ae6600c, type: 3}
|
m_CorrespondingSourceObject: {fileID: 4993183601549197863, guid: 3db3fc62639929c4ba6031ca4ae6600c, type: 3}
|
||||||
|
@ -365,7 +365,8 @@ PrefabInstance:
|
|||||||
propertyPath: m_Name
|
propertyPath: m_Name
|
||||||
value: Prop_CustomerTable_002
|
value: Prop_CustomerTable_002
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
m_RemovedComponents: []
|
m_RemovedComponents:
|
||||||
|
- {fileID: 551358949302262764, guid: 0b1ba2f28535d5147bc0ddf354d0712f, type: 3}
|
||||||
m_RemovedGameObjects: []
|
m_RemovedGameObjects: []
|
||||||
m_AddedGameObjects: []
|
m_AddedGameObjects: []
|
||||||
m_AddedComponents: []
|
m_AddedComponents: []
|
||||||
@ -10722,6 +10723,92 @@ MeshFilter:
|
|||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
m_GameObject: {fileID: 1771012164}
|
m_GameObject: {fileID: 1771012164}
|
||||||
m_Mesh: {fileID: 0}
|
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
|
--- !u!4 &1784230204 stripped
|
||||||
Transform:
|
Transform:
|
||||||
m_CorrespondingSourceObject: {fileID: 1061695247072719575, guid: 70f56d7d65d2e7842b5bd517ae7fe7fe, type: 3}
|
m_CorrespondingSourceObject: {fileID: 1061695247072719575, guid: 70f56d7d65d2e7842b5bd517ae7fe7fe, type: 3}
|
||||||
@ -15146,3 +15233,4 @@ SceneRoots:
|
|||||||
- {fileID: 852575416}
|
- {fileID: 852575416}
|
||||||
- {fileID: 1311760301}
|
- {fileID: 1311760301}
|
||||||
- {fileID: 504708576}
|
- {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
|
public interface IInteractionSubsystemObject<T> : IInteractionSubsystemObject where T : Enum
|
||||||
{
|
{
|
||||||
T GetInteractionSubsystemType();
|
T GetInteractionSubsystemType();
|
||||||
|
void SetInteractionSubsystemType(T inValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IInteractionSubsystemSolver
|
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>();
|
_interactionComponent = GetComponent<RestaurantCharacterInteraction>();
|
||||||
_spineController = GetComponent<SpineController>();
|
_spineController = GetComponent<SpineController>();
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void Start()
|
|
||||||
{
|
|
||||||
foreach (var typeToSolver in RestaurantInteractionEventSolvers.TypeToSolver)
|
foreach (var typeToSolver in RestaurantInteractionEventSolvers.TypeToSolver)
|
||||||
{
|
{
|
||||||
var flag = typeToSolver.Key;
|
var flag = typeToSolver.Key;
|
||||||
@ -32,6 +28,11 @@ protected virtual void Start()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual void Start()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public GameObject GetInteractorGameObject()
|
public GameObject GetInteractorGameObject()
|
||||||
{
|
{
|
||||||
return _interactionComponent.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 Opsive.BehaviorDesigner.Runtime;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.AddressableAssets;
|
using UnityEngine.AddressableAssets;
|
||||||
using UnityEngine.ResourceManagement.AsyncOperations;
|
|
||||||
|
|
||||||
namespace DDD
|
namespace DDD
|
||||||
{
|
{
|
||||||
|
[RequireComponent(typeof(RestaurantCustomerAiComponent))]
|
||||||
public class CustomerCharacter : RestaurantNpcCharacter, ICustomerInitializer
|
public class CustomerCharacter : RestaurantNpcCharacter, ICustomerInitializer
|
||||||
{
|
{
|
||||||
private CustomerData _customerData;
|
private CustomerData _customerData;
|
||||||
|
protected IRestaurantCustomerAi restaurantCustomerAi;
|
||||||
|
|
||||||
private AsyncOperationHandle<Subtree> _subtreeHandle;
|
protected override void Awake()
|
||||||
|
{
|
||||||
|
base.Awake();
|
||||||
|
restaurantCustomerAi = GetComponent<IRestaurantCustomerAi>();
|
||||||
|
}
|
||||||
|
|
||||||
public async void Initialize(CustomerData customerData)
|
public void Initialize(CustomerData customerData)
|
||||||
{
|
{
|
||||||
_customerData = customerData;
|
_customerData = customerData;
|
||||||
|
restaurantCustomerAi.InitializeAi(_customerData);
|
||||||
|
|
||||||
// 스킨 설정
|
// 스킨 설정
|
||||||
_spineController.SetSkin(_customerData.SpineSkinKey);
|
_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
|
namespace DDD
|
||||||
{
|
{
|
||||||
[RequireComponent(typeof(BehaviorTree))]
|
[RequireComponent(typeof(RestaurantNpcMovement))]
|
||||||
public class RestaurantNpcCharacter : RestaurantCharacter
|
public class RestaurantNpcCharacter : RestaurantCharacter
|
||||||
{
|
{
|
||||||
protected BehaviorTree _behaviorTree;
|
|
||||||
|
|
||||||
protected override void Awake()
|
protected override void Awake()
|
||||||
{
|
{
|
||||||
base.Awake();
|
base.Awake();
|
||||||
|
|
||||||
_behaviorTree = GetComponent<BehaviorTree>();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -101,6 +101,7 @@ public bool HasReachedDestination()
|
|||||||
|
|
||||||
public bool IsPositionMovable(Vector3 endPosition)
|
public bool IsPositionMovable(Vector3 endPosition)
|
||||||
{
|
{
|
||||||
|
|
||||||
var nearestNode = AstarPath.active.GetNearest(endPosition).node;
|
var nearestNode = AstarPath.active.GetNearest(endPosition).node;
|
||||||
return nearestNode != null && nearestNode.Walkable;
|
return nearestNode != null && nearestNode.Walkable;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace DDD
|
namespace DDD
|
||||||
{
|
{
|
||||||
|
[RequireComponent(typeof(RestaurantPlayerMovement))]
|
||||||
public class RestaurantPlayerCharacter : RestaurantCharacter
|
public class RestaurantPlayerCharacter : RestaurantCharacter
|
||||||
{
|
{
|
||||||
protected override async void Awake()
|
protected override async void Awake()
|
||||||
|
@ -27,6 +27,9 @@ public override Task InitializeController()
|
|||||||
{
|
{
|
||||||
_restaurantCustomerStateSo = RestaurantState.Instance.CustomerState;
|
_restaurantCustomerStateSo = RestaurantState.Instance.CustomerState;
|
||||||
_restaurantRunStateSo = RestaurantState.Instance.RunState;
|
_restaurantRunStateSo = RestaurantState.Instance.RunState;
|
||||||
|
|
||||||
|
_iCustomerFactory ??= new CustomerFactory();
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,6 +44,9 @@ public override async Task OnReadyNewFlow(GameFlowState newFlowState)
|
|||||||
{
|
{
|
||||||
if (newFlowState == GameFlowState.RunRestaurant)
|
if (newFlowState == GameFlowState.RunRestaurant)
|
||||||
{
|
{
|
||||||
|
_iCustomerFactory.LoadAssets();
|
||||||
|
await _restaurantCustomerStateSo.LoadCustomerBehaviorData();
|
||||||
|
|
||||||
_cts?.Cancel();
|
_cts?.Cancel();
|
||||||
_cts?.Dispose();
|
_cts?.Dispose();
|
||||||
_cts = new CancellationTokenSource();
|
_cts = new CancellationTokenSource();
|
||||||
@ -52,6 +58,9 @@ public override Task OnExitCurrentFlow(GameFlowState exitingFlowState)
|
|||||||
{
|
{
|
||||||
if (exitingFlowState == GameFlowState.RunRestaurant)
|
if (exitingFlowState == GameFlowState.RunRestaurant)
|
||||||
{
|
{
|
||||||
|
_iCustomerFactory.UnloadAssets();
|
||||||
|
_restaurantCustomerStateSo.UnloadCustomerBehaviorData();
|
||||||
|
|
||||||
_cts?.Cancel();
|
_cts?.Cancel();
|
||||||
_cts?.Dispose();
|
_cts?.Dispose();
|
||||||
_cts = null;
|
_cts = null;
|
||||||
@ -61,8 +70,6 @@ public override Task OnExitCurrentFlow(GameFlowState exitingFlowState)
|
|||||||
|
|
||||||
private async Task StartSpawnLoopAsync(CancellationToken token)
|
private async Task StartSpawnLoopAsync(CancellationToken token)
|
||||||
{
|
{
|
||||||
_iCustomerFactory ??= new CustomerFactory();
|
|
||||||
|
|
||||||
var currentGameLevel = GameState.Instance.LevelState.Level;
|
var currentGameLevel = GameState.Instance.LevelState.Level;
|
||||||
_levelDataSo ??= DataManager.Instance.GetDataSo<LevelDataSo>();
|
_levelDataSo ??= DataManager.Instance.GetDataSo<LevelDataSo>();
|
||||||
_customerDataSo ??= DataManager.Instance.GetDataSo<CustomerDataSo>();
|
_customerDataSo ??= DataManager.Instance.GetDataSo<CustomerDataSo>();
|
||||||
@ -115,7 +122,7 @@ SpawnSchedule MakeSchedule() => scheduleBuilder.Build(new SpawnScheduleBuildArgs
|
|||||||
{
|
{
|
||||||
var rotation = Quaternion.identity;
|
var rotation = Quaternion.identity;
|
||||||
|
|
||||||
_ = _iCustomerFactory.CreateAsync(new CustomerSpawnArgs
|
await _iCustomerFactory.CreateAsync(new CustomerSpawnArgs
|
||||||
{
|
{
|
||||||
CustomerData = customerData,
|
CustomerData = customerData,
|
||||||
Position = _restaurantRunStateSo.SpawnPoint,
|
Position = _restaurantRunStateSo.SpawnPoint,
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.AddressableAssets;
|
||||||
|
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||||
|
|
||||||
namespace DDD
|
namespace DDD
|
||||||
{
|
{
|
||||||
public interface ICustomerFactory
|
public interface ICustomerFactory
|
||||||
{
|
{
|
||||||
Task<GameObject> CreateAsync(CustomerSpawnArgs args);
|
Task<GameObject> CreateAsync(CustomerSpawnArgs args);
|
||||||
|
void LoadAssets();
|
||||||
|
void UnloadAssets();
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface ICustomerInitializer
|
public interface ICustomerInitializer
|
||||||
@ -24,26 +28,14 @@ public struct CustomerSpawnArgs
|
|||||||
public class CustomerFactory : ICustomerFactory
|
public class CustomerFactory : ICustomerFactory
|
||||||
{
|
{
|
||||||
private GameObject _customerPrefab;
|
private GameObject _customerPrefab;
|
||||||
|
private AsyncOperationHandle<GameObject> _customerPrefabHandle;
|
||||||
|
|
||||||
public async Task<GameObject> CreateAsync(CustomerSpawnArgs args)
|
public async Task<GameObject> CreateAsync(CustomerSpawnArgs args)
|
||||||
{
|
{
|
||||||
if (!_customerPrefab)
|
if (!_customerPrefab)
|
||||||
{
|
{
|
||||||
var customerDataAsset = RestaurantData.Instance ? RestaurantData.Instance.CustomerData : null;
|
Debug.LogError("[CustomerFactory] Customer prefab is not loaded. Call LoadAssets() first.");
|
||||||
if (customerDataAsset == null || customerDataAsset.CustomerPrefab == null)
|
await LoadCustomerAsset();
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var newCustomer = Object.Instantiate(_customerPrefab, args.Position, args.Rotation, args.Parent);
|
var newCustomer = Object.Instantiate(_customerPrefab, args.Position, args.Rotation, args.Parent);
|
||||||
@ -54,5 +46,47 @@ public async Task<GameObject> CreateAsync(CustomerSpawnArgs args)
|
|||||||
}
|
}
|
||||||
return newCustomer;
|
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.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -19,17 +19,22 @@ public RestaurantManagementType GetInteractionSubsystemType()
|
|||||||
return _managementType;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool OnInteracted(IInteractor interactor, ScriptableObject payloadSo = null)
|
public bool OnInteracted(IInteractor interactor, ScriptableObject payloadSo = null)
|
||||||
{
|
{
|
||||||
return true;
|
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
|
namespace DDD
|
||||||
{
|
{
|
||||||
[Flags]
|
|
||||||
public enum RestaurantOrderType : uint
|
public enum RestaurantOrderType : uint
|
||||||
{
|
{
|
||||||
Wait = 0u,
|
Wait = 0u,
|
||||||
Reserved = 1u,
|
Reserved = 1u,
|
||||||
Order = 1u << 1,
|
Order = 1u << 1,
|
||||||
Serve = 1u << 2,
|
Serve = 1u << 2,
|
||||||
|
Busy = 1u << 3,
|
||||||
|
Dirty = 1u << 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RestaurantOrderInteractionSubsystem : MonoBehaviour, IInteractionSubsystemObject<RestaurantOrderType>
|
public class RestaurantOrderInteractionSubsystem : MonoBehaviour, IInteractionSubsystemObject<RestaurantOrderType>
|
||||||
@ -24,20 +25,18 @@ private void Start()
|
|||||||
|
|
||||||
public bool CanInteract()
|
public bool CanInteract()
|
||||||
{
|
{
|
||||||
if (GetInteractionSubsystemType() == RestaurantOrderType.Wait)
|
//if (GetInteractionSubsystemType() == RestaurantOrderType.Wait)
|
||||||
{
|
//{
|
||||||
|
// return true;
|
||||||
|
//}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool OnInteracted(IInteractor interactor, ScriptableObject payloadSo = null)
|
public bool OnInteracted(IInteractor interactor, ScriptableObject payloadSo = null)
|
||||||
{
|
{
|
||||||
if (GetInteractionSubsystemType() == RestaurantOrderType.Wait)
|
// 간단한 상태 전이: 현재 상태에서 다음 상태로 이동
|
||||||
{
|
var prev = currentRestaurantOrderType;
|
||||||
// DO WAIT CUSTOMER
|
currentRestaurantOrderType = GetNextState(prev);
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,12 +47,31 @@ public ScriptableObject GetPayload()
|
|||||||
|
|
||||||
public void InitializeSubsystem()
|
public void InitializeSubsystem()
|
||||||
{
|
{
|
||||||
|
currentRestaurantOrderType = orderType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RestaurantOrderType GetInteractionSubsystemType()
|
public RestaurantOrderType GetInteractionSubsystemType()
|
||||||
{
|
{
|
||||||
return currentRestaurantOrderType;
|
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()
|
public static Dictionary<InteractionType, Type> TypeToSubsystem = new()
|
||||||
{
|
{
|
||||||
{InteractionType.RestaurantOrder, typeof(RestaurantOrderInteractionSubsystem)},
|
{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] protected GameFlowState _interactionAvailableFlows;
|
||||||
[SerializeField] private Transform[] _aiInteractionPoints;
|
[SerializeField] private Transform[] _aiInteractionPoints;
|
||||||
[SerializeField] private bool autoInitialize = true;
|
[SerializeField] private bool autoInitialize = true;
|
||||||
|
private bool _isInitialized = false;
|
||||||
|
|
||||||
private Dictionary<InteractionType, IInteractionSubsystemObject> _subsystems = new();
|
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()
|
private void Start()
|
||||||
{
|
{
|
||||||
if (autoInitialize)
|
// 보수적으로 Start에서도 등록 시도 (OnEnable 시점에 EnvironmentState가 없었을 경우 대비)
|
||||||
|
var environmentState = RestaurantState.Instance?.EnvironmentState;
|
||||||
|
environmentState?.RegisterInteractable(this);
|
||||||
|
|
||||||
|
if (autoInitialize && !_isInitialized)
|
||||||
{
|
{
|
||||||
InitializeInteraction(_interactionType);
|
InitializeInteraction(_interactionType);
|
||||||
}
|
}
|
||||||
@ -92,6 +116,7 @@ public GameObject GetInteractableGameObject()
|
|||||||
public virtual void InitializeInteraction(InteractionType interactionType)
|
public virtual void InitializeInteraction(InteractionType interactionType)
|
||||||
{
|
{
|
||||||
_interactionType = interactionType;
|
_interactionType = interactionType;
|
||||||
|
_isInitialized = true;
|
||||||
|
|
||||||
InitializeSubsystems();
|
InitializeSubsystems();
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ public static class RestaurantInteractionEventSolvers
|
|||||||
{
|
{
|
||||||
{InteractionType.RestaurantManagement, typeof(RestaurantManagementSolver)},
|
{InteractionType.RestaurantManagement, typeof(RestaurantManagementSolver)},
|
||||||
{InteractionType.RestaurantOrder, typeof(RestaurantOrderSolver)},
|
{InteractionType.RestaurantOrder, typeof(RestaurantOrderSolver)},
|
||||||
|
{InteractionType.RestaurantMeal, typeof(RestaurantMealSolver)},
|
||||||
{InteractionType.RestaurantCook, typeof(RestaurantCookSolver)}
|
{InteractionType.RestaurantCook, typeof(RestaurantCookSolver)}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -3,19 +3,17 @@
|
|||||||
|
|
||||||
namespace DDD
|
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.OpenRestaurantMenu, typeof(RestaurantManagementSolver_Menu) },
|
||||||
{ RestaurantManagementType.StartRestaurant, typeof(RestaurantManagementSolver_Start) },
|
{ RestaurantManagementType.StartRestaurant, typeof(RestaurantManagementSolver_Start) },
|
||||||
};
|
};
|
||||||
}
|
|
||||||
public class RestaurantManagementSolver : RestaurantSubsystemSolver<RestaurantManagementType>
|
|
||||||
{
|
|
||||||
protected override Dictionary<RestaurantManagementType, Type> GetSubsystemSolverTypeMappings()
|
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
|
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.Wait, typeof(RestaurantOrderSolver_Wait) },
|
||||||
{ RestaurantOrderType.Reserved, typeof(RestaurantOrderSolver_Reserved) },
|
{ RestaurantOrderType.Reserved, typeof(RestaurantOrderSolver_Reserved) },
|
||||||
{ RestaurantOrderType.Order, typeof(RestaurantOrderSolver_Order) },
|
{ 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()
|
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)
|
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;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace DDD.RestaurantOrders
|
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)
|
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 : DO SOMETHING!!!
|
||||||
|
/* TODO
|
||||||
|
* OnInteracted에서 상태를 바꾸는 대신, 여기서 직접 바꿔주고, 현재 나에게 점유되어 있다는 사실을 알려주기? 그리고 CanInteractTo에서 이게 일치해야만 참 반환하게?
|
||||||
|
* 필요하다면 IInteractable 인터페이스에 CurrentInteractor를 등록하는 메소드를 추가해야 할수도?
|
||||||
|
*/
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ public abstract class RestaurantSubsystemSolver<T> : MonoBehaviour, IInteraction
|
|||||||
|
|
||||||
protected abstract Dictionary<T, Type> GetSubsystemSolverTypeMappings();
|
protected abstract Dictionary<T, Type> GetSubsystemSolverTypeMappings();
|
||||||
|
|
||||||
private void Start()
|
private void Awake()
|
||||||
{
|
{
|
||||||
foreach (var subsystemSolverType in GetSubsystemSolverTypeMappings())
|
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 Sirenix.OdinInspector;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.AddressableAssets;
|
||||||
|
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||||
|
|
||||||
namespace DDD
|
namespace DDD
|
||||||
{
|
{
|
||||||
public class RestaurantCustomerState : ScriptableObject
|
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> Props = new List<RestaurantPropLocation>();
|
||||||
public List<RestaurantPropLocation> Objects = 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