feat: 플레이어 다운 및 넉백 피격 반응 추가
- HitReactionController로 다운과 넉백 전용 로직을 분리 - 다운 시작, 루프, 회복 애니메이션과 DownEffect를 연결 - 행동 상태와 스킬 취소가 피격 반응과 연동되도록 정리 - 자동 검증 러너에 다운 및 넉백 검증을 추가
This commit is contained in:
@@ -2491,15 +2491,15 @@ PrefabInstance:
|
|||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 170188453522781500, guid: 54087b4bd46db9e4fb7da13cf7a6cc69, type: 3}
|
- target: {fileID: 170188453522781500, guid: 54087b4bd46db9e4fb7da13cf7a6cc69, type: 3}
|
||||||
propertyPath: m_AnchorMax.x
|
propertyPath: m_AnchorMax.x
|
||||||
value: 0
|
value: 0.2
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 170188453522781500, guid: 54087b4bd46db9e4fb7da13cf7a6cc69, type: 3}
|
- target: {fileID: 170188453522781500, guid: 54087b4bd46db9e4fb7da13cf7a6cc69, type: 3}
|
||||||
propertyPath: m_AnchorMax.y
|
propertyPath: m_AnchorMax.y
|
||||||
value: 0
|
value: 1
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 170188453522781500, guid: 54087b4bd46db9e4fb7da13cf7a6cc69, type: 3}
|
- target: {fileID: 170188453522781500, guid: 54087b4bd46db9e4fb7da13cf7a6cc69, type: 3}
|
||||||
propertyPath: m_AnchorMin.x
|
propertyPath: m_AnchorMin.x
|
||||||
value: 0
|
value: 0.2
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 222507439395443271, guid: 54087b4bd46db9e4fb7da13cf7a6cc69, type: 3}
|
- target: {fileID: 222507439395443271, guid: 54087b4bd46db9e4fb7da13cf7a6cc69, type: 3}
|
||||||
propertyPath: m_Enabled
|
propertyPath: m_Enabled
|
||||||
@@ -2579,15 +2579,15 @@ PrefabInstance:
|
|||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 2668790415109567114, guid: 54087b4bd46db9e4fb7da13cf7a6cc69, type: 3}
|
- target: {fileID: 2668790415109567114, guid: 54087b4bd46db9e4fb7da13cf7a6cc69, type: 3}
|
||||||
propertyPath: m_AnchorMax.x
|
propertyPath: m_AnchorMax.x
|
||||||
value: 0
|
value: 0.2
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 2668790415109567114, guid: 54087b4bd46db9e4fb7da13cf7a6cc69, type: 3}
|
- target: {fileID: 2668790415109567114, guid: 54087b4bd46db9e4fb7da13cf7a6cc69, type: 3}
|
||||||
propertyPath: m_AnchorMax.y
|
propertyPath: m_AnchorMax.y
|
||||||
value: 0
|
value: 1
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 2668790415109567114, guid: 54087b4bd46db9e4fb7da13cf7a6cc69, type: 3}
|
- target: {fileID: 2668790415109567114, guid: 54087b4bd46db9e4fb7da13cf7a6cc69, type: 3}
|
||||||
propertyPath: m_AnchorMin.x
|
propertyPath: m_AnchorMin.x
|
||||||
value: 0
|
value: 0.2
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 3299919758736932218, guid: 54087b4bd46db9e4fb7da13cf7a6cc69, type: 3}
|
- target: {fileID: 3299919758736932218, guid: 54087b4bd46db9e4fb7da13cf7a6cc69, type: 3}
|
||||||
propertyPath: m_SizeDelta.x
|
propertyPath: m_SizeDelta.x
|
||||||
@@ -4738,7 +4738,7 @@ MonoBehaviour:
|
|||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.UI.ConnectionUI
|
m_EditorClassIdentifier: Colosseum.Game::Colosseum.UI.ConnectionUI
|
||||||
ipAddress: 127.0.0.1
|
ipAddress: 127.0.0.1
|
||||||
port: 7788
|
port: 7777
|
||||||
autoStartHostInEditor: 0
|
autoStartHostInEditor: 0
|
||||||
connectionStatus: Disconnected
|
connectionStatus: Disconnected
|
||||||
--- !u!4 &854739757 stripped
|
--- !u!4 &854739757 stripped
|
||||||
@@ -7252,6 +7252,18 @@ PrefabInstance:
|
|||||||
propertyPath: m_AnchorMax.y
|
propertyPath: m_AnchorMax.y
|
||||||
value: 0
|
value: 0
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 7035590099291789281, guid: 2122b1e1b36684a40978673f272f200e, type: 3}
|
||||||
|
propertyPath: m_AnchorMax.x
|
||||||
|
value: 1
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 7035590099291789281, guid: 2122b1e1b36684a40978673f272f200e, type: 3}
|
||||||
|
propertyPath: m_AnchorMax.y
|
||||||
|
value: 1
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 7035590099291789281, guid: 2122b1e1b36684a40978673f272f200e, type: 3}
|
||||||
|
propertyPath: m_AnchorMin.x
|
||||||
|
value: 1
|
||||||
|
objectReference: {fileID: 0}
|
||||||
m_RemovedComponents: []
|
m_RemovedComponents: []
|
||||||
m_RemovedGameObjects: []
|
m_RemovedGameObjects: []
|
||||||
m_AddedGameObjects: []
|
m_AddedGameObjects: []
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -79,11 +79,21 @@ AnimatorStateMachine:
|
|||||||
- serializedVersion: 1
|
- serializedVersion: 1
|
||||||
m_State: {fileID: -1032254959699123210}
|
m_State: {fileID: -1032254959699123210}
|
||||||
m_Position: {x: -450, y: 270, z: 0}
|
m_Position: {x: -450, y: 270, z: 0}
|
||||||
|
- serializedVersion: 1
|
||||||
|
m_State: {fileID: 1111345123456789012}
|
||||||
|
m_Position: {x: -700, y: 140, z: 0}
|
||||||
|
- serializedVersion: 1
|
||||||
|
m_State: {fileID: 1111345123456789013}
|
||||||
|
m_Position: {x: -980, y: 140, z: 0}
|
||||||
|
- serializedVersion: 1
|
||||||
|
m_State: {fileID: 1111345123456789014}
|
||||||
|
m_Position: {x: -1260, y: 140, z: 0}
|
||||||
m_ChildStateMachines: []
|
m_ChildStateMachines: []
|
||||||
m_AnyStateTransitions:
|
m_AnyStateTransitions:
|
||||||
- {fileID: -754003289131015157}
|
- {fileID: -754003289131015157}
|
||||||
- {fileID: 469741948129995159}
|
- {fileID: 469741948129995159}
|
||||||
- {fileID: 6228136561094308872}
|
- {fileID: 6228136561094308872}
|
||||||
|
- {fileID: 1111345123456789021}
|
||||||
m_EntryTransitions: []
|
m_EntryTransitions: []
|
||||||
m_StateMachineTransitions: {}
|
m_StateMachineTransitions: {}
|
||||||
m_StateMachineBehaviours: []
|
m_StateMachineBehaviours: []
|
||||||
@@ -323,6 +333,18 @@ AnimatorController:
|
|||||||
m_DefaultInt: 0
|
m_DefaultInt: 0
|
||||||
m_DefaultBool: 0
|
m_DefaultBool: 0
|
||||||
m_Controller: {fileID: 9100000}
|
m_Controller: {fileID: 9100000}
|
||||||
|
- m_Name: Down
|
||||||
|
m_Type: 9
|
||||||
|
m_DefaultFloat: 0
|
||||||
|
m_DefaultInt: 0
|
||||||
|
m_DefaultBool: 0
|
||||||
|
m_Controller: {fileID: 9100000}
|
||||||
|
- m_Name: Recover
|
||||||
|
m_Type: 9
|
||||||
|
m_DefaultFloat: 0
|
||||||
|
m_DefaultInt: 0
|
||||||
|
m_DefaultBool: 0
|
||||||
|
m_Controller: {fileID: 9100000}
|
||||||
m_AnimatorLayers:
|
m_AnimatorLayers:
|
||||||
- serializedVersion: 5
|
- serializedVersion: 5
|
||||||
m_Name: Base Layer
|
m_Name: Base Layer
|
||||||
@@ -437,6 +459,181 @@ AnimatorStateTransition:
|
|||||||
m_InterruptionSource: 0
|
m_InterruptionSource: 0
|
||||||
m_OrderedInterruption: 1
|
m_OrderedInterruption: 1
|
||||||
m_CanTransitionToSelf: 1
|
m_CanTransitionToSelf: 1
|
||||||
|
--- !u!1102 &1111345123456789012
|
||||||
|
AnimatorState:
|
||||||
|
serializedVersion: 6
|
||||||
|
m_ObjectHideFlags: 1
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_Name: DownBegin
|
||||||
|
m_Speed: 1
|
||||||
|
m_CycleOffset: 0
|
||||||
|
m_Transitions:
|
||||||
|
- {fileID: 1111345123456789022}
|
||||||
|
m_StateMachineBehaviours: []
|
||||||
|
m_Position: {x: 50, y: 50, z: 0}
|
||||||
|
m_IKOnFeet: 0
|
||||||
|
m_WriteDefaultValues: 1
|
||||||
|
m_Mirror: 0
|
||||||
|
m_SpeedParameterActive: 0
|
||||||
|
m_MirrorParameterActive: 0
|
||||||
|
m_CycleOffsetParameterActive: 0
|
||||||
|
m_TimeParameterActive: 0
|
||||||
|
m_Motion: {fileID: 3325809351021969952, guid: 9f1ccf176a8592d4997f8a7c48f9571f, type: 3}
|
||||||
|
m_Tag:
|
||||||
|
m_SpeedParameter:
|
||||||
|
m_MirrorParameter:
|
||||||
|
m_CycleOffsetParameter:
|
||||||
|
m_TimeParameter:
|
||||||
|
--- !u!1102 &1111345123456789013
|
||||||
|
AnimatorState:
|
||||||
|
serializedVersion: 6
|
||||||
|
m_ObjectHideFlags: 1
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_Name: DownLoop
|
||||||
|
m_Speed: 1
|
||||||
|
m_CycleOffset: 0
|
||||||
|
m_Transitions:
|
||||||
|
- {fileID: 1111345123456789023}
|
||||||
|
m_StateMachineBehaviours: []
|
||||||
|
m_Position: {x: 50, y: 50, z: 0}
|
||||||
|
m_IKOnFeet: 0
|
||||||
|
m_WriteDefaultValues: 1
|
||||||
|
m_Mirror: 0
|
||||||
|
m_SpeedParameterActive: 0
|
||||||
|
m_MirrorParameterActive: 0
|
||||||
|
m_CycleOffsetParameterActive: 0
|
||||||
|
m_TimeParameterActive: 0
|
||||||
|
m_Motion: {fileID: 4349338778344460676, guid: b0e7c1111468a924a81b4323e5aa8eb4, type: 3}
|
||||||
|
m_Tag:
|
||||||
|
m_SpeedParameter:
|
||||||
|
m_MirrorParameter:
|
||||||
|
m_CycleOffsetParameter:
|
||||||
|
m_TimeParameter:
|
||||||
|
--- !u!1102 &1111345123456789014
|
||||||
|
AnimatorState:
|
||||||
|
serializedVersion: 6
|
||||||
|
m_ObjectHideFlags: 1
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_Name: Recover
|
||||||
|
m_Speed: 1
|
||||||
|
m_CycleOffset: 0
|
||||||
|
m_Transitions:
|
||||||
|
- {fileID: 1111345123456789024}
|
||||||
|
m_StateMachineBehaviours: []
|
||||||
|
m_Position: {x: 50, y: 50, z: 0}
|
||||||
|
m_IKOnFeet: 0
|
||||||
|
m_WriteDefaultValues: 1
|
||||||
|
m_Mirror: 0
|
||||||
|
m_SpeedParameterActive: 0
|
||||||
|
m_MirrorParameterActive: 0
|
||||||
|
m_CycleOffsetParameterActive: 0
|
||||||
|
m_TimeParameterActive: 0
|
||||||
|
m_Motion: {fileID: -3512901111006340046, guid: 3866152d4c1922849be562e6e814dce3, type: 3}
|
||||||
|
m_Tag:
|
||||||
|
m_SpeedParameter:
|
||||||
|
m_MirrorParameter:
|
||||||
|
m_CycleOffsetParameter:
|
||||||
|
m_TimeParameter:
|
||||||
|
--- !u!1101 &1111345123456789021
|
||||||
|
AnimatorStateTransition:
|
||||||
|
m_ObjectHideFlags: 1
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_Name:
|
||||||
|
m_Conditions:
|
||||||
|
- m_ConditionMode: 1
|
||||||
|
m_ConditionEvent: Down
|
||||||
|
m_EventTreshold: 0
|
||||||
|
m_DstStateMachine: {fileID: 0}
|
||||||
|
m_DstState: {fileID: 1111345123456789012}
|
||||||
|
m_Solo: 0
|
||||||
|
m_Mute: 0
|
||||||
|
m_IsExit: 0
|
||||||
|
serializedVersion: 3
|
||||||
|
m_TransitionDuration: 0.05
|
||||||
|
m_TransitionOffset: 0
|
||||||
|
m_ExitTime: 0
|
||||||
|
m_HasExitTime: 0
|
||||||
|
m_HasFixedDuration: 1
|
||||||
|
m_InterruptionSource: 0
|
||||||
|
m_OrderedInterruption: 1
|
||||||
|
m_CanTransitionToSelf: 1
|
||||||
|
--- !u!1101 &1111345123456789022
|
||||||
|
AnimatorStateTransition:
|
||||||
|
m_ObjectHideFlags: 1
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_Name:
|
||||||
|
m_Conditions: []
|
||||||
|
m_DstStateMachine: {fileID: 0}
|
||||||
|
m_DstState: {fileID: 1111345123456789013}
|
||||||
|
m_Solo: 0
|
||||||
|
m_Mute: 0
|
||||||
|
m_IsExit: 0
|
||||||
|
serializedVersion: 3
|
||||||
|
m_TransitionDuration: 0.05
|
||||||
|
m_TransitionOffset: 0
|
||||||
|
m_ExitTime: 0.95
|
||||||
|
m_HasExitTime: 1
|
||||||
|
m_HasFixedDuration: 1
|
||||||
|
m_InterruptionSource: 0
|
||||||
|
m_OrderedInterruption: 1
|
||||||
|
m_CanTransitionToSelf: 1
|
||||||
|
--- !u!1101 &1111345123456789023
|
||||||
|
AnimatorStateTransition:
|
||||||
|
m_ObjectHideFlags: 1
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_Name:
|
||||||
|
m_Conditions:
|
||||||
|
- m_ConditionMode: 1
|
||||||
|
m_ConditionEvent: Recover
|
||||||
|
m_EventTreshold: 0
|
||||||
|
m_DstStateMachine: {fileID: 0}
|
||||||
|
m_DstState: {fileID: 1111345123456789014}
|
||||||
|
m_Solo: 0
|
||||||
|
m_Mute: 0
|
||||||
|
m_IsExit: 0
|
||||||
|
serializedVersion: 3
|
||||||
|
m_TransitionDuration: 0.05
|
||||||
|
m_TransitionOffset: 0
|
||||||
|
m_ExitTime: 0
|
||||||
|
m_HasExitTime: 0
|
||||||
|
m_HasFixedDuration: 1
|
||||||
|
m_InterruptionSource: 0
|
||||||
|
m_OrderedInterruption: 1
|
||||||
|
m_CanTransitionToSelf: 1
|
||||||
|
--- !u!1101 &1111345123456789024
|
||||||
|
AnimatorStateTransition:
|
||||||
|
m_ObjectHideFlags: 1
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_Name:
|
||||||
|
m_Conditions: []
|
||||||
|
m_DstStateMachine: {fileID: 0}
|
||||||
|
m_DstState: {fileID: -7908033645098541312}
|
||||||
|
m_Solo: 0
|
||||||
|
m_Mute: 0
|
||||||
|
m_IsExit: 0
|
||||||
|
serializedVersion: 3
|
||||||
|
m_TransitionDuration: 0.1
|
||||||
|
m_TransitionOffset: 0
|
||||||
|
m_ExitTime: 0.95
|
||||||
|
m_HasExitTime: 1
|
||||||
|
m_HasFixedDuration: 1
|
||||||
|
m_InterruptionSource: 0
|
||||||
|
m_OrderedInterruption: 1
|
||||||
|
m_CanTransitionToSelf: 1
|
||||||
--- !u!1102 &7693173606830535998
|
--- !u!1102 &7693173606830535998
|
||||||
AnimatorState:
|
AnimatorState:
|
||||||
serializedVersion: 6
|
serializedVersion: 6
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!114 &11400000
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 41c96b54a96cdb84c9bda774775b0a1a, type: 3}
|
||||||
|
m_Name: "Data_SkillEffect_Player_\uB2E4\uC6B4"
|
||||||
|
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Skills.Effects.DownEffect
|
||||||
|
targetType: 0
|
||||||
|
targetTeam: 0
|
||||||
|
areaCenter: 0
|
||||||
|
areaShape: 0
|
||||||
|
targetLayers:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 0
|
||||||
|
areaRadius: 3
|
||||||
|
fanOriginDistance: 1
|
||||||
|
fanRadius: 3
|
||||||
|
fanHalfAngle: 45
|
||||||
|
duration: 1
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4e95f5df9caf4134d956c476bb8079e0
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 11400000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -20,7 +20,6 @@ GameObject:
|
|||||||
- component: {fileID: 11400005}
|
- component: {fileID: 11400005}
|
||||||
- component: {fileID: 11400006}
|
- component: {fileID: 11400006}
|
||||||
- component: {fileID: 3792588902782784034}
|
- component: {fileID: 3792588902782784034}
|
||||||
- component: {fileID: 9069822911508997612}
|
|
||||||
m_Layer: 0
|
m_Layer: 0
|
||||||
m_Name: Prefab_Boss_BossTemplate
|
m_Name: Prefab_Boss_BossTemplate
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
@@ -291,23 +290,3 @@ MonoBehaviour:
|
|||||||
SwitchTransformSpaceWhenParented: 0
|
SwitchTransformSpaceWhenParented: 0
|
||||||
Interpolate: 1
|
Interpolate: 1
|
||||||
SlerpPosition: 0
|
SlerpPosition: 0
|
||||||
--- !u!114 &9069822911508997612
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 100000}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 0
|
|
||||||
m_Script: {fileID: 11500000, guid: e8d0727d5ae3244e3b569694d3912374, type: 3}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.Components.NetworkAnimator
|
|
||||||
ShowTopMostFoldoutHeaderGroup: 1
|
|
||||||
NetworkAnimatorExpanded: 0
|
|
||||||
AuthorityMode: 0
|
|
||||||
m_Animator: {fileID: 9500000}
|
|
||||||
TransitionStateInfoList: []
|
|
||||||
AnimatorParameterEntries:
|
|
||||||
ParameterEntries: []
|
|
||||||
AnimatorParametersExpanded: 0
|
|
||||||
|
|||||||
@@ -243,6 +243,14 @@ MonoBehaviour:
|
|||||||
NameHash: 20298039
|
NameHash: 20298039
|
||||||
Synchronize: 1
|
Synchronize: 1
|
||||||
ParameterType: 9
|
ParameterType: 9
|
||||||
|
- name: Down
|
||||||
|
NameHash: -1127399675
|
||||||
|
Synchronize: 1
|
||||||
|
ParameterType: 9
|
||||||
|
- name: Recover
|
||||||
|
NameHash: 976603345
|
||||||
|
Synchronize: 1
|
||||||
|
ParameterType: 9
|
||||||
AnimatorParametersExpanded: 0
|
AnimatorParametersExpanded: 0
|
||||||
--- !u!95 &3426985706796420257
|
--- !u!95 &3426985706796420257
|
||||||
Animator:
|
Animator:
|
||||||
@@ -305,6 +313,8 @@ MonoBehaviour:
|
|||||||
debugMode: 1
|
debugMode: 1
|
||||||
showAreaDebug: 1
|
showAreaDebug: 1
|
||||||
debugDrawDuration: 1
|
debugDrawDuration: 1
|
||||||
|
lastCancelledSkillName:
|
||||||
|
lastCancelReason: 0
|
||||||
--- !u!114 &6585367215453362640
|
--- !u!114 &6585367215453362640
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -345,6 +355,7 @@ MonoBehaviour:
|
|||||||
ShowTopMostFoldoutHeaderGroup: 1
|
ShowTopMostFoldoutHeaderGroup: 1
|
||||||
characterStats: {fileID: -5132198055668300151}
|
characterStats: {fileID: -5132198055668300151}
|
||||||
networkController: {fileID: 1664515335065415329}
|
networkController: {fileID: 1664515335065415329}
|
||||||
|
skillController: {fileID: 0}
|
||||||
--- !u!114 &3552488436187204500
|
--- !u!114 &3552488436187204500
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using UnityEngine;
|
|||||||
using Unity.Netcode;
|
using Unity.Netcode;
|
||||||
using Colosseum.Stats;
|
using Colosseum.Stats;
|
||||||
using Colosseum.Player;
|
using Colosseum.Player;
|
||||||
|
using Colosseum.Skills;
|
||||||
|
|
||||||
namespace Colosseum.Abnormalities
|
namespace Colosseum.Abnormalities
|
||||||
{
|
{
|
||||||
@@ -20,6 +21,9 @@ namespace Colosseum.Abnormalities
|
|||||||
[Tooltip("PlayerNetworkController 컴포넌트 (HP/MP 관리용)")]
|
[Tooltip("PlayerNetworkController 컴포넌트 (HP/MP 관리용)")]
|
||||||
[SerializeField] private PlayerNetworkController networkController;
|
[SerializeField] private PlayerNetworkController networkController;
|
||||||
|
|
||||||
|
[Tooltip("스킬 실행 관리자 (강제 취소 처리용)")]
|
||||||
|
[SerializeField] private SkillController skillController;
|
||||||
|
|
||||||
// 활성화된 이상 상태 목록
|
// 활성화된 이상 상태 목록
|
||||||
private readonly List<ActiveAbnormality> activeAbnormalities = new List<ActiveAbnormality>();
|
private readonly List<ActiveAbnormality> activeAbnormalities = new List<ActiveAbnormality>();
|
||||||
|
|
||||||
@@ -108,6 +112,9 @@ namespace Colosseum.Abnormalities
|
|||||||
if (networkController == null)
|
if (networkController == null)
|
||||||
networkController = GetComponent<PlayerNetworkController>();
|
networkController = GetComponent<PlayerNetworkController>();
|
||||||
|
|
||||||
|
if (skillController == null)
|
||||||
|
skillController = GetComponent<SkillController>();
|
||||||
|
|
||||||
syncedAbnormalities = new NetworkList<AbnormalitySyncData>();
|
syncedAbnormalities = new NetworkList<AbnormalitySyncData>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,9 +421,12 @@ namespace Colosseum.Abnormalities
|
|||||||
|
|
||||||
private void ApplyControlEffect(AbnormalityData data)
|
private void ApplyControlEffect(AbnormalityData data)
|
||||||
{
|
{
|
||||||
|
bool enteredStun = false;
|
||||||
|
|
||||||
switch (data.controlType)
|
switch (data.controlType)
|
||||||
{
|
{
|
||||||
case ControlType.Stun:
|
case ControlType.Stun:
|
||||||
|
enteredStun = stunCount == 0;
|
||||||
stunCount++;
|
stunCount++;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -434,6 +444,11 @@ namespace Colosseum.Abnormalities
|
|||||||
}
|
}
|
||||||
|
|
||||||
SyncControlEffects();
|
SyncControlEffects();
|
||||||
|
|
||||||
|
if (enteredStun)
|
||||||
|
{
|
||||||
|
TryCancelCurrentSkill(SkillCancelReason.Stun, data.abnormalityName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveControlEffect(AbnormalityData data)
|
private void RemoveControlEffect(AbnormalityData data)
|
||||||
@@ -584,6 +599,17 @@ namespace Colosseum.Abnormalities
|
|||||||
Debug.Log($"[Abnormality] Cleared all abnormalities on death: {gameObject.name}");
|
Debug.Log($"[Abnormality] Cleared all abnormalities on death: {gameObject.name}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TryCancelCurrentSkill(SkillCancelReason reason, string sourceName)
|
||||||
|
{
|
||||||
|
if (!IsServer || skillController == null || !skillController.IsPlayingAnimation)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (skillController.CancelSkill(reason))
|
||||||
|
{
|
||||||
|
Debug.Log($"[Abnormality] Cancelled skill because '{sourceName}' applied to {gameObject.name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private AbnormalityData FindAbnormalityDataById(int instanceId)
|
private AbnormalityData FindAbnormalityDataById(int instanceId)
|
||||||
{
|
{
|
||||||
var allData = Resources.FindObjectsOfTypeAll<AbnormalityData>();
|
var allData = Resources.FindObjectsOfTypeAll<AbnormalityData>();
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using Unity.Netcode;
|
|||||||
|
|
||||||
using Colosseum.Stats;
|
using Colosseum.Stats;
|
||||||
using Colosseum.Combat;
|
using Colosseum.Combat;
|
||||||
|
using Colosseum.Skills;
|
||||||
|
|
||||||
namespace Colosseum.Enemy
|
namespace Colosseum.Enemy
|
||||||
{
|
{
|
||||||
@@ -347,7 +348,7 @@ namespace Colosseum.Enemy
|
|||||||
var skillController = GetComponent<Colosseum.Skills.SkillController>();
|
var skillController = GetComponent<Colosseum.Skills.SkillController>();
|
||||||
if (skillController != null)
|
if (skillController != null)
|
||||||
{
|
{
|
||||||
skillController.CancelSkill();
|
skillController.CancelSkill(SkillCancelReason.Death);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 모든 클라이언트에서 사망 애니메이션 재생
|
// 모든 클라이언트에서 사망 애니메이션 재생
|
||||||
@@ -383,6 +384,12 @@ namespace Colosseum.Enemy
|
|||||||
{
|
{
|
||||||
animator.Rebind();
|
animator.Rebind();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var skillController = GetComponent<SkillController>();
|
||||||
|
if (skillController != null)
|
||||||
|
{
|
||||||
|
skillController.CancelSkill(SkillCancelReason.Respawn);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 체력 변화 이벤트 전파
|
// 체력 변화 이벤트 전파
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
namespace Colosseum.Network
|
||||||
|
{
|
||||||
|
// 임시 삭제 후 Unity 컴파일 캐시 정리를 위한 빈 파일
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f55c7f03b6f5b6e448b8d03880306bb3
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
using Unity.Netcode;
|
||||||
|
|
||||||
|
using Colosseum.Skills;
|
||||||
|
|
||||||
|
namespace Colosseum.Player
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 플레이어의 피격 제어 상태를 관리합니다.
|
||||||
|
/// 넉백 강제 이동과 다운 상태, 피격 애니메이션 재생을 담당합니다.
|
||||||
|
/// </summary>
|
||||||
|
[DisallowMultipleComponent]
|
||||||
|
[RequireComponent(typeof(PlayerMovement))]
|
||||||
|
public class HitReactionController : NetworkBehaviour
|
||||||
|
{
|
||||||
|
[Header("References")]
|
||||||
|
[Tooltip("플레이어 이동 컴포넌트")]
|
||||||
|
[SerializeField] private PlayerMovement playerMovement;
|
||||||
|
|
||||||
|
[Tooltip("스킬 실행 관리자")]
|
||||||
|
[SerializeField] private SkillController skillController;
|
||||||
|
|
||||||
|
[Tooltip("플레이어 네트워크 상태")]
|
||||||
|
[SerializeField] private PlayerNetworkController networkController;
|
||||||
|
|
||||||
|
[Tooltip("피격 애니메이션을 재생할 Animator")]
|
||||||
|
[SerializeField] private Animator animator;
|
||||||
|
|
||||||
|
[Header("Animation")]
|
||||||
|
[Tooltip("일반 피격 트리거 이름")]
|
||||||
|
[SerializeField] private string hitTriggerParam = "Hit";
|
||||||
|
|
||||||
|
[Tooltip("다운 시작 트리거 이름")]
|
||||||
|
[SerializeField] private string downTriggerParam = "Down";
|
||||||
|
|
||||||
|
[Tooltip("기상 트리거 이름")]
|
||||||
|
[SerializeField] private string recoverTriggerParam = "Recover";
|
||||||
|
|
||||||
|
[Header("Settings")]
|
||||||
|
[Tooltip("애니메이션 파라미터가 없을 때 경고 로그 출력")]
|
||||||
|
[SerializeField] private bool logMissingAnimationParams = false;
|
||||||
|
|
||||||
|
private readonly NetworkVariable<bool> isDowned = new NetworkVariable<bool>(false);
|
||||||
|
private float downRemainingTime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 다운 상태 여부
|
||||||
|
/// </summary>
|
||||||
|
public bool IsDowned => isDowned.Value;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 넉백 강제 이동 진행 여부
|
||||||
|
/// </summary>
|
||||||
|
public bool IsKnockbackActive => playerMovement != null && playerMovement.IsForcedMoving;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
ResolveReferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnNetworkSpawn()
|
||||||
|
{
|
||||||
|
ResolveReferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (!IsServer || !isDowned.Value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
downRemainingTime -= Time.deltaTime;
|
||||||
|
if (downRemainingTime <= 0f)
|
||||||
|
{
|
||||||
|
RecoverFromDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 넉백을 적용합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void ApplyKnockback(Vector3 worldVelocity, float duration, bool playHitAnimation = true)
|
||||||
|
{
|
||||||
|
if (!IsServer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ResolveReferences();
|
||||||
|
|
||||||
|
if (networkController != null && networkController.IsDead)
|
||||||
|
return;
|
||||||
|
|
||||||
|
playerMovement?.ApplyForcedMovement(worldVelocity, duration);
|
||||||
|
|
||||||
|
if (playHitAnimation)
|
||||||
|
{
|
||||||
|
TriggerAnimationRpc(hitTriggerParam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 다운 상태를 적용합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void ApplyDown(float duration)
|
||||||
|
{
|
||||||
|
if (!IsServer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ResolveReferences();
|
||||||
|
|
||||||
|
if (networkController != null && networkController.IsDead)
|
||||||
|
return;
|
||||||
|
|
||||||
|
downRemainingTime = Mathf.Max(downRemainingTime, duration);
|
||||||
|
|
||||||
|
if (isDowned.Value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
isDowned.Value = true;
|
||||||
|
skillController?.CancelSkill(SkillCancelReason.HitReaction);
|
||||||
|
playerMovement?.ClearForcedMovement();
|
||||||
|
TriggerAnimationRpc(downTriggerParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 다운 상태를 해제합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void RecoverFromDown()
|
||||||
|
{
|
||||||
|
if (!IsServer || !isDowned.Value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
isDowned.Value = false;
|
||||||
|
downRemainingTime = 0f;
|
||||||
|
TriggerAnimationRpc(recoverTriggerParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 피격 상태를 즉시 초기화합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void ClearHitReactionState(bool playRecoverAnimation = false)
|
||||||
|
{
|
||||||
|
if (!IsServer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ResolveReferences();
|
||||||
|
|
||||||
|
playerMovement?.ClearForcedMovement();
|
||||||
|
|
||||||
|
if (!isDowned.Value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
isDowned.Value = false;
|
||||||
|
downRemainingTime = 0f;
|
||||||
|
|
||||||
|
if (playRecoverAnimation)
|
||||||
|
{
|
||||||
|
TriggerAnimationRpc(recoverTriggerParam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Rpc(SendTo.Everyone)]
|
||||||
|
private void TriggerAnimationRpc(string triggerName)
|
||||||
|
{
|
||||||
|
ResolveReferences();
|
||||||
|
|
||||||
|
if (animator == null || string.IsNullOrWhiteSpace(triggerName))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!HasTrigger(triggerName))
|
||||||
|
{
|
||||||
|
if (logMissingAnimationParams)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[HitReaction] Animator trigger not found: {triggerName}");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
animator.SetTrigger(triggerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HasTrigger(string triggerName)
|
||||||
|
{
|
||||||
|
if (animator == null || string.IsNullOrWhiteSpace(triggerName))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (int i = 0; i < animator.parameterCount; i++)
|
||||||
|
{
|
||||||
|
AnimatorControllerParameter parameter = animator.GetParameter(i);
|
||||||
|
if (parameter.type == AnimatorControllerParameterType.Trigger && parameter.name == triggerName)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResolveReferences()
|
||||||
|
{
|
||||||
|
if (playerMovement == null)
|
||||||
|
playerMovement = GetComponent<PlayerMovement>();
|
||||||
|
|
||||||
|
if (skillController == null)
|
||||||
|
skillController = GetComponent<SkillController>();
|
||||||
|
|
||||||
|
if (networkController == null)
|
||||||
|
networkController = GetComponent<PlayerNetworkController>();
|
||||||
|
|
||||||
|
if (animator == null)
|
||||||
|
animator = GetComponentInChildren<Animator>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ebad07a2d5fc29b4ba061866bfa1568e
|
||||||
@@ -93,7 +93,7 @@ namespace Colosseum.Player
|
|||||||
GUILayout.Label($"사망 상태: {(networkController != null && networkController.IsDead ? "Dead" : "Alive")}");
|
GUILayout.Label($"사망 상태: {(networkController != null && networkController.IsDead ? "Dead" : "Alive")}");
|
||||||
if (TryGetComponent<PlayerActionState>(out var actionState))
|
if (TryGetComponent<PlayerActionState>(out var actionState))
|
||||||
{
|
{
|
||||||
GUILayout.Label($"무적 상태: {(actionState.IsDamageImmune ? "Immune" : "Normal")} / 이동:{actionState.CanMove} / 스킬:{actionState.CanUseSkills}");
|
GUILayout.Label($"무적:{(actionState.IsDamageImmune ? "On" : "Off")} / 다운:{(actionState.IsDowned ? "On" : "Off")} / 이동:{actionState.CanMove} / 스킬:{actionState.CanUseSkills}");
|
||||||
}
|
}
|
||||||
GUILayout.Label("입력 예시: 기절 / Data_Abnormality_Player_Stun / 0");
|
GUILayout.Label("입력 예시: 기절 / Data_Abnormality_Player_Stun / 0");
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ namespace Colosseum.Player
|
|||||||
[SerializeField] private PlayerNetworkController networkController;
|
[SerializeField] private PlayerNetworkController networkController;
|
||||||
[SerializeField] private PlayerSkillInput skillInput;
|
[SerializeField] private PlayerSkillInput skillInput;
|
||||||
[SerializeField] private SkillController skillController;
|
[SerializeField] private SkillController skillController;
|
||||||
|
[SerializeField] private PlayerMovement playerMovement;
|
||||||
|
[SerializeField] private HitReactionController hitReactionController;
|
||||||
|
|
||||||
[Header("Test Data")]
|
[Header("Test Data")]
|
||||||
[SerializeField] private AbnormalityData stunData;
|
[SerializeField] private AbnormalityData stunData;
|
||||||
@@ -84,7 +86,7 @@ namespace Colosseum.Player
|
|||||||
ResolveReferences();
|
ResolveReferences();
|
||||||
LoadDefaultAssetsIfNeeded();
|
LoadDefaultAssetsIfNeeded();
|
||||||
|
|
||||||
if (abnormalityManager == null || actionState == null || networkController == null || stunData == null || silenceData == null)
|
if (abnormalityManager == null || actionState == null || networkController == null || playerMovement == null || hitReactionController == null || stunData == null || silenceData == null)
|
||||||
{
|
{
|
||||||
Debug.LogWarning("[AbnormalityVerification] Missing references or test data.");
|
Debug.LogWarning("[AbnormalityVerification] Missing references or test data.");
|
||||||
yield break;
|
yield break;
|
||||||
@@ -105,9 +107,16 @@ namespace Colosseum.Player
|
|||||||
Verify("초기 상태: 이동 가능", actionState.CanMove);
|
Verify("초기 상태: 이동 가능", actionState.CanMove);
|
||||||
Verify("초기 상태: 스킬 사용 가능", actionState.CanUseSkills);
|
Verify("초기 상태: 스킬 사용 가능", actionState.CanUseSkills);
|
||||||
Verify("초기 상태: 무적 상태 아님", !actionState.IsDamageImmune);
|
Verify("초기 상태: 무적 상태 아님", !actionState.IsDamageImmune);
|
||||||
|
Verify("초기 상태: 마지막 취소 이유 없음", skillController == null || skillController.LastCancelReason == SkillCancelReason.None);
|
||||||
|
|
||||||
yield return RunInvincibilitySkillVerification();
|
yield return RunInvincibilitySkillVerification();
|
||||||
|
|
||||||
|
yield return RunStunCancellationVerification();
|
||||||
|
|
||||||
|
yield return RunDownVerification();
|
||||||
|
|
||||||
|
yield return RunKnockbackVerification();
|
||||||
|
|
||||||
abnormalityManager.ApplyAbnormality(stunData, gameObject);
|
abnormalityManager.ApplyAbnormality(stunData, gameObject);
|
||||||
yield return new WaitForSeconds(settleDelay);
|
yield return new WaitForSeconds(settleDelay);
|
||||||
|
|
||||||
@@ -187,6 +196,10 @@ namespace Colosseum.Player
|
|||||||
skillInput = GetComponent<PlayerSkillInput>();
|
skillInput = GetComponent<PlayerSkillInput>();
|
||||||
if (skillController == null)
|
if (skillController == null)
|
||||||
skillController = GetComponent<SkillController>();
|
skillController = GetComponent<SkillController>();
|
||||||
|
if (playerMovement == null)
|
||||||
|
playerMovement = GetComponent<PlayerMovement>();
|
||||||
|
if (hitReactionController == null)
|
||||||
|
hitReactionController = GetComponent<HitReactionController>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadDefaultAssetsIfNeeded()
|
private void LoadDefaultAssetsIfNeeded()
|
||||||
@@ -276,6 +289,109 @@ namespace Colosseum.Player
|
|||||||
Verify("무적 해제: IsDamageImmune false", !actionState.IsDamageImmune);
|
Verify("무적 해제: IsDamageImmune false", !actionState.IsDamageImmune);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IEnumerator RunStunCancellationVerification()
|
||||||
|
{
|
||||||
|
SkillData cancellableSkill = FindCancellableSkill();
|
||||||
|
if (cancellableSkill == null)
|
||||||
|
{
|
||||||
|
AppendLine("[SKIP] 기절 강제 취소 검증: 테스트용 스킬이 없습니다.");
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skillController != null)
|
||||||
|
{
|
||||||
|
yield return WaitForConditionOrTimeout(() => !skillController.IsPlayingAnimation, 1.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skillController == null || !skillController.ExecuteSkill(cancellableSkill))
|
||||||
|
{
|
||||||
|
Verify("기절 강제 취소 검증: 스킬 실행 성공", false);
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return new WaitForSeconds(0.05f);
|
||||||
|
|
||||||
|
abnormalityManager.ApplyAbnormality(stunData, gameObject);
|
||||||
|
yield return WaitForConditionOrTimeout(() => abnormalityManager.IsStunned, settleDelay + 0.5f);
|
||||||
|
yield return new WaitForSeconds(0.05f);
|
||||||
|
|
||||||
|
Verify("기절 강제 취소: 스킬 애니메이션 중단", !skillController.IsPlayingAnimation);
|
||||||
|
Verify("기절 강제 취소: 취소 이유 기록", skillController.LastCancelReason == SkillCancelReason.Stun);
|
||||||
|
|
||||||
|
yield return new WaitForSeconds(stunData.duration + settleDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator RunKnockbackVerification()
|
||||||
|
{
|
||||||
|
abnormalityManager.RemoveAllAbnormalities();
|
||||||
|
hitReactionController.ClearHitReactionState();
|
||||||
|
yield return new WaitForSeconds(settleDelay);
|
||||||
|
|
||||||
|
Vector3 startPosition = transform.position;
|
||||||
|
Vector3 knockbackVelocity = Vector3.back * 6f;
|
||||||
|
|
||||||
|
RequestKnockbackRpc(knockbackVelocity, 0.2f);
|
||||||
|
yield return new WaitForSeconds(0.3f);
|
||||||
|
|
||||||
|
float movedDistance = Vector3.Distance(startPosition, transform.position);
|
||||||
|
Verify("넉백 적용: 위치 이동 발생", movedDistance > 0.2f);
|
||||||
|
Verify("넉백 적용: 강제 이동 종료", !playerMovement.IsForcedMoving);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator RunDownVerification()
|
||||||
|
{
|
||||||
|
SkillData cancellableSkill = FindCancellableSkill();
|
||||||
|
if (cancellableSkill == null)
|
||||||
|
{
|
||||||
|
AppendLine("[SKIP] 다운 검증: 테스트용 스킬이 없습니다.");
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skillController != null)
|
||||||
|
{
|
||||||
|
yield return WaitForConditionOrTimeout(() => !skillController.IsPlayingAnimation, 1.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skillController == null || !skillController.ExecuteSkill(cancellableSkill))
|
||||||
|
{
|
||||||
|
Verify("다운 강제 취소 검증: 스킬 실행 성공", false);
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return new WaitForSeconds(0.05f);
|
||||||
|
|
||||||
|
RequestDownRpc(0.6f);
|
||||||
|
yield return WaitForConditionOrTimeout(() => hitReactionController.IsDowned, settleDelay + 0.5f);
|
||||||
|
yield return new WaitForSeconds(0.05f);
|
||||||
|
|
||||||
|
Verify("다운 적용: IsDowned", hitReactionController.IsDowned);
|
||||||
|
Verify("다운 적용: ActionState.IsDowned", actionState.IsDowned);
|
||||||
|
Verify("다운 강제 취소: 스킬 애니메이션 중단", !skillController.IsPlayingAnimation);
|
||||||
|
Verify("다운 강제 취소: 취소 이유 기록", skillController.LastCancelReason == SkillCancelReason.HitReaction);
|
||||||
|
Verify("다운 적용: 이동 불가", !actionState.CanMove);
|
||||||
|
Verify("다운 적용: 점프 불가", !actionState.CanJump);
|
||||||
|
Verify("다운 적용: 스킬 사용 불가", !actionState.CanUseSkills);
|
||||||
|
Verify("다운 적용: 이동속도 0", Mathf.Approximately(actionState.MoveSpeedMultiplier, 0f));
|
||||||
|
|
||||||
|
yield return WaitForConditionOrTimeout(() => !hitReactionController.IsDowned, 1.5f);
|
||||||
|
|
||||||
|
Verify("다운 해제: IsDowned false", !hitReactionController.IsDowned);
|
||||||
|
Verify("다운 해제: 이동 가능 복구", actionState.CanMove);
|
||||||
|
Verify("다운 해제: 스킬 사용 가능 복구", actionState.CanUseSkills);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Rpc(SendTo.Server)]
|
||||||
|
private void RequestDownRpc(float duration)
|
||||||
|
{
|
||||||
|
hitReactionController?.ApplyDown(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Rpc(SendTo.Server)]
|
||||||
|
private void RequestKnockbackRpc(Vector3 velocity, float duration)
|
||||||
|
{
|
||||||
|
hitReactionController?.ApplyKnockback(velocity, duration, false);
|
||||||
|
}
|
||||||
|
|
||||||
private float GetSkillDuration(SkillData skill)
|
private float GetSkillDuration(SkillData skill)
|
||||||
{
|
{
|
||||||
if (skill == null || skill.SkillClip == null)
|
if (skill == null || skill.SkillClip == null)
|
||||||
@@ -284,6 +400,21 @@ namespace Colosseum.Player
|
|||||||
return Mathf.Max(settleDelay, skill.SkillClip.length / Mathf.Max(0.1f, skill.AnimationSpeed));
|
return Mathf.Max(settleDelay, skill.SkillClip.length / Mathf.Max(0.1f, skill.AnimationSpeed));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SkillData FindCancellableSkill()
|
||||||
|
{
|
||||||
|
if (skillInput == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
for (int i = 0; i < 6; i++)
|
||||||
|
{
|
||||||
|
SkillData skill = skillInput.GetSkill(i);
|
||||||
|
if (skill != null)
|
||||||
|
return skill;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private IEnumerator WaitForConditionOrTimeout(Func<bool> predicate, float timeout)
|
private IEnumerator WaitForConditionOrTimeout(Func<bool> predicate, float timeout)
|
||||||
{
|
{
|
||||||
float elapsed = 0f;
|
float elapsed = 0f;
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ namespace Colosseum.Player
|
|||||||
[Tooltip("스킬 실행 관리자")]
|
[Tooltip("스킬 실행 관리자")]
|
||||||
[SerializeField] private SkillController skillController;
|
[SerializeField] private SkillController skillController;
|
||||||
|
|
||||||
|
[Tooltip("피격 제어 관리자")]
|
||||||
|
[SerializeField] private HitReactionController hitReactionController;
|
||||||
|
|
||||||
[Tooltip("관전 관리자")]
|
[Tooltip("관전 관리자")]
|
||||||
[SerializeField] private PlayerSpectator spectator;
|
[SerializeField] private PlayerSpectator spectator;
|
||||||
|
|
||||||
@@ -35,6 +38,11 @@ namespace Colosseum.Player
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsStunned => abnormalityManager != null && abnormalityManager.IsStunned;
|
public bool IsStunned => abnormalityManager != null && abnormalityManager.IsStunned;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 다운 상태 여부
|
||||||
|
/// </summary>
|
||||||
|
public bool IsDowned => hitReactionController != null && hitReactionController.IsDowned;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 침묵 상태 여부
|
/// 침묵 상태 여부
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -68,17 +76,17 @@ namespace Colosseum.Player
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 플레이어가 직접 이동 입력을 사용할 수 있는지 여부
|
/// 플레이어가 직접 이동 입력을 사용할 수 있는지 여부
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool CanMove => CanReceiveInput && !IsStunned && !BlocksMovementForCurrentSkill();
|
public bool CanMove => CanReceiveInput && !IsStunned && !IsDowned && !BlocksMovementForCurrentSkill();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 점프 가능 여부
|
/// 점프 가능 여부
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool CanJump => CanReceiveInput && !IsStunned && !BlocksJumpForCurrentSkill();
|
public bool CanJump => CanReceiveInput && !IsStunned && !IsDowned && !BlocksJumpForCurrentSkill();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 일반 스킬 시작 가능 여부
|
/// 일반 스킬 시작 가능 여부
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool CanUseSkills => CanReceiveInput && !IsStunned && !IsSilenced && !BlocksSkillUseForCurrentSkill();
|
public bool CanUseSkills => CanReceiveInput && !IsStunned && !IsDowned && !IsSilenced && !BlocksSkillUseForCurrentSkill();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 특정 스킬의 시작 가능 여부.
|
/// 특정 스킬의 시작 가능 여부.
|
||||||
@@ -89,7 +97,7 @@ namespace Colosseum.Player
|
|||||||
if (skill == null)
|
if (skill == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!CanReceiveInput || IsStunned || IsSilenced)
|
if (!CanReceiveInput || IsStunned || IsDowned || IsSilenced)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return !BlocksSkillUseForCurrentSkill();
|
return !BlocksSkillUseForCurrentSkill();
|
||||||
@@ -102,7 +110,7 @@ namespace Colosseum.Player
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (!CanReceiveInput || IsStunned)
|
if (!CanReceiveInput || IsStunned || IsDowned)
|
||||||
return 0f;
|
return 0f;
|
||||||
|
|
||||||
return abnormalityManager != null ? abnormalityManager.MoveSpeedMultiplier : 1f;
|
return abnormalityManager != null ? abnormalityManager.MoveSpeedMultiplier : 1f;
|
||||||
@@ -117,6 +125,8 @@ namespace Colosseum.Player
|
|||||||
abnormalityManager = GetComponent<AbnormalityManager>();
|
abnormalityManager = GetComponent<AbnormalityManager>();
|
||||||
if (skillController == null)
|
if (skillController == null)
|
||||||
skillController = GetComponent<SkillController>();
|
skillController = GetComponent<SkillController>();
|
||||||
|
if (hitReactionController == null)
|
||||||
|
hitReactionController = GetOrCreateHitReactionController();
|
||||||
if (spectator == null)
|
if (spectator == null)
|
||||||
spectator = GetComponentInChildren<PlayerSpectator>();
|
spectator = GetComponentInChildren<PlayerSpectator>();
|
||||||
}
|
}
|
||||||
@@ -144,5 +154,14 @@ namespace Colosseum.Player
|
|||||||
|
|
||||||
return CurrentSkill == null || CurrentSkill.BlockOtherSkillsWhileCasting;
|
return CurrentSkill == null || CurrentSkill.BlockOtherSkillsWhileCasting;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private HitReactionController GetOrCreateHitReactionController()
|
||||||
|
{
|
||||||
|
HitReactionController foundController = GetComponent<HitReactionController>();
|
||||||
|
if (foundController != null)
|
||||||
|
return foundController;
|
||||||
|
|
||||||
|
return gameObject.AddComponent<HitReactionController>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ namespace Colosseum.Player
|
|||||||
private InputSystem_Actions inputActions;
|
private InputSystem_Actions inputActions;
|
||||||
private bool isJumping;
|
private bool isJumping;
|
||||||
private bool wasGrounded;
|
private bool wasGrounded;
|
||||||
|
private Vector3 forcedMovementVelocity;
|
||||||
|
private float forcedMovementRemaining;
|
||||||
|
|
||||||
// 클라이언트가 기록, 서버가 소비하는 월드 스페이스 이동 방향
|
// 클라이언트가 기록, 서버가 소비하는 월드 스페이스 이동 방향
|
||||||
private NetworkVariable<Vector2> netMoveInput = new NetworkVariable<Vector2>(
|
private NetworkVariable<Vector2> netMoveInput = new NetworkVariable<Vector2>(
|
||||||
@@ -48,6 +50,7 @@ namespace Colosseum.Player
|
|||||||
public float CurrentMoveSpeed => netMoveInput.Value.magnitude * moveSpeed * GetMoveSpeedMultiplier();
|
public float CurrentMoveSpeed => netMoveInput.Value.magnitude * moveSpeed * GetMoveSpeedMultiplier();
|
||||||
public bool IsGrounded => characterController != null ? characterController.isGrounded : false;
|
public bool IsGrounded => characterController != null ? characterController.isGrounded : false;
|
||||||
public bool IsJumping => isJumping;
|
public bool IsJumping => isJumping;
|
||||||
|
public bool IsForcedMoving => forcedMovementRemaining > 0f && forcedMovementVelocity.sqrMagnitude > 0.0001f;
|
||||||
|
|
||||||
public override void OnNetworkSpawn()
|
public override void OnNetworkSpawn()
|
||||||
{
|
{
|
||||||
@@ -115,6 +118,7 @@ namespace Colosseum.Player
|
|||||||
{
|
{
|
||||||
CleanupInputActions();
|
CleanupInputActions();
|
||||||
moveInput = Vector2.zero;
|
moveInput = Vector2.zero;
|
||||||
|
ClearForcedMovement();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
@@ -143,6 +147,7 @@ namespace Colosseum.Player
|
|||||||
public override void OnNetworkDespawn()
|
public override void OnNetworkDespawn()
|
||||||
{
|
{
|
||||||
CleanupInputActions();
|
CleanupInputActions();
|
||||||
|
ClearForcedMovement();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMovePerformed(InputAction.CallbackContext context) => moveInput = context.ReadValue<Vector2>();
|
private void OnMovePerformed(InputAction.CallbackContext context) => moveInput = context.ReadValue<Vector2>();
|
||||||
@@ -259,10 +264,12 @@ namespace Colosseum.Player
|
|||||||
{
|
{
|
||||||
if (characterController == null) return;
|
if (characterController == null) return;
|
||||||
|
|
||||||
|
Vector3 forcedDelta = ConsumeForcedMovementDelta(Time.deltaTime);
|
||||||
|
|
||||||
if (skillController != null && skillController.IsPlayingAnimation)
|
if (skillController != null && skillController.IsPlayingAnimation)
|
||||||
{
|
{
|
||||||
if (!skillController.UsesRootMotion)
|
if (!skillController.UsesRootMotion)
|
||||||
characterController.Move(velocity * Time.deltaTime);
|
characterController.Move(velocity * Time.deltaTime + forcedDelta);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,7 +285,7 @@ namespace Colosseum.Player
|
|||||||
moveDirection = Vector3.zero;
|
moveDirection = Vector3.zero;
|
||||||
|
|
||||||
float actualMoveSpeed = moveSpeed * GetMoveSpeedMultiplier();
|
float actualMoveSpeed = moveSpeed * GetMoveSpeedMultiplier();
|
||||||
characterController.Move((moveDirection * actualMoveSpeed + velocity) * Time.deltaTime);
|
characterController.Move((moveDirection * actualMoveSpeed + velocity) * Time.deltaTime + forcedDelta);
|
||||||
|
|
||||||
if (moveDirection != Vector3.zero)
|
if (moveDirection != Vector3.zero)
|
||||||
{
|
{
|
||||||
@@ -323,6 +330,33 @@ namespace Colosseum.Player
|
|||||||
return actionState.MoveSpeedMultiplier;
|
return actionState.MoveSpeedMultiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 넉백처럼 입력과 무관한 강제 이동을 적용합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void ApplyForcedMovement(Vector3 worldVelocity, float duration)
|
||||||
|
{
|
||||||
|
if (!IsServer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (duration <= 0f || worldVelocity.sqrMagnitude <= 0.0001f)
|
||||||
|
{
|
||||||
|
ClearForcedMovement();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
forcedMovementVelocity = worldVelocity;
|
||||||
|
forcedMovementRemaining = duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 강제 이동 상태를 즉시 초기화합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void ClearForcedMovement()
|
||||||
|
{
|
||||||
|
forcedMovementVelocity = Vector3.zero;
|
||||||
|
forcedMovementRemaining = 0f;
|
||||||
|
}
|
||||||
|
|
||||||
private PlayerActionState GetOrCreateActionState()
|
private PlayerActionState GetOrCreateActionState()
|
||||||
{
|
{
|
||||||
var foundState = GetComponent<PlayerActionState>();
|
var foundState = GetComponent<PlayerActionState>();
|
||||||
@@ -343,6 +377,7 @@ namespace Colosseum.Player
|
|||||||
if (!skillController.UsesRootMotion) return;
|
if (!skillController.UsesRootMotion) return;
|
||||||
|
|
||||||
Vector3 deltaPosition = animator.deltaPosition;
|
Vector3 deltaPosition = animator.deltaPosition;
|
||||||
|
Vector3 forcedDelta = ConsumeForcedMovementDelta(Time.deltaTime);
|
||||||
|
|
||||||
if (blockedDirection != Vector3.zero)
|
if (blockedDirection != Vector3.zero)
|
||||||
{
|
{
|
||||||
@@ -357,11 +392,11 @@ namespace Colosseum.Player
|
|||||||
if (skillController.IgnoreRootMotionY)
|
if (skillController.IgnoreRootMotionY)
|
||||||
{
|
{
|
||||||
deltaPosition.y = 0f;
|
deltaPosition.y = 0f;
|
||||||
characterController.Move(deltaPosition + velocity * Time.deltaTime);
|
characterController.Move(deltaPosition + velocity * Time.deltaTime + forcedDelta);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
characterController.Move(deltaPosition);
|
characterController.Move(deltaPosition + forcedDelta);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (animator.deltaRotation != Quaternion.identity)
|
if (animator.deltaRotation != Quaternion.identity)
|
||||||
@@ -372,5 +407,22 @@ namespace Colosseum.Player
|
|||||||
|
|
||||||
wasGrounded = characterController.isGrounded;
|
wasGrounded = characterController.isGrounded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Vector3 ConsumeForcedMovementDelta(float deltaTime)
|
||||||
|
{
|
||||||
|
if (forcedMovementRemaining <= 0f || forcedMovementVelocity.sqrMagnitude <= 0.0001f)
|
||||||
|
return Vector3.zero;
|
||||||
|
|
||||||
|
float appliedDeltaTime = Mathf.Min(deltaTime, forcedMovementRemaining);
|
||||||
|
forcedMovementRemaining = Mathf.Max(0f, forcedMovementRemaining - appliedDeltaTime);
|
||||||
|
|
||||||
|
Vector3 delta = forcedMovementVelocity * appliedDeltaTime;
|
||||||
|
if (forcedMovementRemaining <= 0f)
|
||||||
|
{
|
||||||
|
forcedMovementVelocity = Vector3.zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
return delta;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -176,9 +176,16 @@ namespace Colosseum.Player
|
|||||||
var movement = GetComponent<PlayerMovement>();
|
var movement = GetComponent<PlayerMovement>();
|
||||||
if (movement != null)
|
if (movement != null)
|
||||||
{
|
{
|
||||||
|
movement.ClearForcedMovement();
|
||||||
movement.enabled = false;
|
movement.enabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var hitReactionController = GetComponent<HitReactionController>();
|
||||||
|
if (hitReactionController != null)
|
||||||
|
{
|
||||||
|
hitReactionController.ClearHitReactionState();
|
||||||
|
}
|
||||||
|
|
||||||
// 스킬 입력 비활성화
|
// 스킬 입력 비활성화
|
||||||
var skillInput = GetComponent<PlayerSkillInput>();
|
var skillInput = GetComponent<PlayerSkillInput>();
|
||||||
if (skillInput != null)
|
if (skillInput != null)
|
||||||
@@ -190,7 +197,7 @@ namespace Colosseum.Player
|
|||||||
var skillController = GetComponent<SkillController>();
|
var skillController = GetComponent<SkillController>();
|
||||||
if (skillController != null)
|
if (skillController != null)
|
||||||
{
|
{
|
||||||
skillController.CancelSkill();
|
skillController.CancelSkill(SkillCancelReason.Death);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 모든 클라이언트에서 사망 애니메이션 재생
|
// 모든 클라이언트에서 사망 애니메이션 재생
|
||||||
@@ -222,9 +229,16 @@ namespace Colosseum.Player
|
|||||||
var movement = GetComponent<PlayerMovement>();
|
var movement = GetComponent<PlayerMovement>();
|
||||||
if (movement != null)
|
if (movement != null)
|
||||||
{
|
{
|
||||||
|
movement.ClearForcedMovement();
|
||||||
movement.enabled = true;
|
movement.enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var hitReactionController = GetComponent<HitReactionController>();
|
||||||
|
if (hitReactionController != null)
|
||||||
|
{
|
||||||
|
hitReactionController.ClearHitReactionState();
|
||||||
|
}
|
||||||
|
|
||||||
// 스킬 입력 재활성화
|
// 스킬 입력 재활성화
|
||||||
var skillInput = GetComponent<PlayerSkillInput>();
|
var skillInput = GetComponent<PlayerSkillInput>();
|
||||||
if (skillInput != null)
|
if (skillInput != null)
|
||||||
@@ -239,6 +253,12 @@ namespace Colosseum.Player
|
|||||||
animator.Rebind();
|
animator.Rebind();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var skillController = GetComponent<SkillController>();
|
||||||
|
if (skillController != null)
|
||||||
|
{
|
||||||
|
skillController.CancelSkill(SkillCancelReason.Respawn);
|
||||||
|
}
|
||||||
|
|
||||||
OnRespawned?.Invoke(this);
|
OnRespawned?.Invoke(this);
|
||||||
|
|
||||||
Debug.Log($"[Player] Player {OwnerClientId} respawned!");
|
Debug.Log($"[Player] Player {OwnerClientId} respawned!");
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
using Colosseum.Player;
|
||||||
|
|
||||||
|
namespace Colosseum.Skills.Effects
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 대상에게 다운 상태를 적용하는 스킬 효과입니다.
|
||||||
|
/// </summary>
|
||||||
|
[CreateAssetMenu(fileName = "DownEffect", menuName = "Colosseum/Skills/Effects/Down")]
|
||||||
|
public class DownEffect : SkillEffect
|
||||||
|
{
|
||||||
|
[Header("Settings")]
|
||||||
|
[Tooltip("다운 지속 시간")]
|
||||||
|
[Min(0f)] [SerializeField] private float duration = 1f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 다운 지속 시간
|
||||||
|
/// </summary>
|
||||||
|
public float Duration => duration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 다운 효과를 적용합니다.
|
||||||
|
/// </summary>
|
||||||
|
protected override void ApplyEffect(GameObject source, GameObject target)
|
||||||
|
{
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[DownEffect] Target is null.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!target.TryGetComponent(out HitReactionController hitReactionController))
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[DownEffect] HitReactionController not found on target: {target.name}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hitReactionController.ApplyDown(duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 41c96b54a96cdb84c9bda774775b0a1a
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
|
using Colosseum.Player;
|
||||||
|
|
||||||
namespace Colosseum.Skills.Effects
|
namespace Colosseum.Skills.Effects
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -11,6 +13,7 @@ namespace Colosseum.Skills.Effects
|
|||||||
[Header("Knockback Settings")]
|
[Header("Knockback Settings")]
|
||||||
[Min(0f)] [SerializeField] private float force = 5f;
|
[Min(0f)] [SerializeField] private float force = 5f;
|
||||||
[SerializeField] private float upwardForce = 2f;
|
[SerializeField] private float upwardForce = 2f;
|
||||||
|
[Min(0.05f)] [SerializeField] private float duration = 0.2f;
|
||||||
|
|
||||||
protected override void ApplyEffect(GameObject caster, GameObject target)
|
protected override void ApplyEffect(GameObject caster, GameObject target)
|
||||||
{
|
{
|
||||||
@@ -18,13 +21,23 @@ namespace Colosseum.Skills.Effects
|
|||||||
|
|
||||||
Vector3 direction = target.transform.position - caster.transform.position;
|
Vector3 direction = target.transform.position - caster.transform.position;
|
||||||
direction.y = 0f;
|
direction.y = 0f;
|
||||||
|
if (direction.sqrMagnitude <= 0.0001f)
|
||||||
|
direction = caster.transform.forward;
|
||||||
|
else
|
||||||
direction.Normalize();
|
direction.Normalize();
|
||||||
|
|
||||||
Vector3 knockback = direction * force + Vector3.up * upwardForce;
|
Vector3 knockbackVelocity = direction * force + Vector3.up * upwardForce;
|
||||||
|
|
||||||
// TODO: 실제 물리 시스템 연동
|
if (target.TryGetComponent<HitReactionController>(out var hitReactionController))
|
||||||
// if (target.TryGetComponent<Rigidbody>(out var rb))
|
{
|
||||||
// rb.AddForce(knockback, ForceMode.Impulse);
|
hitReactionController.ApplyKnockback(knockbackVelocity, duration);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.TryGetComponent<PlayerMovement>(out var playerMovement))
|
||||||
|
{
|
||||||
|
playerMovement.ApplyForcedMovement(knockbackVelocity, duration);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,19 @@ using Unity.Netcode;
|
|||||||
|
|
||||||
namespace Colosseum.Skills
|
namespace Colosseum.Skills
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 스킬 강제 취소 이유
|
||||||
|
/// </summary>
|
||||||
|
public enum SkillCancelReason
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Manual,
|
||||||
|
Death,
|
||||||
|
Stun,
|
||||||
|
HitReaction,
|
||||||
|
Respawn,
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 스킬 실행을 관리하는 컴포넌트.
|
/// 스킬 실행을 관리하는 컴포넌트.
|
||||||
/// 애니메이션 이벤트 기반으로 효과가 발동됩니다.
|
/// 애니메이션 이벤트 기반으로 효과가 발동됩니다.
|
||||||
@@ -31,6 +44,12 @@ namespace Colosseum.Skills
|
|||||||
[Tooltip("범위 표시 지속 시간")]
|
[Tooltip("범위 표시 지속 시간")]
|
||||||
[Min(0.1f)] [SerializeField] private float debugDrawDuration = 1f;
|
[Min(0.1f)] [SerializeField] private float debugDrawDuration = 1f;
|
||||||
|
|
||||||
|
[Header("디버그")]
|
||||||
|
[Tooltip("마지막으로 강제 취소된 스킬 이름")]
|
||||||
|
[SerializeField] private string lastCancelledSkillName = string.Empty;
|
||||||
|
[Tooltip("마지막 강제 취소 이유")]
|
||||||
|
[SerializeField] private SkillCancelReason lastCancelReason = SkillCancelReason.None;
|
||||||
|
|
||||||
// 현재 실행 중인 스킬
|
// 현재 실행 중인 스킬
|
||||||
private SkillData currentSkill;
|
private SkillData currentSkill;
|
||||||
private bool skillEndRequested; // OnSkillEnd 이벤트 호출 여부
|
private bool skillEndRequested; // OnSkillEnd 이벤트 호출 여부
|
||||||
@@ -47,9 +66,14 @@ namespace Colosseum.Skills
|
|||||||
public bool IgnoreRootMotionY => currentSkill != null && currentSkill.IgnoreRootMotionY;
|
public bool IgnoreRootMotionY => currentSkill != null && currentSkill.IgnoreRootMotionY;
|
||||||
public SkillData CurrentSkill => currentSkill;
|
public SkillData CurrentSkill => currentSkill;
|
||||||
public Animator Animator => animator;
|
public Animator Animator => animator;
|
||||||
|
public SkillCancelReason LastCancelReason => lastCancelReason;
|
||||||
|
public string LastCancelledSkillName => lastCancelledSkillName;
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
|
lastCancelledSkillName = string.Empty;
|
||||||
|
lastCancelReason = SkillCancelReason.None;
|
||||||
|
|
||||||
if (animator == null)
|
if (animator == null)
|
||||||
{
|
{
|
||||||
animator = GetComponentInChildren<Animator>();
|
animator = GetComponentInChildren<Animator>();
|
||||||
@@ -135,6 +159,7 @@ namespace Colosseum.Skills
|
|||||||
currentSkill = skill;
|
currentSkill = skill;
|
||||||
skillEndRequested = false;
|
skillEndRequested = false;
|
||||||
waitingForEndAnimation = false;
|
waitingForEndAnimation = false;
|
||||||
|
lastCancelReason = SkillCancelReason.None;
|
||||||
|
|
||||||
if (debugMode) Debug.Log($"[Skill] Cast: {skill.SkillName}");
|
if (debugMode) Debug.Log($"[Skill] Cast: {skill.SkillName}");
|
||||||
|
|
||||||
@@ -339,16 +364,24 @@ namespace Colosseum.Skills
|
|||||||
if (debugMode) Debug.Log($"[Skill] End requested: {currentSkill.SkillName} (will complete after animation)");
|
if (debugMode) Debug.Log($"[Skill] End requested: {currentSkill.SkillName} (will complete after animation)");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CancelSkill()
|
/// <summary>
|
||||||
|
/// 현재 스킬을 강제 취소합니다.
|
||||||
|
/// </summary>
|
||||||
|
public bool CancelSkill(SkillCancelReason reason = SkillCancelReason.Manual)
|
||||||
{
|
{
|
||||||
if (currentSkill != null)
|
if (currentSkill == null)
|
||||||
{
|
return false;
|
||||||
if (debugMode) Debug.Log($"Skill cancelled: {currentSkill.SkillName}");
|
|
||||||
|
lastCancelledSkillName = currentSkill.SkillName;
|
||||||
|
lastCancelReason = reason;
|
||||||
|
|
||||||
|
Debug.Log($"[Skill] Cancelled: {currentSkill.SkillName} / reason={reason}");
|
||||||
|
|
||||||
RestoreBaseController();
|
RestoreBaseController();
|
||||||
currentSkill = null;
|
currentSkill = null;
|
||||||
skillEndRequested = false;
|
skillEndRequested = false;
|
||||||
waitingForEndAnimation = false;
|
waitingForEndAnimation = false;
|
||||||
}
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsOnCooldown(SkillData skill)
|
public bool IsOnCooldown(SkillData skill)
|
||||||
|
|||||||
Reference in New Issue
Block a user