Merge remote-tracking branch 'origin/develop' into develop

This commit is contained in:
김산 2025-09-02 18:44:45 +09:00
commit 55f7e35d92
85 changed files with 6391 additions and 217 deletions

View File

@ -12,7 +12,7 @@ GameObject:
- component: {fileID: 8265494928291148343} - component: {fileID: 8265494928291148343}
- component: {fileID: 2739769706013484733} - component: {fileID: 2739769706013484733}
- component: {fileID: 5210806482330915924} - component: {fileID: 5210806482330915924}
m_Layer: 0 m_Layer: 7
m_Name: Spine GameObject (Trashcan) m_Name: Spine GameObject (Trashcan)
m_TagString: Untagged m_TagString: Untagged
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
@ -106,7 +106,7 @@ MonoBehaviour:
initialFlipY: 0 initialFlipY: 0
updateWhenInvisible: 3 updateWhenInvisible: 3
separatorSlotNames: [] separatorSlotNames: []
zSpacing: 0 zSpacing: -0.0005
useClipping: 1 useClipping: 1
immutableTriangles: 0 immutableTriangles: 0
pmaVertexColors: 1 pmaVertexColors: 1
@ -138,10 +138,18 @@ PrefabInstance:
serializedVersion: 3 serializedVersion: 3
m_TransformParent: {fileID: 0} m_TransformParent: {fileID: 0}
m_Modifications: m_Modifications:
- target: {fileID: 3406375906160120237, guid: 15c73973805ba914cbcc9929659591d9, type: 3}
propertyPath: m_Layer
value: 7
objectReference: {fileID: 0}
- target: {fileID: 3406375906160120237, guid: 15c73973805ba914cbcc9929659591d9, type: 3} - target: {fileID: 3406375906160120237, guid: 15c73973805ba914cbcc9929659591d9, type: 3}
propertyPath: m_IsActive propertyPath: m_IsActive
value: 0 value: 0
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 3511876579184512741, guid: 15c73973805ba914cbcc9929659591d9, type: 3}
propertyPath: m_Layer
value: 7
objectReference: {fileID: 0}
- target: {fileID: 6689525833630355058, guid: 15c73973805ba914cbcc9929659591d9, type: 3} - target: {fileID: 6689525833630355058, guid: 15c73973805ba914cbcc9929659591d9, type: 3}
propertyPath: m_LocalScale.x propertyPath: m_LocalScale.x
value: 1 value: 1
@ -198,14 +206,293 @@ PrefabInstance:
propertyPath: m_Name propertyPath: m_Name
value: Prop_Trashcan value: Prop_Trashcan
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 9211739394093953175, guid: 15c73973805ba914cbcc9929659591d9, type: 3}
propertyPath: m_Layer
value: 7
objectReference: {fileID: 0}
m_RemovedComponents: [] m_RemovedComponents: []
m_RemovedGameObjects: [] m_RemovedGameObjects: []
m_AddedGameObjects: m_AddedGameObjects:
- targetCorrespondingSourceObject: {fileID: 8881739536043914635, guid: 15c73973805ba914cbcc9929659591d9, type: 3} - targetCorrespondingSourceObject: {fileID: 8881739536043914635, guid: 15c73973805ba914cbcc9929659591d9, type: 3}
insertIndex: 0 insertIndex: 0
addedObject: {fileID: 8327749831302471016} addedObject: {fileID: 8327749831302471016}
m_AddedComponents: [] m_AddedComponents:
- targetCorrespondingSourceObject: {fileID: 9211739394093953175, guid: 15c73973805ba914cbcc9929659591d9, type: 3}
insertIndex: -1
addedObject: {fileID: 1306886132046416555}
- targetCorrespondingSourceObject: {fileID: 9211739394093953175, guid: 15c73973805ba914cbcc9929659591d9, type: 3}
insertIndex: -1
addedObject: {fileID: 7977710813131547754}
- targetCorrespondingSourceObject: {fileID: 9211739394093953175, guid: 15c73973805ba914cbcc9929659591d9, type: 3}
insertIndex: -1
addedObject: {fileID: 3620652486811179709}
m_SourcePrefab: {fileID: 100100000, guid: 15c73973805ba914cbcc9929659591d9, type: 3} m_SourcePrefab: {fileID: 100100000, guid: 15c73973805ba914cbcc9929659591d9, type: 3}
--- !u!1 &6576439486311623297 stripped
GameObject:
m_CorrespondingSourceObject: {fileID: 9211739394093953175, guid: 15c73973805ba914cbcc9929659591d9, type: 3}
m_PrefabInstance: {fileID: 2635336900336278038}
m_PrefabAsset: {fileID: 0}
--- !u!114 &1306886132046416555
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6576439486311623297}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 201f9e6d7ca7404baa9945950292a392, type: 3}
m_Name:
m_EditorClassIdentifier:
_interactionType: 16
_executionParameters:
_holdTime: 1
_displayParameters:
<DefaultMessageKey>k__BackingField:
<ConditionalMessageKey>k__BackingField:
_interactionAvailableFlows: 2
_aiInteractionPoints: []
_autoInitialize: 1
--- !u!114 &7977710813131547754
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6576439486311623297}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 888380afc233049ce9e618f9f36c8ba8, type: 3}
m_Name:
m_EditorClassIdentifier:
profile: {fileID: 0}
profileSync: 0
camerasLayerMask:
serializedVersion: 2
m_Bits: 4294967295
effectGroup: 0
effectTarget: {fileID: 0}
effectGroupLayer:
serializedVersion: 2
m_Bits: 4294967295
effectNameFilter:
effectNameUseRegEx: 0
combineMeshes: 0
alphaCutOff: 0
cullBackFaces: 1
padding: 0
ignoreObjectVisibility: 0
reflectionProbes: 0
GPUInstancing: 1
sortingPriority: 0
optimizeSkinnedMesh: 1
depthClip: 0
cameraDistanceFade: 0
cameraDistanceFadeNear: 0
cameraDistanceFadeFar: 1000
normalsOption: 0
ignore: 0
_highlighted: 0
fadeInDuration: 0
fadeOutDuration: 0
flipY: 0
constantWidth: 1
extraCoveragePixels: 0
minimumWidth: 0
subMeshMask: -1
overlay: 0
overlayMode: 0
overlayColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1}
overlayAnimationSpeed: 1
overlayMinIntensity: 0.5
overlayBlending: 1
overlayTexture: {fileID: 0}
overlayTextureUVSpace: 0
overlayTextureScale: 1
overlayTextureScrolling: {x: 0, y: 0}
overlayVisibility: 0
outline: 1
outlineColor: {r: 0, g: 0, b: 0, a: 1}
outlineColorStyle: 0
outlineGradient:
serializedVersion: 2
key0: {r: 1, g: 1, b: 1, a: 1}
key1: {r: 1, g: 1, b: 1, a: 1}
key2: {r: 0, g: 0, b: 0, a: 0}
key3: {r: 0, g: 0, b: 0, a: 0}
key4: {r: 0, g: 0, b: 0, a: 0}
key5: {r: 0, g: 0, b: 0, a: 0}
key6: {r: 0, g: 0, b: 0, a: 0}
key7: {r: 0, g: 0, b: 0, a: 0}
ctime0: 0
ctime1: 65535
ctime2: 0
ctime3: 0
ctime4: 0
ctime5: 0
ctime6: 0
ctime7: 0
atime0: 0
atime1: 65535
atime2: 0
atime3: 0
atime4: 0
atime5: 0
atime6: 0
atime7: 0
m_Mode: 0
m_ColorSpace: -1
m_NumColorKeys: 2
m_NumAlphaKeys: 2
outlineGradientInLocalSpace: 0
outlineWidth: 0.45
outlineBlurPasses: 2
outlineQuality: 3
outlineEdgeMode: 0
outlineEdgeThreshold: 0.995
outlineSharpness: 1
outlineDownsampling: 1
outlineVisibility: 0
glowBlendMode: 0
outlineBlitDebug: 0
outlineIndependent: 0
outlineContourStyle: 0
outlineMaskMode: 0
glow: 0
glowWidth: 0.4
glowQuality: 3
glowBlurMethod: 0
glowDownsampling: 2
glowHQColor: {r: 0.64, g: 1, b: 0, a: 1}
glowDithering: 1
glowDitheringStyle: 0
glowMagicNumber1: 0.75
glowMagicNumber2: 0.5
glowAnimationSpeed: 1
glowVisibility: 0
glowBlitDebug: 0
glowBlendPasses: 1
glowPasses:
- offset: 4
alpha: 0.1
color: {r: 0.64, g: 1, b: 0, a: 1}
- offset: 3
alpha: 0.2
color: {r: 0.64, g: 1, b: 0, a: 1}
- offset: 2
alpha: 0.3
color: {r: 0.64, g: 1, b: 0, a: 1}
- offset: 1
alpha: 0.4
color: {r: 0.64, g: 1, b: 0, a: 1}
glowMaskMode: 0
innerGlow: 0
innerGlowWidth: 1
innerGlowColor: {r: 1, g: 1, b: 1, a: 1}
innerGlowBlendMode: 0
innerGlowVisibility: 0
targetFX: 0
targetFXTexture: {fileID: 0}
targetFXColor: {r: 1, g: 1, b: 1, a: 1}
targetFXCenter: {fileID: 0}
targetFXRotationSpeed: 50
targetFXInitialScale: 4
targetFXEndScale: 1.5
targetFXScaleToRenderBounds: 1
targetFXUseEnclosingBounds: 0
targetFXAlignToGround: 0
targetFXOffset: {x: 0, y: 0, z: 0}
targetFXFadePower: 32
targetFXGroundMaxDistance: 10
targetFXGroundLayerMask:
serializedVersion: 2
m_Bits: 4294967295
targetFXTransitionDuration: 0.5
targetFXStayDuration: 1.5
targetFXVisibility: 1
iconFX: 0
iconFXMesh: {fileID: 0}
iconFXLightColor: {r: 1, g: 1, b: 1, a: 1}
iconFXDarkColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
iconFXCenter: {fileID: 0}
iconFXRotationSpeed: 50
iconFXAnimationOption: 0
iconFXAnimationAmount: 0.1
iconFXAnimationSpeed: 3
iconFXScale: 1
iconFXScaleToRenderBounds: 0
iconFXOffset: {x: 0, y: 1, z: 0}
iconFXTransitionDuration: 0.5
iconFXStayDuration: 1.5
seeThrough: 2
seeThroughOccluderMask:
serializedVersion: 2
m_Bits: 4294967295
seeThroughOccluderThreshold: 0.3
seeThroughOccluderMaskAccurate: 0
seeThroughOccluderCheckInterval: 1
seeThroughOccluderCheckIndividualObjects: 0
seeThroughDepthOffset: 0
seeThroughMaxDepth: 0
seeThroughIntensity: 0.8
seeThroughTintAlpha: 0.5
seeThroughTintColor: {r: 1, g: 0, b: 0, a: 1}
seeThroughNoise: 1
seeThroughBorder: 0
seeThroughBorderColor: {r: 0, g: 0, b: 0, a: 1}
seeThroughBorderOnly: 0
seeThroughBorderWidth: 0.45
seeThroughOrdered: 0
seeThroughTexture: {fileID: 0}
seeThroughTextureUVSpace: 0
seeThroughTextureScale: 1
seeThroughChildrenSortingMode: 0
rmsCount: 2
hitFxInitialIntensity: 0
hitFxMode: 0
hitFxFadeOutDuration: 0.25
hitFxColor: {r: 1, g: 1, b: 1, a: 1}
hitFxRadius: 0.5
--- !u!114 &3620652486811179709
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6576439486311623297}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f0feb22ab60a4d1885271637838f43b9, type: 3}
m_Name:
m_EditorClassIdentifier:
_availableStyle:
Color: {r: 1, g: 1, b: 1, a: 1}
Width: 1
Opacity: 1
_focusedStyle:
Color: {r: 1, g: 0.92156863, b: 0.015686275, a: 1}
Width: 1
Opacity: 1
_unavailableStyle:
Color: {r: 0.5, g: 0.5, b: 0.5, a: 1}
Width: 0.5
Opacity: 1
_objectiveStyle:
Color: {r: 0, g: 1, b: 1, a: 1}
Width: 1
Opacity: 1
_breathingSpeed: 2
_breathingRange: 0.3
_enableBreathingEffect: 1
_alphaCutOff: 0.5
_combineMeshes: 1
_constantWidth: 1
_outlineQuality: 2
_outlineIndependent: 1
_outlineBlurPasses: 1
_outlineSharpness: 8
_currentOutlineType: 0
_currentOpacityMultiplier: 1
--- !u!4 &6904264511603437469 stripped --- !u!4 &6904264511603437469 stripped
Transform: Transform:
m_CorrespondingSourceObject: {fileID: 8881739536043914635, guid: 15c73973805ba914cbcc9929659591d9, type: 3} m_CorrespondingSourceObject: {fileID: 8881739536043914635, guid: 15c73973805ba914cbcc9929659591d9, type: 3}

View File

@ -46,6 +46,7 @@ GameObject:
- component: {fileID: 3365694194251356714} - component: {fileID: 3365694194251356714}
- component: {fileID: 8736963048629680089} - component: {fileID: 8736963048629680089}
- component: {fileID: 127430239903465757} - component: {fileID: 127430239903465757}
- component: {fileID: 6612028984666579384}
- component: {fileID: 3095965496140440094} - component: {fileID: 3095965496140440094}
- component: {fileID: 7606279200344222219} - component: {fileID: 7606279200344222219}
- component: {fileID: 3805557225565208309} - component: {fileID: 3805557225565208309}
@ -158,6 +159,18 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 38cb67223546879468e9c0655893e025, type: 3} m_Script: {fileID: 11500000, guid: 38cb67223546879468e9c0655893e025, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
--- !u!114 &6612028984666579384
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5259510642736920361}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3af7fa0aab2d45b19f78c34b028732b3, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!114 &3095965496140440094 --- !u!114 &3095965496140440094
MonoBehaviour: MonoBehaviour:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View File

@ -426,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: 15 _availableInteractions: 31
_nearColliders: _nearColliders:
- {fileID: 0} - {fileID: 0}
- {fileID: 0} - {fileID: 0}

Binary file not shown.

View File

@ -2,6 +2,7 @@ namespace DDD.Restaurant
{ {
public interface IMovementConstraint public interface IMovementConstraint
{ {
public bool IsBlockingMovement();
public float GetMovementSpeedMultiplier();
} }
} }

View File

@ -12,6 +12,7 @@ public enum InteractionType : uint
RestaurantManagement = 1u << 0, RestaurantManagement = 1u << 0,
RestaurantOrder = 1u << 1, RestaurantOrder = 1u << 1,
RestaurantCook = 1u << 3, RestaurantCook = 1u << 3,
RestaurantTrash = 1u << 4,
All = 0xFFFFFFFFu All = 0xFFFFFFFFu
} }
@ -58,8 +59,8 @@ public interface IInteractor
bool CanSolveInteractionType(InteractionType interactionType); bool CanSolveInteractionType(InteractionType interactionType);
bool FetchSolverTypeForInteraction(InteractionType type, out Type solverType); bool FetchSolverTypeForInteraction(InteractionType type, out Type solverType);
bool IsInteractionHidden(IInteractable interactable); bool IsInteractionHidden(IInteractable interactable);
bool CanInteractTo(IInteractable interactable, bool CanInteractTo(IInteractable interactable, ScriptableObject payloadSo = null);
ScriptableObject payloadSo = null); bool IsInteracting();
} }
public interface IInteractionSolver public interface IInteractionSolver

View File

@ -1,3 +1,4 @@
using DDD.Restaurant;
using UnityEngine; using UnityEngine;
namespace DDD namespace DDD

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 3ccf6fe11034f47bb919858425c88639
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 19cebae8480a34e3fbbbe68cf80aff6c
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: bdfbe0e1177bb41f7b119e0c091be646
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -22,7 +22,7 @@ public bool TryGetValueByTypeName(string interactionTypeName, string subsystemTy
targetString = $"{interactionTypeName}.{subsystemTypeName}"; targetString = $"{interactionTypeName}.{subsystemTypeName}";
} }
interactionDataEntry = _datas.FirstOrDefault(entry => interactionDataEntry = _datas.FirstOrDefault(entry =>
string.Equals(entry.UnparsedInteractionType, targetString, StringComparison.Ordinal)); string.Equals(entry.Id, targetString, StringComparison.Ordinal));
return interactionDataEntry != null; return interactionDataEntry != null;
} }

View File

@ -12,11 +12,6 @@ public class InteractionDataEntry : IId
[field: SerializeField] [field: SerializeField]
public string Id { get; set; } public string Id { get; set; }
/// <summary>파싱 전 타입</summary>
[Tooltip("파싱 전 타입")]
[field: SerializeField]
public string UnparsedInteractionType;
/// <summary>상호작용 기본 현지화 키 값</summary> /// <summary>상호작용 기본 현지화 키 값</summary>
[Tooltip("상호작용 기본 현지화 키 값")] [Tooltip("상호작용 기본 현지화 키 값")]
[field: SerializeField] [field: SerializeField]
@ -26,5 +21,9 @@ public class InteractionDataEntry : IId
[Tooltip("상호작용 예외처리 현지화 키 값")] [Tooltip("상호작용 예외처리 현지화 키 값")]
[field: SerializeField] [field: SerializeField]
public string ConditionalMessageKey; public string ConditionalMessageKey;
[Tooltip("상호작용 실행 홀드 타임")]
[field: SerializeField]
public float HoldTime;
} }
} }

View File

@ -1760,72 +1760,72 @@
{ {
"Id": "식별ID", "Id": "식별ID",
"#설명": "설명", "#설명": "설명",
"UnparsedInteractionType:string": "파싱 전 타입",
"DefaultMessageKey:string": "상호작용 기본 현지화 키 값", "DefaultMessageKey:string": "상호작용 기본 현지화 키 값",
"ConditionalMessageKey:string": "상호작용 예외처리 현지화 키 값" "ConditionalMessageKey:string": "상호작용 예외처리 현지화 키 값",
"HoldTime:float": ""
}, },
{ {
"Id": "interaction_001", "Id": "RestaurantManagement.OpenManagementUi",
"#설명": "준비단계 - 메뉴 ui 오픈", "#설명": "준비단계 - 메뉴 ui 오픈",
"UnparsedInteractionType:string": "RestaurantManagement.OpenManagementUi",
"DefaultMessageKey:string": "interaction_001_default", "DefaultMessageKey:string": "interaction_001_default",
"ConditionalMessageKey:string": "" "ConditionalMessageKey:string": "",
"HoldTime:float": 0
}, },
{ {
"Id": "interaction_002", "Id": "RestaurantManagement.RunRestaurant",
"#설명": "준비단계 - 레스토랑 오픈", "#설명": "준비단계 - 레스토랑 오픈",
"UnparsedInteractionType:string": "RestaurantManagement.RunRestaurant",
"DefaultMessageKey:string": "interaction_002_default", "DefaultMessageKey:string": "interaction_002_default",
"ConditionalMessageKey:string": "interaction_002_failure" "ConditionalMessageKey:string": "interaction_002_failure",
"HoldTime:float": 1
}, },
{ {
"Id": "interaction_003", "Id": "RestaurantOrder.Order",
"#설명": "운영중 - 손님 주문 받기", "#설명": "운영중 - 손님 주문 받기",
"UnparsedInteractionType:string": "RestaurantOrder.Order",
"DefaultMessageKey:string": "interaction_003_default", "DefaultMessageKey:string": "interaction_003_default",
"ConditionalMessageKey:string": "" "ConditionalMessageKey:string": "",
"HoldTime:float": 0.5
}, },
{ {
"Id": "interaction_004", "Id": "RestaurantOrder.Serve",
"#설명": "운영중 - 요리 서빙하기", "#설명": "운영중 - 요리 서빙하기",
"UnparsedInteractionType:string": "RestaurantOrder.Serve",
"DefaultMessageKey:string": "interaction_004_default", "DefaultMessageKey:string": "interaction_004_default",
"ConditionalMessageKey:string": "" "ConditionalMessageKey:string": "",
"HoldTime:float": 0.5
}, },
{ {
"Id": "interaction_005", "Id": "RestaurantOrder.Dirty",
"#설명": "운영중 - 테이블 치우기", "#설명": "운영중 - 테이블 치우기",
"UnparsedInteractionType:string": "RestaurantOrder.TableDirty",
"DefaultMessageKey:string": "interaction_005_default", "DefaultMessageKey:string": "interaction_005_default",
"ConditionalMessageKey:string": "" "ConditionalMessageKey:string": "",
"HoldTime:float": 1
}, },
{ {
"Id": "interaction_006", "Id": "RestaurantCook.StartCooking",
"#설명": "운영중 - 요리도구를 통해 요리 ui 오픈", "#설명": "운영중 - 요리도구를 통해 요리 ui 오픈",
"UnparsedInteractionType:string": "RestaurantCook.StartCooking",
"DefaultMessageKey:string": "interaction_006_default", "DefaultMessageKey:string": "interaction_006_default",
"ConditionalMessageKey:string": "" "ConditionalMessageKey:string": "",
"HoldTime:float": 0
}, },
{ {
"Id": "interaction_007", "Id": "RestaurantWork.CleaningFloor_TODO",
"#설명": "운영중 - 청소하기", "#설명": "운영중 - 청소하기",
"UnparsedInteractionType:string": "RestaurantOrder.FloorDirty",
"DefaultMessageKey:string": "interaction_007_default", "DefaultMessageKey:string": "interaction_007_default",
"ConditionalMessageKey:string": "" "ConditionalMessageKey:string": "",
"HoldTime:float": 1
}, },
{ {
"Id": "interaction_008", "Id": "RestaurantServe",
"#설명": "운영중 - 음식을 서빙 테이블에 놓기", "#설명": "운영중 - 음식을 서빙 테이블에 놓기",
"UnparsedInteractionType:string": "RestaurantOrder.ServingTable",
"DefaultMessageKey:string": "interaction_008_default", "DefaultMessageKey:string": "interaction_008_default",
"ConditionalMessageKey:string": "" "ConditionalMessageKey:string": "",
"HoldTime:float": 0
}, },
{ {
"Id": "interaction_009", "Id": "RestaurantTrash",
"#설명": "운영중 - 음식과 빈그릇 버리기", "#설명": "운영중 - 음식과 빈그릇 버리기",
"UnparsedInteractionType:string": "RestaurantOrder.Throw",
"DefaultMessageKey:string": "interaction_009_default", "DefaultMessageKey:string": "interaction_009_default",
"ConditionalMessageKey:string": "" "ConditionalMessageKey:string": "",
"HoldTime:float": 1
} }
] ]
} }

View File

@ -0,0 +1,10 @@
using UnityEngine;
namespace DDD.Restaurant
{
public enum CharacterActionState
{
None = 0,
CleaningTable = 1,
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d2e8668bef454e00a5f8b8cd49919645
timeCreated: 1756795869

View File

@ -1,6 +1,6 @@
using UnityEngine; using UnityEngine;
namespace DDD namespace DDD.Restaurant
{ {
[RequireComponent(typeof(SpineController))] [RequireComponent(typeof(SpineController))]
public class CharacterAnimation : MonoBehaviour public class CharacterAnimation : MonoBehaviour
@ -15,6 +15,11 @@ protected virtual void Awake()
protected virtual void Start() protected virtual void Start()
{ {
}
protected virtual void Update()
{
} }
protected virtual void OnDestroy() protected virtual void OnDestroy()

View File

@ -1,6 +1,6 @@
using UnityEngine; using UnityEngine;
namespace DDD namespace DDD.Restaurant
{ {
public enum CarriableType public enum CarriableType
{ {
@ -17,7 +17,7 @@ public interface ICarrier
bool IsCarrying(); bool IsCarrying();
bool CanCarryTo(ICarriable carriable); bool CanCarryTo(ICarriable carriable);
void Carry(ICarriable carriable); void Carry(ICarriable carriable);
void Use(ICarriable carriable); void Use();
} }
public interface ICarriable public interface ICarriable
@ -78,7 +78,7 @@ public void Carry(ICarriable carriable)
EventBus.Broadcast(evt); EventBus.Broadcast(evt);
} }
public void Use(ICarriable carriable) public void Use()
{ {
_currentCarriable = null; _currentCarriable = null;
_speechBubble?.Hide(); _speechBubble?.Hide();

View File

@ -5,7 +5,19 @@
namespace DDD.Restaurant namespace DDD.Restaurant
{ {
public class CharacterInteraction : MonoBehaviour, IInteractor, IEventHandler<RestaurantInteractionEvent> public interface IInteractionStateProvider
{
bool IsInteracting();
float GetMovementSpeedMultiplier();
bool IsMovementBlocked();
}
public interface ICharacterActionStateProvider
{
CharacterActionState GetCurrentActionState();
}
public class CharacterInteraction : MonoBehaviour, IInteractor, IEventHandler<RestaurantInteractionEvent>, IInteractionStateProvider, ICharacterActionStateProvider
{ {
[EnumToggleButtons, SerializeField] protected InteractionType _availableInteractions; [EnumToggleButtons, SerializeField] protected InteractionType _availableInteractions;
[SerializeField, ReadOnly] protected Collider[] _nearColliders = new Collider[10]; [SerializeField, ReadOnly] protected Collider[] _nearColliders = new Collider[10];
@ -56,7 +68,7 @@ public IInteractable GetFocusedInteractable()
return _nearestInteractable; return _nearestInteractable;
} }
private bool TryGetSolverFor(IInteractable interactable, out IInteractionSolver solver) protected bool TryGetSolverFor(IInteractable interactable, out IInteractionSolver solver)
{ {
solver = null; solver = null;
if (interactable == null) return false; if (interactable == null) return false;
@ -64,7 +76,7 @@ private bool TryGetSolverFor(IInteractable interactable, out IInteractionSolver
return TryGetSolverForType(interactable.GetInteractionType(), out solver); return TryGetSolverForType(interactable.GetInteractionType(), out solver);
} }
private bool TryGetSolverForType(InteractionType type, out IInteractionSolver solver) protected bool TryGetSolverForType(InteractionType type, out IInteractionSolver solver)
{ {
if (_cachedSolvers.TryGetValue(type, out solver)) return solver != null; if (_cachedSolvers.TryGetValue(type, out solver)) return solver != null;
@ -142,5 +154,21 @@ protected void InitializeInteractionSolvers(Dictionary<InteractionType, Type> ty
} }
} }
} }
public virtual bool IsInteracting() => _interactingTarget != null;
public virtual float GetMovementSpeedMultiplier() => 1f;
public virtual bool IsMovementBlocked() => false;
public CharacterActionState GetCurrentActionState()
{
if (IsInteracting())
{
if(_nearestInteractable is ICharacterActionStateProvider actionStateProvider)
{
return actionStateProvider.GetCurrentActionState();
}
}
return CharacterActionState.None;
}
} }
} }

View File

@ -1,13 +1,12 @@
using UnityEngine; using UnityEngine;
namespace DDD namespace DDD.Restaurant
{ {
public class CharacterMovement : MonoBehaviour public class CharacterMovement : MonoBehaviour
{ {
private CharacterMovementConstraint _constraint;
protected virtual void Awake() protected virtual void Awake()
{ {
_constraint = gameObject.AddComponent<CharacterMovementConstraint>();
} }
public virtual bool CanMove() public virtual bool CanMove()
@ -24,5 +23,19 @@ public virtual bool CanMove()
} }
return true; return true;
} }
public virtual float GetMovementSpeedMultiplier()
{
var constraints = GetComponents<IMovementConstraint>();
float minMultiplier = 1f;
foreach (var constraint in constraints)
{
float multiplier = constraint.GetMovementSpeedMultiplier();
minMultiplier = Mathf.Min(minMultiplier, multiplier);
}
return minMultiplier;
}
} }
} }

View File

@ -1,18 +1,47 @@
using UnityEngine; using UnityEngine;
namespace DDD namespace DDD.Restaurant
{ {
[RequireComponent(typeof(CharacterAnimation))] [RequireComponent(typeof(CharacterAnimation))]
public class CharacterMovementConstraint : MonoBehaviour, IMovementConstraint public class CharacterMovementConstraint : MonoBehaviour, IMovementConstraint
{ {
private CharacterAnimation _characterAnimation;
private IInteractionStateProvider _interactionStateProvider;
private void Awake()
{
_characterAnimation = GetComponent<CharacterAnimation>();
_interactionStateProvider = GetComponent<IInteractionStateProvider>();
}
public bool IsBlockingMovement() public bool IsBlockingMovement()
{ {
if (GetComponent<CharacterAnimation>().IsPlayingAnimation()) if (_characterAnimation.IsPlayingAnimation())
{
return true;
}
if (_interactionStateProvider != null && _interactionStateProvider.IsMovementBlocked())
{ {
return true; return true;
} }
return false; return false;
} }
public float GetMovementSpeedMultiplier()
{
if (IsBlockingMovement())
{
return 0f;
}
if (_interactionStateProvider != null)
{
return _interactionStateProvider.GetMovementSpeedMultiplier();
}
return 1f;
}
} }
} }

View File

@ -1,12 +1,10 @@
using System;
using Sirenix.OdinInspector;
using UnityEngine; using UnityEngine;
namespace DDD.Restaurant namespace DDD.Restaurant
{ {
[RequireComponent(typeof(CharacterInteraction))] [RequireComponent(typeof(CharacterInteraction))]
[RequireComponent(typeof(SpineController))] [RequireComponent(typeof(SpineController))]
public class RestaurantCharacter : MonoBehaviour, IGameCharacter, IInteractor public class RestaurantCharacter : MonoBehaviour, IGameCharacter
{ {
CharacterInteraction _interactionComponent; CharacterInteraction _interactionComponent;
protected SpineController _spineController; protected SpineController _spineController;
@ -22,35 +20,5 @@ protected virtual void Start()
{ {
} }
public GameObject GetInteractorGameObject()
{
return _interactionComponent.GetInteractorGameObject();
}
public IInteractable GetFocusedInteractable()
{
return _interactionComponent.GetFocusedInteractable();
}
public bool CanSolveInteractionType(InteractionType interactionType)
{
return _interactionComponent.CanSolveInteractionType(interactionType);
}
public bool FetchSolverTypeForInteraction(InteractionType type, out Type solverType)
{
return _interactionComponent.FetchSolverTypeForInteraction(type, out solverType);
}
public bool IsInteractionHidden(IInteractable interactable)
{
return _interactionComponent.IsInteractionHidden(interactable);
}
public bool CanInteractTo(IInteractable interactable, ScriptableObject payloadSo = null)
{
return _interactionComponent.CanInteractTo(interactable, payloadSo);
}
} }
} }

View File

@ -1,7 +1,7 @@
using Pathfinding; using Pathfinding;
using UnityEngine; using UnityEngine;
namespace DDD namespace DDD.Restaurant
{ {
[RequireComponent(typeof(AIPath))] [RequireComponent(typeof(AIPath))]
public class NpcMovement : CharacterMovement, IAiMovement, ICurrentDirection public class NpcMovement : CharacterMovement, IAiMovement, ICurrentDirection

View File

@ -1,48 +1,191 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine; using UnityEngine;
namespace DDD.Restaurant namespace DDD.Restaurant
{ {
public enum PlayerTaskAnimationState
{
None = 0,
Dashing
}
public enum PlayerDefaultAnimationState
{
None = 0,
Idle = 1,
Moving = 2,
}
[RequireComponent(typeof(PlayerMovement))] [RequireComponent(typeof(PlayerMovement))]
public class PlayerAnimation : CharacterAnimation public class PlayerAnimation : CharacterAnimation
{ {
private PlayerMovement _playerMovement; private IInteractor _interactor;
private IMovementState _movementState;
private PlayerTaskAnimationState _currentTaskAnimationState = PlayerTaskAnimationState.None;
private CharacterActionState _currentActionState = CharacterActionState.None;
private PlayerDefaultAnimationState _currentDefaultAnimationState = PlayerDefaultAnimationState.None;
private Dictionary<PlayerTaskAnimationState, string> _taskToAnimation = new()
{
{ PlayerTaskAnimationState.Dashing, "Dash" },
};
private Dictionary<CharacterActionState, string> _actionToAnimation = new()
{
{ CharacterActionState.CleaningTable, "Cleaning/CleaningTable" },
};
private Dictionary<PlayerDefaultAnimationState, string> _defaultToAnimation = new()
{
{ PlayerDefaultAnimationState.Idle, "Idle" },
{ PlayerDefaultAnimationState.Moving, "RunFast" },
};
protected override void Awake() protected override void Awake()
{ {
base.Awake(); base.Awake();
_playerMovement = GetComponent<PlayerMovement>(); _interactor = GetComponent<IInteractor>();
_movementState = GetComponent<IMovementState>();
} }
protected override void Start() protected override void Start()
{ {
base.Start(); base.Start();
_playerMovement.OnMoving += OnMove; InitializeTaskAnimations();
_playerMovement.OnDashing += OnDash;
} }
protected override void Update()
{
UpdateAnimations();
}
protected override void OnDestroy() protected override void OnDestroy()
{ {
base.OnDestroy(); base.OnDestroy();
if (_playerMovement) if (_movementState != null)
{ {
_playerMovement.OnMoving -= OnMove; _movementState.OnDashing -= HandleDashingTask;
_playerMovement.OnDashing -= OnDash;
} }
} }
private void OnMove(bool isMoving) private void InitializeTaskAnimations()
{ {
string animationName = isMoving ? RestaurantPlayerAnimationType.Walk : RestaurantPlayerAnimationType.Idle; _movementState.OnDashing += HandleDashingTask;
_spineController.PlayAnimation(animationName, true);
} }
private void OnDash(float dashTime) private void UpdateAnimations()
{ {
_spineController.PlayAnimationDuration(RestaurantPlayerAnimationType.Dash, false, duration:dashTime); // 1순위: Task 애니메이션이 재생 중이면 다른 애니메이션 처리하지 않음
} if (_currentTaskAnimationState != PlayerTaskAnimationState.None)
{
_currentActionState = CharacterActionState.None;
_currentDefaultAnimationState = PlayerDefaultAnimationState.None;
return;
}
// 2순위: ActionState 체크
var desiredActionState = GetDesiredActionState();
if (desiredActionState == CharacterActionState.None)
{
_currentActionState = CharacterActionState.None;
}
else
{
if (_currentActionState != desiredActionState)
{
_currentDefaultAnimationState = PlayerDefaultAnimationState.None;
_currentActionState = desiredActionState;
PlayActionAnimation(_currentActionState);
}
return;
}
// 3순위: 기본 AnimationState 처리
var desiredADefaultState = GetDesiredADefaultState();
if (_currentDefaultAnimationState != desiredADefaultState)
{
_currentDefaultAnimationState = desiredADefaultState;
PlayStateAnimation(_currentDefaultAnimationState);
}
}
private void HandleDashingTask(float duration)
{
_ = PlayTaskAnimation(PlayerTaskAnimationState.Dashing, duration);
}
private async Task PlayTaskAnimation(PlayerTaskAnimationState state, float duration = 0f)
{
_currentTaskAnimationState = state;
if (_taskToAnimation.TryGetValue(state, out string animationName))
{
if (duration > 0f)
{
_spineController.PlayAnimationDuration(animationName, false, duration);
}
else
{
_spineController.PlayAnimation(animationName, false);
}
}
await Awaitable.WaitForSecondsAsync(duration);
_currentTaskAnimationState = PlayerTaskAnimationState.None;
}
private CharacterActionState GetDesiredActionState()
{
if (_interactor.IsInteracting() == false) return CharacterActionState.None;
if (_interactor is ICharacterActionStateProvider actionStateProvider)
{
return actionStateProvider.GetCurrentActionState();
}
return CharacterActionState.None;
}
private void PlayActionAnimation(CharacterActionState actionState)
{
if (_actionToAnimation.TryGetValue(actionState, out string animationName))
{
_spineController.PlayAnimation(animationName, true);
}
}
private PlayerDefaultAnimationState GetDesiredADefaultState()
{
if (_movementState.IsMoving())
{
return PlayerDefaultAnimationState.Moving;
}
return PlayerDefaultAnimationState.Idle;
}
private void PlayStateAnimation(PlayerDefaultAnimationState state)
{
if (_defaultToAnimation.TryGetValue(state, out string animationName))
{
switch (state)
{
case PlayerDefaultAnimationState.None:
break;
case PlayerDefaultAnimationState.Idle:
case PlayerDefaultAnimationState.Moving:
_spineController.PlayAnimation(animationName, true);
break;
default:
throw new ArgumentOutOfRangeException(nameof(state), state, null);
}
}
}
} }
} }

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine; using UnityEngine;
using UnityEngine.InputSystem; using UnityEngine.InputSystem;
@ -12,6 +11,7 @@ public class PlayerInteraction : CharacterInteraction
private float _interactHeldTime; private float _interactHeldTime;
private bool _isInteracting; private bool _isInteracting;
private float _interactionStartTime = -1f;
protected override void Start() protected override void Start()
{ {
@ -68,7 +68,7 @@ protected virtual void Update()
if (_nearestInteractable != _previousInteractable) if (_nearestInteractable != _previousInteractable)
{ {
_previousInteractable = _nearestInteractable; _previousInteractable = _nearestInteractable;
OnNearestInteractableChanged(_nearestInteractable); BroadcastInteractionUi(_nearestInteractable, CanInteractTo(_nearestInteractable), 0f);
} }
if (_isInteracting) if (_isInteracting)
@ -90,13 +90,17 @@ protected virtual void Update()
OnInteractionCompleted(); OnInteractionCompleted();
ResetInteractionState(); ResetInteractionState();
OnInteractionHoldProgress(0f); BroadcastInteractionUi(_nearestInteractable, CanInteractTo(_nearestInteractable), 0f);
} }
else else
{ {
OnInteractionHoldProgress(ratio); BroadcastInteractionUi(_nearestInteractable, CanInteractTo(_nearestInteractable), ratio);
} }
} }
else
{
BroadcastInteractionUi(_nearestInteractable, CanInteractTo(_nearestInteractable), 0f);
}
} }
@ -120,28 +124,9 @@ private void OnInteractPerformed(InputAction.CallbackContext context)
private void OnInteractCanceled(InputAction.CallbackContext context) private void OnInteractCanceled(InputAction.CallbackContext context)
{ {
OnInteractionHoldProgress(0f); BroadcastInteractionUi(_nearestInteractable, CanInteractTo(_nearestInteractable), 0f);
ResetInteractionState(); ResetInteractionState();
} _interactionStartTime = -1f;
protected void OnNearestInteractableChanged(IInteractable newTarget)
{
if (newTarget != null)
{
BroadcastShowUi(newTarget, CanInteractTo(newTarget), 0f);
}
else
{
EventBus.Broadcast(GameEvents.HideInteractionUiEvent);
}
}
protected void OnInteractionHoldProgress(float ratio)
{
if (_interactingTarget != null)
{
BroadcastShowUi(_interactingTarget, CanInteractTo(_interactingTarget), ratio);
}
} }
protected override void OnInteractionCompleted() protected override void OnInteractionCompleted()
@ -149,8 +134,14 @@ protected override void OnInteractionCompleted()
} }
private void BroadcastShowUi(IInteractable interactable, bool canInteract, float ratio) private void BroadcastInteractionUi(IInteractable interactable, bool canInteract, float ratio)
{ {
if (interactable == null)
{
EventBus.Broadcast(GameEvents.HideInteractionUiEvent);
return;
}
var displayParameters = interactable.GetDisplayParameters(); var displayParameters = interactable.GetDisplayParameters();
var evt = GameEvents.ShowInteractionUiEvent; var evt = GameEvents.ShowInteractionUiEvent;
evt.CanInteract = canInteract; evt.CanInteract = canInteract;
@ -167,7 +158,7 @@ protected void ResetInteractionState()
_isInteracting = false; _isInteracting = false;
_interactingTarget = null; _interactingTarget = null;
_interactHeldTime = 0f; _interactHeldTime = 0f;
OnInteractionHoldProgress(0f); BroadcastInteractionUi(_nearestInteractable, CanInteractTo(_nearestInteractable), 0f);
} }
protected IInteractable GetNearestInteractable() protected IInteractable GetNearestInteractable()
@ -206,5 +197,31 @@ public override bool FetchSolverTypeForInteraction(InteractionType type, out Typ
return base.FetchSolverTypeForInteraction(type, out solverType); return base.FetchSolverTypeForInteraction(type, out solverType);
} }
public override bool IsInteracting()
{
if (base.IsInteracting() == false) return false;
return _isInteracting;
}
public override float GetMovementSpeedMultiplier()
{
if (_isInteracting == false)
{
_interactionStartTime = -1f;
return 1f;
}
if (_interactionStartTime < 0f)
{
_interactionStartTime = Time.time;
}
float elapsed = Time.time - _interactionStartTime;
float normalizedTime = Mathf.Clamp01(elapsed / _restaurantPlayerDataSo.DecelerationTime);
return Mathf.Clamp01(_restaurantPlayerDataSo.InteractionDecelerationCurve.Evaluate(normalizedTime));
}
} }
} }

View File

@ -7,27 +7,32 @@
namespace DDD.Restaurant namespace DDD.Restaurant
{ {
public interface IMovementState
{
bool IsMoving();
bool IsDashing();
Action<float> OnDashing { get; set; }
}
[RequireComponent(typeof(Rigidbody))] [RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(BoxCollider))] [RequireComponent(typeof(BoxCollider))]
public class PlayerMovement : CharacterMovement, ICurrentDirection public class PlayerMovement : CharacterMovement, ICurrentDirection, IMovementState
{ {
#region Fields #region Fields
private Rigidbody _rigidbody; private Rigidbody _rigidbody;
private BoxCollider _boxCollider; private BoxCollider _boxCollider;
private RestaurantPlayerData _playerDataSo;
private Vector3 _inputDirection; private Vector3 _inputDirection;
private Vector3 _currentDirection; private Vector3 _currentDirection;
private Vector3 _currentVelocity; private Vector3 _currentVelocity;
private bool _isInputtedMovement;
private bool _isMoving; private bool _isMoving;
private bool _isDashing; private bool _isDashing;
private bool _isDashCooldown; private bool _isDashCooldown;
private bool _isInitialized;
public Action<float> OnDashing { get; set; }
public Action<bool> OnMoving;
public Action<float> OnDashing;
#if UNITY_EDITOR #if UNITY_EDITOR
private MovementDebugVisualizer _debugVisualizer; private MovementDebugVisualizer _debugVisualizer;
@ -44,15 +49,13 @@ protected override void Awake()
InitializeComponents(); InitializeComponents();
} }
private async void Start() private void Start()
{ {
await InitializePlayerData(); SubscribeToInputEvents();
} }
private void FixedUpdate() private void FixedUpdate()
{ {
if (!_isInitialized) return;
HandleMovement(); HandleMovement();
#if UNITY_EDITOR #if UNITY_EDITOR
@ -69,6 +72,8 @@ private void OnDestroy()
#region Initialization #region Initialization
private RestaurantPlayerData GetPlayerData() => RestaurantData.Instance.PlayerData;
private void InitializeComponents() private void InitializeComponents()
{ {
_rigidbody = GetComponent<Rigidbody>(); _rigidbody = GetComponent<Rigidbody>();
@ -79,46 +84,30 @@ private void InitializeComponents()
#endif #endif
} }
private System.Threading.Tasks.Task InitializePlayerData()
{
try
{
_playerDataSo = RestaurantData.Instance.PlayerData;
SubscribeToInputEvents();
_isInitialized = true;
}
catch (Exception e)
{
Debug.LogError($"Player data load failed\n{e}");
}
return System.Threading.Tasks.Task.CompletedTask;
}
private void SubscribeToInputEvents() private void SubscribeToInputEvents()
{ {
_playerDataSo.MoveAction = InputManager.Instance.GetAction(InputActionMaps.Restaurant, nameof(RestaurantActions.Move)); GetPlayerData().MoveAction = InputManager.Instance.GetAction(InputActionMaps.Restaurant, nameof(RestaurantActions.Move));
_playerDataSo.DashAction = InputManager.Instance.GetAction(InputActionMaps.Restaurant, nameof(RestaurantActions.Dash)); GetPlayerData().DashAction = InputManager.Instance.GetAction(InputActionMaps.Restaurant, nameof(RestaurantActions.Dash));
_playerDataSo.MoveAction.performed += OnMove; GetPlayerData().MoveAction.performed += OnMove;
_playerDataSo.MoveAction.canceled += OnMove; GetPlayerData().MoveAction.canceled += OnMove;
_playerDataSo.DashAction.performed += OnDash; GetPlayerData().DashAction.performed += OnDash;
} }
private void UnsubscribeFromInputEvents() private void UnsubscribeFromInputEvents()
{ {
if (!_playerDataSo) return; if (GetPlayerData() == null) return;
_playerDataSo.MoveAction.performed -= OnMove; GetPlayerData().MoveAction.performed -= OnMove;
_playerDataSo.MoveAction.canceled -= OnMove; GetPlayerData().MoveAction.canceled -= OnMove;
_playerDataSo.DashAction.performed -= OnDash; GetPlayerData().DashAction.performed -= OnDash;
} }
#endregion #endregion
#region Movement #region Movement
private void HandleMovement() private void HandleMovement()
{ {
if (CanMove()) if (CanMove())
{ {
@ -128,7 +117,7 @@ private void HandleMovement()
public override bool CanMove() public override bool CanMove()
{ {
return base.CanMove() && _playerDataSo.IsMoveEnabled && !_isDashing; return base.CanMove() && GetPlayerData().IsMoveEnabled && !_isDashing;
} }
private void Move() private void Move()
@ -141,23 +130,25 @@ private void Move()
private void UpdateMovementState() private void UpdateMovementState()
{ {
_isMoving = _inputDirection != Vector3.zero; _isInputtedMovement = _inputDirection != Vector3.zero;
OnMoving?.Invoke(_isMoving); _isMoving = _isInputtedMovement && _currentVelocity != Vector3.zero;
} }
private void UpdateVelocity() private void UpdateVelocity()
{ {
if (_isMoving) float speedMultiplier = GetMovementSpeedMultiplier();
if (_isInputtedMovement)
{ {
Vector3 slideDirection = GetSlideAdjustedDirection(_inputDirection.normalized); Vector3 slideDirection = GetSlideAdjustedDirection(_inputDirection.normalized);
Vector3 targetVelocity = slideDirection * _playerDataSo.MoveSpeed; Vector3 targetVelocity = slideDirection * (GetPlayerData().MoveSpeed * speedMultiplier);
_currentVelocity = Vector3.MoveTowards(_currentVelocity, targetVelocity, _currentVelocity = Vector3.MoveTowards(_currentVelocity, targetVelocity,
_playerDataSo.Acceleration * Time.fixedDeltaTime); GetPlayerData().Acceleration * Time.fixedDeltaTime);
} }
else else
{ {
_currentVelocity = Vector3.MoveTowards(_currentVelocity, Vector3.zero, _currentVelocity = Vector3.MoveTowards(_currentVelocity, Vector3.zero,
_playerDataSo.Deceleration * Time.fixedDeltaTime); GetPlayerData().Deceleration * Time.fixedDeltaTime);
} }
} }
@ -178,14 +169,14 @@ private void OnDash(InputAction.CallbackContext context)
} }
} }
private bool CanDash() => _playerDataSo.IsDashEnabled && !_isDashing && !_isDashCooldown; private bool CanDash() => GetPlayerData().IsDashEnabled && !_isDashing && !_isDashCooldown;
private IEnumerator DashCoroutine() private IEnumerator DashCoroutine()
{ {
StartDash(); StartDash();
yield return new WaitForSeconds(_playerDataSo.DashTime); yield return new WaitForSeconds(GetPlayerData().DashTime);
EndDash(); EndDash();
yield return new WaitForSeconds(_playerDataSo.DashCooldown); yield return new WaitForSeconds(GetPlayerData().DashCooldown);
ResetDashCooldown(); ResetDashCooldown();
} }
@ -193,10 +184,10 @@ private void StartDash()
{ {
_isDashing = true; _isDashing = true;
_isDashCooldown = true; _isDashCooldown = true;
OnDashing?.Invoke(_playerDataSo.DashTime); OnDashing?.Invoke(GetPlayerData().DashTime);
Vector3 slideDashDirection = GetSlideAdjustedDirection(_currentDirection.normalized); Vector3 slideDashDirection = GetSlideAdjustedDirection(_currentDirection.normalized);
_rigidbody.linearVelocity = slideDashDirection * _playerDataSo.DashSpeed; _rigidbody.linearVelocity = slideDashDirection * GetPlayerData().DashSpeed;
} }
private void EndDash() private void EndDash()
@ -245,7 +236,7 @@ private Vector3 GetSlideAdjustedDirection(Vector3 inputDirection)
Vector3 slide = Vector3.ProjectOnPlane(inputDirection, hit.normal).normalized; Vector3 slide = Vector3.ProjectOnPlane(inputDirection, hit.normal).normalized;
float slideFactor = CalculateSlideFactor(inputDirection, hit.normal); float slideFactor = CalculateSlideFactor(inputDirection, hit.normal);
return slideFactor < _playerDataSo.MinSlideFactorThreshold ? Vector3.zero : slide * slideFactor; return slideFactor < GetPlayerData().MinSlideFactorThreshold ? Vector3.zero : slide * slideFactor;
} }
private bool TryGetCollisionInfo(Vector3 direction, out RaycastHit hit) private bool TryGetCollisionInfo(Vector3 direction, out RaycastHit hit)
@ -253,16 +244,16 @@ private bool TryGetCollisionInfo(Vector3 direction, out RaycastHit hit)
Vector3 origin = _boxCollider.bounds.center; Vector3 origin = _boxCollider.bounds.center;
Vector3 halfExtents = _boxCollider.bounds.extents; Vector3 halfExtents = _boxCollider.bounds.extents;
float distance = Mathf.Min(_boxCollider.bounds.size.x, _boxCollider.bounds.size.z); float distance = Mathf.Min(_boxCollider.bounds.size.x, _boxCollider.bounds.size.z);
int layerMask = ~_playerDataSo.IgnoreSlidingLayerMask; int layerMask = ~GetPlayerData().IgnoreSlidingLayerMask;
return Physics.BoxCast(origin, halfExtents * _playerDataSo.BoxCastExtentScale, return Physics.BoxCast(origin, halfExtents * GetPlayerData().BoxCastExtentScale,
direction, out hit, transform.rotation, distance, layerMask, QueryTriggerInteraction.Ignore); direction, out hit, transform.rotation, distance, layerMask, QueryTriggerInteraction.Ignore);
} }
private float CalculateSlideFactor(Vector3 direction, Vector3 normal) private float CalculateSlideFactor(Vector3 direction, Vector3 normal)
{ {
float dot = Vector3.Dot(direction.normalized, normal); float dot = Vector3.Dot(direction.normalized, normal);
return Mathf.Pow(1f - Mathf.Abs(dot), _playerDataSo.SlidingThreshold); return Mathf.Pow(1f - Mathf.Abs(dot), GetPlayerData().SlidingThreshold);
} }
#endregion #endregion
@ -270,13 +261,22 @@ private float CalculateSlideFactor(Vector3 direction, Vector3 normal)
#if UNITY_EDITOR #if UNITY_EDITOR
private void HandleDebugVisualization() private void HandleDebugVisualization()
{ {
if (_playerDataSo.IsDrawLineDebug) if (GetPlayerData().IsDrawLineDebug)
{ {
_debugVisualizer.UpdateVisualization(transform.position, _inputDirection, _debugVisualizer.UpdateVisualization(transform.position, _inputDirection,
_currentVelocity, _playerDataSo); _currentVelocity, GetPlayerData());
} }
} }
#endif #endif
public bool IsMoving()
{
return _isMoving;
}
public bool IsDashing()
{
return _isDashing;
}
} }
#if UNITY_EDITOR #if UNITY_EDITOR

View File

@ -33,6 +33,9 @@ public class RestaurantPlayerData : ScriptableObject
public float InteractionRadius = 1f; public float InteractionRadius = 1f;
public LayerMask InteractionLayerMask; public LayerMask InteractionLayerMask;
public AnimationCurve InteractionDecelerationCurve;
public float DecelerationTime = 0.3f;
// 디버그 // 디버그
public int InputLineSortingOrder = 10; public int InputLineSortingOrder = 10;
public int VelocityLineSortingOrder = 9; public int VelocityLineSortingOrder = 9;

View File

@ -10,7 +10,7 @@ namespace DDD.Restaurant
{ {
public static class RestaurantInteractionSubsystems public static class RestaurantInteractionSubsystems
{ {
public static Dictionary<InteractionType, Type> TypeToSubsystem = new() public static readonly Dictionary<InteractionType, Type> TypeToSubsystem = new()
{ {
{InteractionType.RestaurantManagement, typeof(InteractionSubsystem_Management)}, {InteractionType.RestaurantManagement, typeof(InteractionSubsystem_Management)},
{InteractionType.RestaurantOrder, typeof(InteractionSubsystem_Order)}, {InteractionType.RestaurantOrder, typeof(InteractionSubsystem_Order)},
@ -18,8 +18,15 @@ public static class RestaurantInteractionSubsystems
}; };
} }
public static class RestaurantInteractionActions
{
public static readonly Dictionary<InteractionType, CharacterActionState> TypeToAction = new()
{
{InteractionType.RestaurantTrash, CharacterActionState.CleaningTable},
};
}
public class RestaurantInteractionComponent : MonoBehaviour, IInteractable, IInteractionSubsystemOwner public class RestaurantInteractionComponent : MonoBehaviour, IInteractable, IInteractionSubsystemOwner, ICharacterActionStateProvider
{ {
// Single interaction type // Single interaction type
[ValueDropdown("GetAllInteractionTypes")] [ValueDropdown("GetAllInteractionTypes")]
@ -134,16 +141,41 @@ private bool TryGetSubsystem(InteractionType interactionType, out IInteractionSu
return _subsystems.TryGetValue(interactionType, out subsystem); return _subsystems.TryGetValue(interactionType, out subsystem);
} }
private bool FindCurrentInteractionDataEntry(out InteractionDataEntry interactionDataEntry)
{
string interactionType = _interactionType.ToString();
string subsystemType = string.Empty;
if (HasSubsystem(_interactionType) && GetSubsystem(_interactionType) != null)
{
subsystemType = GetSubsystem(_interactionType).GetCurrentSubsystemTypeName();
}
bool dataFound = DataManager.Instance.GetDataAsset<InteractionDataAsset>().TryGetValueByTypeName(interactionType,
subsystemType, out interactionDataEntry);
if (!dataFound)
{
interactionDataEntry = new InteractionDataEntry();
}
return dataFound;
}
// 새로운 스트럭트 기반 메서드들 // 새로운 스트럭트 기반 메서드들
public virtual InteractionExecutionParameters GetExecutionParameters() public virtual InteractionExecutionParameters GetExecutionParameters()
{ {
bool dataFound = FindCurrentInteractionDataEntry(out var interactionDataEntry);
if (!dataFound)
{
return new InteractionExecutionParameters();
}
_executionParameters = new InteractionExecutionParameters(interactionDataEntry.HoldTime);
return _executionParameters; return _executionParameters;
} }
public virtual InteractionDisplayParameters GetDisplayParameters() public virtual InteractionDisplayParameters GetDisplayParameters()
{ {
if (DataManager.Instance.GetDataAsset<InteractionDataAsset>().TryGetValueByTypeName(_interactionType.ToString(), bool dataFound = FindCurrentInteractionDataEntry(out var interactionDataEntry);
_subsystems[_interactionType].GetCurrentSubsystemTypeName(), out var interactionDataEntry) == false) if (!dataFound)
{ {
return new InteractionDisplayParameters(); return new InteractionDisplayParameters();
} }
@ -214,5 +246,24 @@ public bool TryGetSubsystemEnumType<T>(out T enumValue) where T : Enum
enumValue = default; enumValue = default;
return false; return false;
} }
public CharacterActionState GetCurrentActionState()
{
if (HasSubsystem(_interactionType))
{
var subsystemObject = GetSubsystem(_interactionType);
if(subsystemObject is ICharacterActionStateProvider actionStateProvider)
{
return actionStateProvider.GetCurrentActionState();
}
}
else
{
return RestaurantInteractionActions.TypeToAction.GetValueOrDefault(_interactionType,
CharacterActionState.None);
}
return CharacterActionState.None;
}
} }
} }

View File

@ -11,7 +11,8 @@ public static class RestaurantInteractionEventSolvers
{ {
{InteractionType.RestaurantManagement, typeof(RestaurantManagementSolver)}, {InteractionType.RestaurantManagement, typeof(RestaurantManagementSolver)},
{InteractionType.RestaurantOrder, typeof(RestaurantOrderSolver)}, {InteractionType.RestaurantOrder, typeof(RestaurantOrderSolver)},
{InteractionType.RestaurantCook, typeof(RestaurantCookSolver)} {InteractionType.RestaurantCook, typeof(RestaurantCookSolver)},
{InteractionType.RestaurantTrash, typeof(RestaurantTrashSolver)}
}; };
public static readonly Dictionary<InteractionType, Type> TypeToPlayerSolver = new() public static readonly Dictionary<InteractionType, Type> TypeToPlayerSolver = new()
{ {

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4cc0ab3a0d5b47e2b46992a16ecbbc5a
timeCreated: 1756798057

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3d48481b0d4746e581513865e89c72e3
timeCreated: 1756788895

View File

@ -0,0 +1,33 @@
using UnityEngine;
namespace DDD.Restaurant
{
public class RestaurantTrashSolver : RestaurantBaseSolver
{
public override bool ExecuteInteraction(IInteractor interactor, IInteractable interactable, ScriptableObject payload = null)
{
var carrier = interactor?.GetInteractorGameObject()?.GetComponent<ICarrier>();
if (carrier == null)
return false;
carrier.Use();
return true;
}
public override bool CanExecuteInteraction(IInteractor interactor = null, IInteractable interactable = null,
ScriptableObject payload = null)
{
// Check carrying state
var carrier = interactor?.GetInteractorGameObject()?.GetComponent<ICarrier>();
if (carrier != null)
return carrier.IsCarrying();
return false;
}
public override bool CanSolveInteraction(IInteractor interactor, IInteractable interactable)
{
return true;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 36efefef94e6497889f41757921d8963
timeCreated: 1756788905

View File

@ -11,6 +11,11 @@ public override bool ExecuteInteractionSubsystem(IInteractor interactor, IIntera
public override bool CanExecuteInteractionSubsystem(IInteractor interactor = null, IInteractable interactable = null, ScriptableObject payload = null) public override bool CanExecuteInteractionSubsystem(IInteractor interactor = null, IInteractable interactable = null, ScriptableObject payload = null)
{ {
// Check carrying state
var carrier = interactor?.GetInteractorGameObject()?.GetComponent<ICarrier>();
if (carrier != null)
return !carrier.IsCarrying();
return true; return true;
} }
} }

View File

@ -18,7 +18,7 @@ public override bool ExecuteInteractionSubsystem(IInteractor interactor, IIntera
bool result = base.ExecuteInteractionSubsystem(interactor, interactable, payload); bool result = base.ExecuteInteractionSubsystem(interactor, interactable, payload);
// ExecuteInteractionSubsystem 이후에 음식 제거 - 미리 제거하면 CanExecute 통과 못 함 // ExecuteInteractionSubsystem 이후에 음식 제거 - 미리 제거하면 CanExecute 통과 못 함
carrier.Use(carrier.GetCurrentCarriable()); carrier.Use();
// OnFoodServed (Consume Bill Hud item) // OnFoodServed (Consume Bill Hud item)
RestaurantOrderEvent evt = new RestaurantOrderEvent RestaurantOrderEvent evt = new RestaurantOrderEvent

View File

@ -2,9 +2,20 @@
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine; using UnityEngine;
namespace DDD namespace DDD.Restaurant
{ {
public abstract class RestaurantSubsystemSolver<T> : MonoBehaviour, IInteractionSolver where T : Enum public abstract class RestaurantBaseSolver : MonoBehaviour, IInteractionSolver
{
public abstract bool ExecuteInteraction(IInteractor interactor, IInteractable interactable,
ScriptableObject payload = null);
public abstract bool CanExecuteInteraction(IInteractor interactor = null, IInteractable interactable = null,
ScriptableObject payload = null);
public abstract bool CanSolveInteraction(IInteractor interactor, IInteractable interactable);
}
public abstract class RestaurantSubsystemSolver<T> : RestaurantBaseSolver where T : Enum
{ {
private Dictionary<T, IInteractionSubsystemSolver<T>> _solvers = new(); private Dictionary<T, IInteractionSubsystemSolver<T>> _solvers = new();
@ -18,19 +29,19 @@ private void Awake()
_solvers.Add(subsystemSolverType.Key, solver); _solvers.Add(subsystemSolverType.Key, solver);
} }
} }
public bool ExecuteInteraction(IInteractor interactor, IInteractable interactable, ScriptableObject payload = null) public override bool ExecuteInteraction(IInteractor interactor, IInteractable interactable, ScriptableObject payload = null)
{ {
return TryGetSolver(interactable, out var solver) && return TryGetSolver(interactable, out var solver) &&
solver.ExecuteInteractionSubsystem(interactor, interactable, payload); solver.ExecuteInteractionSubsystem(interactor, interactable, payload);
} }
public bool CanExecuteInteraction(IInteractor interactor = null, IInteractable interactable = null, ScriptableObject payload = null) public override bool CanExecuteInteraction(IInteractor interactor = null, IInteractable interactable = null, ScriptableObject payload = null)
{ {
return TryGetSolver(interactable, out var solver) && return TryGetSolver(interactable, out var solver) &&
solver.CanExecuteInteractionSubsystem(interactor, interactable, payload); solver.CanExecuteInteractionSubsystem(interactor, interactable, payload);
} }
public bool CanSolveInteraction(IInteractor interactor, IInteractable interactable) public override bool CanSolveInteraction(IInteractor interactor, IInteractable interactable)
{ {
if (interactable is IInteractionSubsystemOwner subsystemOwner) if (interactable is IInteractionSubsystemOwner subsystemOwner)
{ {

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 889bc878e70343cab7416f799f767451
timeCreated: 1756798128

View File

@ -0,0 +1,9 @@
using UnityEngine;
namespace DDD.Restaurant
{
public abstract class InteractionSubsystemBase : MonoBehaviour, ICharacterActionStateProvider
{
public abstract CharacterActionState GetCurrentActionState();
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d9f52d1cd4bd4917a884b6e3bc1152e7
timeCreated: 1756798490

View File

@ -1,7 +1,7 @@
using System; using System;
using UnityEngine; using UnityEngine;
namespace DDD namespace DDD.Restaurant
{ {
[Flags] [Flags]
public enum RestaurantCookType : uint public enum RestaurantCookType : uint
@ -9,7 +9,7 @@ public enum RestaurantCookType : uint
StartCooking = 0, StartCooking = 0,
} }
public class InteractionSubsystem_Cook : MonoBehaviour, IInteractionSubsystemObject<RestaurantCookType> public class InteractionSubsystem_Cook : InteractionSubsystemBase, IInteractionSubsystemObject<RestaurantCookType>
{ {
[SerializeField] protected RestaurantCookType _cookType = RestaurantCookType.StartCooking; [SerializeField] protected RestaurantCookType _cookType = RestaurantCookType.StartCooking;
@ -42,5 +42,10 @@ public string GetCurrentSubsystemTypeName()
{ {
return _cookType.ToString(); return _cookType.ToString();
} }
public override CharacterActionState GetCurrentActionState()
{
return CharacterActionState.None;
}
} }
} }

View File

@ -1,7 +1,7 @@
using System; using System;
using UnityEngine; using UnityEngine;
namespace DDD namespace DDD.Restaurant
{ {
[Flags] [Flags]
public enum RestaurantManagementType : uint public enum RestaurantManagementType : uint
@ -10,7 +10,7 @@ public enum RestaurantManagementType : uint
RunRestaurant = 1, RunRestaurant = 1,
} }
public class InteractionSubsystem_Management : MonoBehaviour, IInteractionSubsystemObject<RestaurantManagementType> public class InteractionSubsystem_Management : InteractionSubsystemBase, IInteractionSubsystemObject<RestaurantManagementType>
{ {
[SerializeField] protected RestaurantManagementType _managementType = RestaurantManagementType.OpenManagementUi; [SerializeField] protected RestaurantManagementType _managementType = RestaurantManagementType.OpenManagementUi;
@ -48,5 +48,10 @@ public ScriptableObject GetPayload()
{ {
return null; return null;
} }
public override CharacterActionState GetCurrentActionState()
{
return CharacterActionState.None;
}
} }
} }

View File

@ -1,4 +1,6 @@
using System; using System;
using System.Collections.Generic;
using Sirenix.Serialization;
using UnityEngine; using UnityEngine;
using UnityEngine.Serialization; using UnityEngine.Serialization;
@ -45,11 +47,11 @@ public interface IRestaurantOrderObject
// TODO : 도중에 이탈할 경우, 순차적으로 다음 페이즈로 넘어가는 게 아니라 바로 Wait, Dirty로 전이시킬 필요가 있음 // TODO : 도중에 이탈할 경우, 순차적으로 다음 페이즈로 넘어가는 게 아니라 바로 Wait, Dirty로 전이시킬 필요가 있음
public class InteractionSubsystem_Order : MonoBehaviour, IInteractionSubsystemObject<RestaurantOrderType>, IRestaurantOrderObject, IEventHandler<RestaurantOrderInterrupt> public class InteractionSubsystem_Order : InteractionSubsystemBase, IInteractionSubsystemObject<RestaurantOrderType>, IRestaurantOrderObject, IEventHandler<RestaurantOrderInterrupt>
{ {
[SerializeField] private RestaurantOrderType _currentRestaurantOrderType = RestaurantOrderType.Wait; [SerializeField] private RestaurantOrderType _currentRestaurantOrderType = RestaurantOrderType.Wait;
[SerializeField] private RestaurantOrderObjectState _orderObjectState = new(); [SerializeField] private RestaurantOrderObjectState _orderObjectState = new();
public bool CanInteract() public bool CanInteract()
{ {
if (GetInteractionSubsystemType() == RestaurantOrderType.Wait) if (GetInteractionSubsystemType() == RestaurantOrderType.Wait)
@ -145,5 +147,15 @@ private void OnDestroy()
{ {
EventBus.Unregister(this); EventBus.Unregister(this);
} }
public override CharacterActionState GetCurrentActionState()
{
if (_currentRestaurantOrderType == RestaurantOrderType.Dirty)
{
return CharacterActionState.CleaningTable;
}
return CharacterActionState.None;
}
} }
} }

View File

@ -92,9 +92,8 @@ protected override int GetDisplayLayer()
} }
else else
{ {
return LayerMask.NameToLayer(LayerConstants.WorldUi); return base.GetDisplayLayer();
} }
return base.GetDisplayLayer();
} }
} }
} }

View File

@ -10,13 +10,6 @@ public static class CommonConstants
public const string BlockImage = "BlockImage"; public const string BlockImage = "BlockImage";
} }
public static class RestaurantPlayerAnimationType
{
public const string Idle = "Idle";
public const string Walk = "RunFast";
public const string Dash = "Dash";
}
public static class PathConstants public static class PathConstants
{ {
public const string RawSpritesPathUpper = "ASSETS/_DDD/_RAW/SPRITES/"; public const string RawSpritesPathUpper = "ASSETS/_DDD/_RAW/SPRITES/";