Compare commits
6 Commits
c7165a7ef4
...
d5d4b2f125
| Author | SHA1 | Date | |
|---|---|---|---|
| d5d4b2f125 | |||
| 671f8d8a25 | |||
| 1cb46e1d8d | |||
| 2faf41a0bb | |||
| 9f27d87c0e | |||
| 1adfd61d97 |
@@ -2491,15 +2491,15 @@ PrefabInstance:
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 170188453522781500, guid: 54087b4bd46db9e4fb7da13cf7a6cc69, type: 3}
|
||||
propertyPath: m_AnchorMax.x
|
||||
value: 0
|
||||
value: 0.2
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 170188453522781500, guid: 54087b4bd46db9e4fb7da13cf7a6cc69, type: 3}
|
||||
propertyPath: m_AnchorMax.y
|
||||
value: 0
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 170188453522781500, guid: 54087b4bd46db9e4fb7da13cf7a6cc69, type: 3}
|
||||
propertyPath: m_AnchorMin.x
|
||||
value: 0
|
||||
value: 0.2
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 222507439395443271, guid: 54087b4bd46db9e4fb7da13cf7a6cc69, type: 3}
|
||||
propertyPath: m_Enabled
|
||||
@@ -2579,15 +2579,15 @@ PrefabInstance:
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2668790415109567114, guid: 54087b4bd46db9e4fb7da13cf7a6cc69, type: 3}
|
||||
propertyPath: m_AnchorMax.x
|
||||
value: 0
|
||||
value: 0.2
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2668790415109567114, guid: 54087b4bd46db9e4fb7da13cf7a6cc69, type: 3}
|
||||
propertyPath: m_AnchorMax.y
|
||||
value: 0
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2668790415109567114, guid: 54087b4bd46db9e4fb7da13cf7a6cc69, type: 3}
|
||||
propertyPath: m_AnchorMin.x
|
||||
value: 0
|
||||
value: 0.2
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3299919758736932218, guid: 54087b4bd46db9e4fb7da13cf7a6cc69, type: 3}
|
||||
propertyPath: m_SizeDelta.x
|
||||
@@ -4738,8 +4738,8 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.UI.ConnectionUI
|
||||
ipAddress: 127.0.0.1
|
||||
port: 7788
|
||||
autoStartHostInEditor: 0
|
||||
port: 7777
|
||||
autoStartHostInEditor: 1
|
||||
connectionStatus: Disconnected
|
||||
--- !u!4 &854739757 stripped
|
||||
Transform:
|
||||
@@ -6796,6 +6796,19 @@ Transform:
|
||||
m_CorrespondingSourceObject: {fileID: 7132605379903659868, guid: 5b4ac53b97612ae4392b84786de0d50d, type: 3}
|
||||
m_PrefabInstance: {fileID: 1329389111}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!134 &1236586838
|
||||
PhysicsMaterial:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: ' (Instance)'
|
||||
serializedVersion: 2
|
||||
m_DynamicFriction: 0.6
|
||||
m_StaticFriction: 0.6
|
||||
m_Bounciness: 0
|
||||
m_FrictionCombine: 0
|
||||
m_BounceCombine: 0
|
||||
--- !u!1001 &1236816272
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -7252,6 +7265,18 @@ PrefabInstance:
|
||||
propertyPath: m_AnchorMax.y
|
||||
value: 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_RemovedGameObjects: []
|
||||
m_AddedGameObjects: []
|
||||
@@ -11385,19 +11410,6 @@ Transform:
|
||||
m_CorrespondingSourceObject: {fileID: 1824081129582740912, guid: bf62c127a7934334d9b955503391f574, type: 3}
|
||||
m_PrefabInstance: {fileID: 649018401}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!134 &1976569495
|
||||
PhysicsMaterial:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name:
|
||||
serializedVersion: 2
|
||||
m_DynamicFriction: 0.6
|
||||
m_StaticFriction: 0.6
|
||||
m_Bounciness: 0
|
||||
m_FrictionCombine: 0
|
||||
m_BounceCombine: 0
|
||||
--- !u!1001 &1986213921
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -11470,18 +11482,18 @@ PrefabInstance:
|
||||
- target: {fileID: -4476104823835307179, guid: 56986b707b0dc09439cb35ff2f87dcc9, type: 3}
|
||||
propertyPath: m_Material
|
||||
value:
|
||||
objectReference: {fileID: 1976569495}
|
||||
objectReference: {fileID: 1236586838}
|
||||
- target: {fileID: 3122493901255878198, guid: 56986b707b0dc09439cb35ff2f87dcc9, type: 3}
|
||||
propertyPath: m_LocalPosition.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3122493901255878198, guid: 56986b707b0dc09439cb35ff2f87dcc9, type: 3}
|
||||
propertyPath: m_LocalPosition.y
|
||||
value: -0
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3122493901255878198, guid: 56986b707b0dc09439cb35ff2f87dcc9, type: 3}
|
||||
propertyPath: m_LocalPosition.z
|
||||
value: 15
|
||||
value: 8
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3122493901255878198, guid: 56986b707b0dc09439cb35ff2f87dcc9, type: 3}
|
||||
propertyPath: m_LocalRotation.w
|
||||
@@ -11511,13 +11523,9 @@ PrefabInstance:
|
||||
propertyPath: m_LocalEulerAnglesHint.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5153439431748782209, guid: 56986b707b0dc09439cb35ff2f87dcc9, type: 3}
|
||||
propertyPath: m_BaseOffset
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5581648761285601425, guid: 56986b707b0dc09439cb35ff2f87dcc9, type: 3}
|
||||
propertyPath: m_Name
|
||||
value: TestBoss
|
||||
value: Drog
|
||||
objectReference: {fileID: 0}
|
||||
m_RemovedComponents: []
|
||||
m_RemovedGameObjects: []
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
%YAML 1.1
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &-6758363554468061369
|
||||
MonoBehaviour:
|
||||
@@ -10,7 +10,7 @@ MonoBehaviour:
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 5b27bb6d9a2c8d540a10dff10acc543e, type: 3}
|
||||
m_Name: BT_TestBoss Debug Info
|
||||
m_Name: BT_Drog Debug Info
|
||||
m_EditorClassIdentifier: Unity.Behavior::Unity.Behavior.BehaviorGraphDebugInfo
|
||||
m_CodeBreakPointsList: []
|
||||
--- !u!114 &-6591390698989283165
|
||||
@@ -23,7 +23,7 @@ MonoBehaviour:
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 2dd922ae02c94c87a66e46a10a7319b9, type: 3}
|
||||
m_Name: BT_TestBoss Blackboard
|
||||
m_Name: BT_Drog Blackboard
|
||||
m_EditorClassIdentifier: Unity.Behavior.Authoring::Unity.Behavior.BehaviorBlackboardAuthoringAsset
|
||||
AssetID:
|
||||
m_Value0: 11861793134961942170
|
||||
@@ -112,7 +112,7 @@ MonoBehaviour:
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: db920e62f70f420bb33c771449926fa4, type: 3}
|
||||
m_Name: BT_TestBoss
|
||||
m_Name: BT_Drog
|
||||
m_EditorClassIdentifier: Unity.Behavior::Unity.Behavior.BehaviorGraph
|
||||
Graphs:
|
||||
- rid: 8805855836547055693
|
||||
@@ -595,7 +595,7 @@ MonoBehaviour:
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: bad8f2220607dac4db5082ff333fafb8, type: 3}
|
||||
m_Name: BT_TestBoss
|
||||
m_Name: BT_Drog
|
||||
m_EditorClassIdentifier: Unity.Behavior.Authoring::Unity.Behavior.BehaviorAuthoringGraph
|
||||
Blackboard: {fileID: -6591390698989283165}
|
||||
m_Description:
|
||||
@@ -2417,7 +2417,7 @@ MonoBehaviour:
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 5c02bb70996b49eba31d0c206e28da24, type: 3}
|
||||
m_Name: BT_TestBoss Blackboard
|
||||
m_Name: BT_Drog Blackboard
|
||||
m_EditorClassIdentifier: Unity.Behavior::Unity.Behavior.RuntimeBlackboardAsset
|
||||
VersionTimestamp: 639094519736977735
|
||||
AssetID:
|
||||
@@ -2483,3 +2483,4 @@ MonoBehaviour:
|
||||
m_Value1: 14990413519379198129
|
||||
Name: Die
|
||||
m_Value: 0
|
||||
|
||||
BIN
Assets/_Game/Animations/Anim_Common_경직.fbx
Normal file
BIN
Assets/_Game/Animations/Anim_Common_경직.fbx
Normal file
Binary file not shown.
1564
Assets/_Game/Animations/Anim_Common_경직.fbx.meta
Normal file
1564
Assets/_Game/Animations/Anim_Common_경직.fbx.meta
Normal file
File diff suppressed because it is too large
Load Diff
BIN
Assets/_Game/Animations/Anim_Common_다운루프.fbx
Normal file
BIN
Assets/_Game/Animations/Anim_Common_다운루프.fbx
Normal file
Binary file not shown.
1564
Assets/_Game/Animations/Anim_Common_다운루프.fbx.meta
Normal file
1564
Assets/_Game/Animations/Anim_Common_다운루프.fbx.meta
Normal file
File diff suppressed because it is too large
Load Diff
BIN
Assets/_Game/Animations/Anim_Common_다운시작.fbx
Normal file
BIN
Assets/_Game/Animations/Anim_Common_다운시작.fbx
Normal file
Binary file not shown.
1564
Assets/_Game/Animations/Anim_Common_다운시작.fbx.meta
Normal file
1564
Assets/_Game/Animations/Anim_Common_다운시작.fbx.meta
Normal file
File diff suppressed because it is too large
Load Diff
BIN
Assets/_Game/Animations/Anim_Common_다운회복.fbx
Normal file
BIN
Assets/_Game/Animations/Anim_Common_다운회복.fbx
Normal file
Binary file not shown.
1564
Assets/_Game/Animations/Anim_Common_다운회복.fbx.meta
Normal file
1564
Assets/_Game/Animations/Anim_Common_다운회복.fbx.meta
Normal file
File diff suppressed because it is too large
Load Diff
@@ -56,6 +56,13 @@ ModelImporter:
|
||||
curves: []
|
||||
events:
|
||||
- time: 0.35211262
|
||||
functionName: OnEffect
|
||||
data:
|
||||
objectReferenceParameter: {instanceID: 0}
|
||||
floatParameter: 0
|
||||
intParameter: 1
|
||||
messageOptions: 0
|
||||
- time: 0.3636364
|
||||
functionName: OnEffect
|
||||
data:
|
||||
objectReferenceParameter: {instanceID: 0}
|
||||
@@ -79,11 +79,21 @@ AnimatorStateMachine:
|
||||
- serializedVersion: 1
|
||||
m_State: {fileID: -1032254959699123210}
|
||||
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_AnyStateTransitions:
|
||||
- {fileID: -754003289131015157}
|
||||
- {fileID: 469741948129995159}
|
||||
- {fileID: 6228136561094308872}
|
||||
- {fileID: 1111345123456789021}
|
||||
m_EntryTransitions: []
|
||||
m_StateMachineTransitions: {}
|
||||
m_StateMachineBehaviours: []
|
||||
@@ -323,6 +333,18 @@ AnimatorController:
|
||||
m_DefaultInt: 0
|
||||
m_DefaultBool: 0
|
||||
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:
|
||||
- serializedVersion: 5
|
||||
m_Name: Base Layer
|
||||
@@ -437,6 +459,181 @@ AnimatorStateTransition:
|
||||
m_InterruptionSource: 0
|
||||
m_OrderedInterruption: 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
|
||||
AnimatorState:
|
||||
serializedVersion: 6
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
%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: b08cc671f858a3b409170a5356e960a0, type: 3}
|
||||
m_Name: Data_Abnormality_Player_Invincible
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Abnormalities.AbnormalityData
|
||||
abnormalityName: "\uBB34\uC801"
|
||||
icon: {fileID: 0}
|
||||
duration: 1
|
||||
level: 1
|
||||
isDebuff: 0
|
||||
statModifiers: []
|
||||
periodicInterval: 0
|
||||
periodicValue: 0
|
||||
controlType: 4
|
||||
slowMultiplier: 0.5
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a1f2b3c4d5e6f708192a3b4c5d6e7f8
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,4 +1,4 @@
|
||||
%YAML 1.1
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
@@ -10,7 +10,7 @@ MonoBehaviour:
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 1ecdc2379b078b246a0bd5c0fb58e346, type: 3}
|
||||
m_Name: Data_Enemy_TestBoss
|
||||
m_Name: Data_Enemy_Drog
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Enemy.EnemyData
|
||||
enemyName: Boss The Test
|
||||
description:
|
||||
@@ -26,3 +26,4 @@ MonoBehaviour:
|
||||
aggroRange: 10
|
||||
attackRange: 2
|
||||
attackCooldown: 1
|
||||
|
||||
20
Assets/_Game/Data/Patterns/Data_Pattern_Drog_다운추가타.asset
Normal file
20
Assets/_Game/Data/Patterns/Data_Pattern_Drog_다운추가타.asset
Normal file
@@ -0,0 +1,20 @@
|
||||
%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: 0ce956e0878565343974c31b8111c0c6, type: 3}
|
||||
m_Name: "Data_Pattern_Drog_\uB2E4\uC6B4\uCD94\uAC00\uD0C0"
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.AI.BossPatternData
|
||||
patternName: "\uB2E4\uC6B4 \uCD94\uAC00\uD0C0"
|
||||
steps:
|
||||
- Type: 0
|
||||
Skill: {fileID: 11400000, guid: fd7a5d5cc9494229b0c475e7f0cda218, type: 2}
|
||||
Duration: 0
|
||||
cooldown: 2.5
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fe5100f855d14c0faac44b6d4f2c771e
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,4 +1,4 @@
|
||||
%YAML 1.1
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
@@ -10,7 +10,7 @@ MonoBehaviour:
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 0ce956e0878565343974c31b8111c0c6, type: 3}
|
||||
m_Name: "Data_Pattern_TestBoss_\uC57D2\uD0C0-\uC2A4\uC719"
|
||||
m_Name: "Data_Pattern_Drog_\uC57D2\uD0C0-\uC2A4\uC719"
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.AI.BossPatternData
|
||||
patternName: "\uAE30\uBCF8 \uD328\uD134"
|
||||
steps:
|
||||
@@ -24,3 +24,4 @@ MonoBehaviour:
|
||||
Skill: {fileID: 11400000, guid: 3acbf1c5ec71bef4bb13f8534605d554, type: 2}
|
||||
Duration: 0.5
|
||||
cooldown: 5
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
%YAML 1.1
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
@@ -10,7 +10,7 @@ MonoBehaviour:
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 0ce956e0878565343974c31b8111c0c6, type: 3}
|
||||
m_Name: "Data_Pattern_TestBoss_\uC810\uD504"
|
||||
m_Name: "Data_Pattern_Drog_\uC810\uD504"
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.AI.BossPatternData
|
||||
patternName: "\uAE30\uBCF8 \uD328\uD134"
|
||||
steps:
|
||||
@@ -24,3 +24,4 @@ MonoBehaviour:
|
||||
Skill: {fileID: 11400000, guid: 16321efbd1f2498458683bac7605b054, type: 2}
|
||||
Duration: 0
|
||||
cooldown: 1
|
||||
|
||||
27
Assets/_Game/Data/Skills/Data_Skill_Drog_다운추가타.asset
Normal file
27
Assets/_Game/Data/Skills/Data_Skill_Drog_다운추가타.asset
Normal file
@@ -0,0 +1,27 @@
|
||||
%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: 94f0a76cebcac2f4fb5daf1b675fd79f, type: 3}
|
||||
m_Name: "Data_Skill_Drog_\uB2E4\uC6B4\uCD94\uAC00\uD0C0"
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Skills.SkillData
|
||||
skillName: "\uB2E4\uC6B4 \uCD94\uAC00\uD0C0"
|
||||
description:
|
||||
icon: {fileID: 0}
|
||||
skillClip: {fileID: -242498254790479478, guid: 585e8961b6c6e9f4ba96bdb4ffb2cbc3, type: 3}
|
||||
endClip: {fileID: 3627526391332626453, guid: 39aaec38fc96c4842b972f1e991e5a46, type: 3}
|
||||
animationSpeed: 1
|
||||
useRootMotion: 1
|
||||
ignoreRootMotionY: 1
|
||||
jumpToTarget: 0
|
||||
cooldown: 0
|
||||
manaCost: 0
|
||||
effects:
|
||||
- {fileID: 11400000, guid: 0f134a897a7e4d0e98c8d9058b1d79d1, type: 2}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fd7a5d5cc9494229b0c475e7f0cda218
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,4 +1,4 @@
|
||||
%YAML 1.1
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
@@ -10,7 +10,7 @@ MonoBehaviour:
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 94f0a76cebcac2f4fb5daf1b675fd79f, type: 3}
|
||||
m_Name: "Data_Skill_TestBoss_\uC2A4\uC719"
|
||||
m_Name: "Data_Skill_Drog_\uC2A4\uC719"
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Skills.SkillData
|
||||
skillName: "\uC2A4\uC719"
|
||||
description:
|
||||
@@ -23,3 +23,4 @@ MonoBehaviour:
|
||||
manaCost: 0
|
||||
effects:
|
||||
- {fileID: 11400000, guid: 94b0f3305cea88c458a56783a486340e, type: 2}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
%YAML 1.1
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
@@ -10,7 +10,7 @@ MonoBehaviour:
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 94f0a76cebcac2f4fb5daf1b675fd79f, type: 3}
|
||||
m_Name: "Data_Skill_TestBoss_\uC624\uB978\uC190\uCE58\uAE30"
|
||||
m_Name: "Data_Skill_Drog_\uC624\uB978\uC190\uCE58\uAE30"
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Skills.SkillData
|
||||
skillName: "\uC624\uB978\uC190\uCE58\uAE30"
|
||||
description:
|
||||
@@ -23,3 +23,4 @@ MonoBehaviour:
|
||||
manaCost: 0
|
||||
effects:
|
||||
- {fileID: 11400000, guid: 86bf7e282c1639c4889910475aaccdef, type: 2}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
%YAML 1.1
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
@@ -10,7 +10,7 @@ MonoBehaviour:
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 94f0a76cebcac2f4fb5daf1b675fd79f, type: 3}
|
||||
m_Name: "Data_Skill_TestBoss_\uC624\uB978\uC190\uCE58\uAE302"
|
||||
m_Name: "Data_Skill_Drog_\uC624\uB978\uC190\uCE58\uAE302"
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Skills.SkillData
|
||||
skillName: "\uC624\uB978\uC190\uCE58\uAE302"
|
||||
description:
|
||||
@@ -23,3 +23,5 @@ MonoBehaviour:
|
||||
manaCost: 0
|
||||
effects:
|
||||
- {fileID: 11400000, guid: 87b064a0134987b4b9638e184ab07411, type: 2}
|
||||
- {fileID: 11400000, guid: 0dba6dca651743bc84b0df42f9dbd290, type: 2}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
%YAML 1.1
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
@@ -10,7 +10,7 @@ MonoBehaviour:
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 94f0a76cebcac2f4fb5daf1b675fd79f, type: 3}
|
||||
m_Name: "Data_Skill_TestBoss_\uC810\uD504"
|
||||
m_Name: "Data_Skill_Drog_\uC810\uD504"
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Skills.SkillData
|
||||
skillName: "\uC810\uD504"
|
||||
description:
|
||||
@@ -25,3 +25,4 @@ MonoBehaviour:
|
||||
manaCost: 0
|
||||
effects:
|
||||
- {fileID: 0}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
%YAML 1.1
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
@@ -10,7 +10,7 @@ MonoBehaviour:
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 94f0a76cebcac2f4fb5daf1b675fd79f, type: 3}
|
||||
m_Name: "Data_Skill_TestBoss_\uC810\uD504\uC900\uBE44"
|
||||
m_Name: "Data_Skill_Drog_\uC810\uD504\uC900\uBE44"
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Skills.SkillData
|
||||
skillName: "\uC810\uD504"
|
||||
description:
|
||||
@@ -25,3 +25,4 @@ MonoBehaviour:
|
||||
manaCost: 0
|
||||
effects:
|
||||
- {fileID: 0}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
%YAML 1.1
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
@@ -10,7 +10,7 @@ MonoBehaviour:
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 94f0a76cebcac2f4fb5daf1b675fd79f, type: 3}
|
||||
m_Name: "Data_Skill_TestBoss_\uC810\uD504\uCC29\uC9C0"
|
||||
m_Name: "Data_Skill_Drog_\uC810\uD504\uCC29\uC9C0"
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Skills.SkillData
|
||||
skillName: "\uC810\uD504"
|
||||
description:
|
||||
@@ -25,3 +25,4 @@ MonoBehaviour:
|
||||
manaCost: 0
|
||||
effects:
|
||||
- {fileID: 11400000, guid: 11bd2d1ebdbfc2f4abf5a9d886615eb3, type: 2}
|
||||
|
||||
@@ -19,6 +19,10 @@ MonoBehaviour:
|
||||
endClip: {fileID: 0}
|
||||
useRootMotion: 1
|
||||
ignoreRootMotionY: 1
|
||||
blockMovementWhileCasting: 1
|
||||
blockJumpWhileCasting: 1
|
||||
blockOtherSkillsWhileCasting: 1
|
||||
cooldown: 10
|
||||
manaCost: 0
|
||||
effects: []
|
||||
effects:
|
||||
- {fileID: 11400000, guid: 8b2c3d4e5f60718293a4b5c6d7e8f901, type: 2}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
%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: 6fd8c6f4c4454ecdb111d13dc8471024, type: 3}
|
||||
m_Name: "Data_SkillEffect_Drog_\uB2E4\uC6B4\uCD94\uAC00\uD0C0_0_\uD53C\uACA9\uAC00\uC911\uB370\uBBF8\uC9C0"
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Skills.Effects.HitReactionDamageEffect
|
||||
targetType: 1
|
||||
targetTeam: 0
|
||||
areaCenter: 0
|
||||
areaShape: 0
|
||||
targetLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
areaRadius: 3.25
|
||||
fanOriginDistance: 1
|
||||
fanRadius: 3
|
||||
fanHalfAngle: 45
|
||||
baseDamage: 1
|
||||
damageType: 0
|
||||
statScaling: 1
|
||||
bonusAgainstDownedTarget: 1
|
||||
downedDamageMultiplier: 1.5
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0f134a897a7e4d0e98c8d9058b1d79d1
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,4 +1,4 @@
|
||||
%YAML 1.1
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
@@ -10,7 +10,7 @@ MonoBehaviour:
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 58efb3c775496fa40b801b21127a011e, type: 3}
|
||||
m_Name: "Data_SkillEffect_TestBoss_\uC2A4\uC719_0_\uB370\uBBF8\uC9C0"
|
||||
m_Name: "Data_SkillEffect_Drog_\uC2A4\uC719_0_\uB370\uBBF8\uC9C0"
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Skills.Effects.DamageEffect
|
||||
targetType: 1
|
||||
targetTeam: 0
|
||||
@@ -26,3 +26,4 @@ MonoBehaviour:
|
||||
baseDamage: 1
|
||||
damageType: 0
|
||||
statScaling: 1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
%YAML 1.1
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
@@ -10,7 +10,7 @@ MonoBehaviour:
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 58efb3c775496fa40b801b21127a011e, type: 3}
|
||||
m_Name: "Data_SkillEffect_TestBoss_\uC624\uB978\uC190\uCE58\uAE302_0_\uB370\uBBF8\uC9C0"
|
||||
m_Name: "Data_SkillEffect_Drog_\uC624\uB978\uC190\uCE58\uAE302_0_\uB370\uBBF8\uC9C0"
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Skills.Effects.DamageEffect
|
||||
targetType: 1
|
||||
targetTeam: 0
|
||||
@@ -26,3 +26,4 @@ MonoBehaviour:
|
||||
baseDamage: 1
|
||||
damageType: 0
|
||||
statScaling: 1
|
||||
|
||||
@@ -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_Drog_\uC624\uB978\uC190\uCE58\uAE302_1_\uB2E4\uC6B4"
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Skills.Effects.DownEffect
|
||||
targetType: 1
|
||||
targetTeam: 0
|
||||
areaCenter: 0
|
||||
areaShape: 1
|
||||
targetLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
areaRadius: 3
|
||||
fanOriginDistance: 0
|
||||
fanRadius: 3
|
||||
fanHalfAngle: 45
|
||||
duration: 2.5
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0dba6dca651743bc84b0df42f9dbd290
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,4 +1,4 @@
|
||||
%YAML 1.1
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
@@ -10,7 +10,7 @@ MonoBehaviour:
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 58efb3c775496fa40b801b21127a011e, type: 3}
|
||||
m_Name: "Data_SkillEffect_TestBoss_\uC624\uB978\uC190\uCE58\uAE30_0_\uB370\uBBF8\uC9C0"
|
||||
m_Name: "Data_SkillEffect_Drog_\uC624\uB978\uC190\uCE58\uAE30_0_\uB370\uBBF8\uC9C0"
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Skills.Effects.DamageEffect
|
||||
targetType: 1
|
||||
targetTeam: 0
|
||||
@@ -26,3 +26,4 @@ MonoBehaviour:
|
||||
baseDamage: 1
|
||||
damageType: 0
|
||||
statScaling: 1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
%YAML 1.1
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
@@ -10,7 +10,7 @@ MonoBehaviour:
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 58efb3c775496fa40b801b21127a011e, type: 3}
|
||||
m_Name: "Data_SkillEffect_TestBoss_\uC810\uD504_0_\uB370\uBBF8\uC9C0"
|
||||
m_Name: "Data_SkillEffect_Drog_\uC810\uD504_0_\uB370\uBBF8\uC9C0"
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Skills.Effects.DamageEffect
|
||||
targetType: 1
|
||||
targetTeam: 0
|
||||
@@ -26,3 +26,4 @@ MonoBehaviour:
|
||||
baseDamage: 1
|
||||
damageType: 0
|
||||
statScaling: 1
|
||||
|
||||
@@ -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:
|
||||
@@ -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: bf750718c64c4bd48af905d2927351de, type: 3}
|
||||
m_Name: "Data_SkillEffect_Player_\uBB34\uC801"
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Skills.Effects.AbnormalityEffect
|
||||
targetType: 0
|
||||
targetTeam: 0
|
||||
areaCenter: 0
|
||||
areaShape: 0
|
||||
targetLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
areaRadius: 3
|
||||
fanOriginDistance: 1
|
||||
fanRadius: 3
|
||||
fanHalfAngle: 45
|
||||
abnormalityData: {fileID: 11400000, guid: 7a1f2b3c4d5e6f708192a3b4c5d6e7f8, type: 2}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b2c3d4e5f60718293a4b5c6d7e8f901
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,4 +1,4 @@
|
||||
%YAML 1.1
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &1008926773928116
|
||||
GameObject:
|
||||
@@ -1364,7 +1364,7 @@ GameObject:
|
||||
- component: {fileID: 4373196678005266}
|
||||
- component: {fileID: 95998111322576624}
|
||||
m_Layer: 0
|
||||
m_Name: Model_Enemy_TestBoss
|
||||
m_Name: Model_Enemy_Drog
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
@@ -3285,3 +3285,4 @@ Transform:
|
||||
m_CorrespondingSourceObject: {fileID: 8583892753143838813, guid: 95219051c0a1142448e9ed4beee02fe1, type: 3}
|
||||
m_PrefabInstance: {fileID: 8587889261130452289}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
|
||||
@@ -20,7 +20,6 @@ GameObject:
|
||||
- component: {fileID: 11400005}
|
||||
- component: {fileID: 11400006}
|
||||
- component: {fileID: 3792588902782784034}
|
||||
- component: {fileID: 9069822911508997612}
|
||||
m_Layer: 0
|
||||
m_Name: Prefab_Boss_BossTemplate
|
||||
m_TagString: Untagged
|
||||
@@ -291,23 +290,3 @@ MonoBehaviour:
|
||||
SwitchTransformSpaceWhenParented: 0
|
||||
Interpolate: 1
|
||||
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
|
||||
|
||||
@@ -1929,11 +1929,12 @@ GameObject:
|
||||
- component: {fileID: 5774766047967133809}
|
||||
- component: {fileID: 8818883032728065057}
|
||||
- component: {fileID: -2857689419101920665}
|
||||
- component: {fileID: 1459274958342653187}
|
||||
- component: {fileID: 7544406269366897481}
|
||||
- component: {fileID: 4137653497738922896}
|
||||
- component: {fileID: 2670434347080613863}
|
||||
m_Layer: 6
|
||||
m_Name: Prefab_Boss_TestBoss
|
||||
m_Name: Prefab_Boss_Drog
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
@@ -2030,6 +2031,10 @@ MonoBehaviour:
|
||||
animator: {fileID: 4019041888965840580}
|
||||
navMeshAgent: {fileID: 5153439431748782209}
|
||||
enemyData: {fileID: 11400000, guid: f21773b42c60c4b40b609966f09146aa, type: 2}
|
||||
useThreatSystem: 1
|
||||
damageThreatMultiplier: 1
|
||||
threatDecayPerSecond: 0
|
||||
retargetThreshold: 0
|
||||
phases: []
|
||||
initialBehaviorGraph: {fileID: -3933356984444701103, guid: 9ade9280028e9da4aa0151fe8e9ec454, type: 2}
|
||||
phaseTransitionInvincibilityTime: 2
|
||||
@@ -2149,6 +2154,8 @@ MonoBehaviour:
|
||||
debugMode: 1
|
||||
showAreaDebug: 1
|
||||
debugDrawDuration: 1
|
||||
lastCancelledSkillName:
|
||||
lastCancelReason: 0
|
||||
--- !u!114 &-2857689419101920665
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -2162,6 +2169,33 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Team
|
||||
teamType: 2
|
||||
--- !u!114 &1459274958342653187
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5581648761285601425}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c5b2d4ef2f1b4ee49b5f7f2c7175fd10, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Enemy.DrogPatternController
|
||||
ShowTopMostFoldoutHeaderGroup: 1
|
||||
bossEnemy: {fileID: 6949205239376088310}
|
||||
enemyBase: {fileID: 6949205239376088310}
|
||||
skillController: {fileID: 8818883032728065057}
|
||||
navMeshAgent: {fileID: 5153439431748782209}
|
||||
mainPattern: {fileID: 11400000, guid: 5efd8123be76bf844875d386d9d5f73d, type: 2}
|
||||
leapPattern: {fileID: 11400000, guid: 88e6cc7cab28baf4c8f8a742247000ec, type: 2}
|
||||
downPunishPattern: {fileID: 11400000, guid: fe5100f855d14c0faac44b6d4f2c771e, type: 2}
|
||||
phase2HealthThreshold: 0.75
|
||||
phase3HealthThreshold: 0.4
|
||||
targetRefreshInterval: 0.2
|
||||
leapDistanceThreshold: 8
|
||||
downPunishSearchRadius: 6
|
||||
disableBehaviorGraph: 1
|
||||
debugMode: 1
|
||||
--- !u!114 &7544406269366897481
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -2177,6 +2211,7 @@ MonoBehaviour:
|
||||
ShowTopMostFoldoutHeaderGroup: 1
|
||||
characterStats: {fileID: 0}
|
||||
networkController: {fileID: 0}
|
||||
skillController: {fileID: 0}
|
||||
--- !u!114 &4137653497738922896
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -203,6 +203,7 @@ MonoBehaviour:
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Player.PlayerNetworkController
|
||||
ShowTopMostFoldoutHeaderGroup: 1
|
||||
characterStats: {fileID: -5132198055668300151}
|
||||
abnormalityManager: {fileID: 0}
|
||||
--- !u!114 &8606252901290138286
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -242,6 +243,14 @@ MonoBehaviour:
|
||||
NameHash: 20298039
|
||||
Synchronize: 1
|
||||
ParameterType: 9
|
||||
- name: Down
|
||||
NameHash: -1127399675
|
||||
Synchronize: 1
|
||||
ParameterType: 9
|
||||
- name: Recover
|
||||
NameHash: 976603345
|
||||
Synchronize: 1
|
||||
ParameterType: 9
|
||||
AnimatorParametersExpanded: 0
|
||||
--- !u!95 &3426985706796420257
|
||||
Animator:
|
||||
@@ -304,6 +313,8 @@ MonoBehaviour:
|
||||
debugMode: 1
|
||||
showAreaDebug: 1
|
||||
debugDrawDuration: 1
|
||||
lastCancelledSkillName:
|
||||
lastCancelReason: 0
|
||||
--- !u!114 &6585367215453362640
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -344,6 +355,7 @@ MonoBehaviour:
|
||||
ShowTopMostFoldoutHeaderGroup: 1
|
||||
characterStats: {fileID: -5132198055668300151}
|
||||
networkController: {fileID: 1664515335065415329}
|
||||
skillController: {fileID: 0}
|
||||
--- !u!114 &3552488436187204500
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -453,6 +465,8 @@ MonoBehaviour:
|
||||
abnormalityManager: {fileID: 0}
|
||||
actionState: {fileID: 0}
|
||||
networkController: {fileID: 0}
|
||||
skillInput: {fileID: 0}
|
||||
skillController: {fileID: 0}
|
||||
stunData: {fileID: 0}
|
||||
silenceData: {fileID: 0}
|
||||
runOnStartInEditor: 0
|
||||
|
||||
@@ -4,6 +4,7 @@ using UnityEngine;
|
||||
using Action = Unity.Behavior.Action;
|
||||
using Unity.Properties;
|
||||
using Colosseum.Combat;
|
||||
using Colosseum.Enemy;
|
||||
|
||||
[Serializable, GeneratePropertyBag]
|
||||
[NodeDescription(name: "FindTarget", story: "[타겟] 탐색", category: "Action", id: "bb947540549026f3c5625c6d19213311")]
|
||||
@@ -30,6 +31,17 @@ public partial class FindTargetAction : Action
|
||||
return Status.Failure;
|
||||
}
|
||||
|
||||
EnemyBase enemy = GameObject.GetComponent<EnemyBase>();
|
||||
if (enemy != null && enemy.UseThreatSystem)
|
||||
{
|
||||
GameObject threatTarget = enemy.GetHighestThreatTarget(Target?.Value, Tag.Value);
|
||||
if (threatTarget != null)
|
||||
{
|
||||
Target.Value = threatTarget;
|
||||
return Status.Success;
|
||||
}
|
||||
}
|
||||
|
||||
// 사망하지 않은 타겟 찾기
|
||||
foreach (GameObject candidate in candidates)
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ using UnityEngine;
|
||||
using Action = Unity.Behavior.Action;
|
||||
using Unity.Properties;
|
||||
using Colosseum.Combat;
|
||||
using Colosseum.Enemy;
|
||||
|
||||
[Serializable, GeneratePropertyBag]
|
||||
[NodeDescription(name: "SetTargetInRange", story: "[Range] 내에 [Tag] 있는지 확인", category: "Action", id: "93b7a5d823a58618d5371c01ef894948")]
|
||||
@@ -33,6 +34,17 @@ public partial class SetTargetInRangeAction : Action
|
||||
return Status.Failure;
|
||||
}
|
||||
|
||||
EnemyBase enemy = GameObject.GetComponent<EnemyBase>();
|
||||
if (enemy != null && enemy.UseThreatSystem)
|
||||
{
|
||||
GameObject threatTarget = enemy.GetHighestThreatTarget(Target?.Value, Tag.Value, Range.Value);
|
||||
if (threatTarget != null)
|
||||
{
|
||||
Target.Value = threatTarget;
|
||||
return Status.Success;
|
||||
}
|
||||
}
|
||||
|
||||
// 가장 가까운 살아있는 타겟 찾기
|
||||
GameObject nearestTarget = null;
|
||||
float nearestDistance = Range.Value; // Range 내에서만 검색
|
||||
|
||||
@@ -13,7 +13,8 @@ namespace Colosseum.Abnormalities
|
||||
None, // 제어 효과 없음
|
||||
Stun, // 기절 (이동, 스킬 사용 불가)
|
||||
Silence, // 침묵 (스킬 사용 불가)
|
||||
Slow // 둔화 (이동 속도 감소)
|
||||
Slow, // 둔화 (이동 속도 감소)
|
||||
Invincible // 무적 (대미지 무시)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -4,6 +4,7 @@ using UnityEngine;
|
||||
using Unity.Netcode;
|
||||
using Colosseum.Stats;
|
||||
using Colosseum.Player;
|
||||
using Colosseum.Skills;
|
||||
|
||||
namespace Colosseum.Abnormalities
|
||||
{
|
||||
@@ -20,17 +21,22 @@ namespace Colosseum.Abnormalities
|
||||
[Tooltip("PlayerNetworkController 컴포넌트 (HP/MP 관리용)")]
|
||||
[SerializeField] private PlayerNetworkController networkController;
|
||||
|
||||
[Tooltip("스킬 실행 관리자 (강제 취소 처리용)")]
|
||||
[SerializeField] private SkillController skillController;
|
||||
|
||||
// 활성화된 이상 상태 목록
|
||||
private readonly List<ActiveAbnormality> activeAbnormalities = new List<ActiveAbnormality>();
|
||||
|
||||
// 제어 효과 상태
|
||||
private int stunCount;
|
||||
private int silenceCount;
|
||||
private int invincibleCount;
|
||||
private float slowMultiplier = 1f;
|
||||
|
||||
// 클라이언트 판정용 제어 효과 동기화 변수
|
||||
private NetworkVariable<int> syncedStunCount = new NetworkVariable<int>(0);
|
||||
private NetworkVariable<int> syncedSilenceCount = new NetworkVariable<int>(0);
|
||||
private NetworkVariable<int> syncedInvincibleCount = new NetworkVariable<int>(0);
|
||||
private NetworkVariable<float> syncedSlowMultiplier = new NetworkVariable<float>(1f);
|
||||
|
||||
// 네트워크 동기화용 데이터
|
||||
@@ -46,6 +52,11 @@ namespace Colosseum.Abnormalities
|
||||
/// </summary>
|
||||
public bool IsSilenced => GetCurrentSilenceCount() > 0;
|
||||
|
||||
/// <summary>
|
||||
/// 무적 상태 여부
|
||||
/// </summary>
|
||||
public bool IsInvincible => GetCurrentInvincibleCount() > 0;
|
||||
|
||||
/// <summary>
|
||||
/// 이동 속도 배율 (1.0 = 기본, 0.5 = 50% 감소)
|
||||
/// </summary>
|
||||
@@ -101,6 +112,9 @@ namespace Colosseum.Abnormalities
|
||||
if (networkController == null)
|
||||
networkController = GetComponent<PlayerNetworkController>();
|
||||
|
||||
if (skillController == null)
|
||||
skillController = GetComponent<SkillController>();
|
||||
|
||||
syncedAbnormalities = new NetworkList<AbnormalitySyncData>();
|
||||
}
|
||||
|
||||
@@ -110,6 +124,7 @@ namespace Colosseum.Abnormalities
|
||||
|
||||
syncedStunCount.OnValueChanged += HandleSyncedStunChanged;
|
||||
syncedSilenceCount.OnValueChanged += HandleSyncedSilenceChanged;
|
||||
syncedInvincibleCount.OnValueChanged += HandleSyncedInvincibleChanged;
|
||||
syncedSlowMultiplier.OnValueChanged += HandleSyncedSlowChanged;
|
||||
|
||||
if (networkController != null)
|
||||
@@ -128,6 +143,7 @@ namespace Colosseum.Abnormalities
|
||||
syncedAbnormalities.OnListChanged -= OnSyncedAbnormalitiesChanged;
|
||||
syncedStunCount.OnValueChanged -= HandleSyncedStunChanged;
|
||||
syncedSilenceCount.OnValueChanged -= HandleSyncedSilenceChanged;
|
||||
syncedInvincibleCount.OnValueChanged -= HandleSyncedInvincibleChanged;
|
||||
syncedSlowMultiplier.OnValueChanged -= HandleSyncedSlowChanged;
|
||||
|
||||
if (networkController != null)
|
||||
@@ -405,9 +421,12 @@ namespace Colosseum.Abnormalities
|
||||
|
||||
private void ApplyControlEffect(AbnormalityData data)
|
||||
{
|
||||
bool enteredStun = false;
|
||||
|
||||
switch (data.controlType)
|
||||
{
|
||||
case ControlType.Stun:
|
||||
enteredStun = stunCount == 0;
|
||||
stunCount++;
|
||||
break;
|
||||
|
||||
@@ -418,9 +437,18 @@ namespace Colosseum.Abnormalities
|
||||
case ControlType.Slow:
|
||||
slowMultiplier = Mathf.Min(slowMultiplier, data.slowMultiplier);
|
||||
break;
|
||||
|
||||
case ControlType.Invincible:
|
||||
invincibleCount++;
|
||||
break;
|
||||
}
|
||||
|
||||
SyncControlEffects();
|
||||
|
||||
if (enteredStun)
|
||||
{
|
||||
TryCancelCurrentSkill(SkillCancelReason.Stun, data.abnormalityName);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveControlEffect(AbnormalityData data)
|
||||
@@ -438,6 +466,10 @@ namespace Colosseum.Abnormalities
|
||||
case ControlType.Slow:
|
||||
RecalculateSlowMultiplier();
|
||||
break;
|
||||
|
||||
case ControlType.Invincible:
|
||||
invincibleCount = Mathf.Max(0, invincibleCount - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
SyncControlEffects();
|
||||
@@ -460,6 +492,8 @@ namespace Colosseum.Abnormalities
|
||||
|
||||
private int GetCurrentSilenceCount() => IsServer ? silenceCount : syncedSilenceCount.Value;
|
||||
|
||||
private int GetCurrentInvincibleCount() => IsServer ? invincibleCount : syncedInvincibleCount.Value;
|
||||
|
||||
private float GetCurrentSlowMultiplier() => IsServer ? slowMultiplier : syncedSlowMultiplier.Value;
|
||||
|
||||
private void SyncControlEffects()
|
||||
@@ -469,6 +503,7 @@ namespace Colosseum.Abnormalities
|
||||
|
||||
syncedStunCount.Value = stunCount;
|
||||
syncedSilenceCount.Value = silenceCount;
|
||||
syncedInvincibleCount.Value = invincibleCount;
|
||||
syncedSlowMultiplier.Value = slowMultiplier;
|
||||
}
|
||||
|
||||
@@ -541,6 +576,14 @@ namespace Colosseum.Abnormalities
|
||||
OnAbnormalitiesChanged?.Invoke();
|
||||
}
|
||||
|
||||
private void HandleSyncedInvincibleChanged(int oldValue, int newValue)
|
||||
{
|
||||
if (oldValue == newValue)
|
||||
return;
|
||||
|
||||
OnAbnormalitiesChanged?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 사망 시 활성 이상 상태를 모두 제거합니다.
|
||||
/// </summary>
|
||||
@@ -556,6 +599,17 @@ namespace Colosseum.Abnormalities
|
||||
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)
|
||||
{
|
||||
var allData = Resources.FindObjectsOfTypeAll<AbnormalityData>();
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Colosseum.Editor
|
||||
{
|
||||
private BossEnemy boss;
|
||||
private bool showPhaseDetails = true;
|
||||
private bool showThreatInfo = true;
|
||||
private bool showDebugTools = true;
|
||||
private int selectedPhaseIndex = 0;
|
||||
|
||||
@@ -45,6 +46,11 @@ namespace Colosseum.Editor
|
||||
|
||||
EditorGUILayout.Space(10);
|
||||
|
||||
// 위협 정보
|
||||
DrawThreatInfo();
|
||||
|
||||
EditorGUILayout.Space(10);
|
||||
|
||||
// 디버그 도구
|
||||
DrawDebugTools();
|
||||
}
|
||||
@@ -136,6 +142,36 @@ namespace Colosseum.Editor
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 위협 정보 표시
|
||||
/// </summary>
|
||||
private void DrawThreatInfo()
|
||||
{
|
||||
showThreatInfo = EditorGUILayout.Foldout(showThreatInfo, "위협 정보", true);
|
||||
|
||||
if (!showThreatInfo)
|
||||
return;
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
if (!boss.UseThreatSystem)
|
||||
{
|
||||
EditorGUILayout.HelpBox("위협 시스템이 비활성화되어 있습니다.", MessageType.Info);
|
||||
EditorGUI.indentLevel--;
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("위협 테이블", EditorStyles.boldLabel);
|
||||
EditorGUILayout.TextArea(boss.GetThreatDebugSummary(), GUILayout.MinHeight(70f));
|
||||
|
||||
if (GUILayout.Button("위협 초기화"))
|
||||
{
|
||||
boss.ClearAllThreat();
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 디버그 도구 표시
|
||||
/// </summary>
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace Colosseum.Enemy
|
||||
|
||||
// 컴포넌트
|
||||
private BehaviorGraphAgent behaviorAgent;
|
||||
private DrogPatternController drogPatternController;
|
||||
|
||||
// 페이즈 상태
|
||||
private int currentPhaseIndex = 0;
|
||||
@@ -77,8 +78,15 @@ namespace Colosseum.Enemy
|
||||
behaviorAgent = gameObject.AddComponent<BehaviorGraphAgent>();
|
||||
}
|
||||
|
||||
drogPatternController = GetComponent<DrogPatternController>();
|
||||
|
||||
// 초기 AI 설정
|
||||
if (IsServer && initialBehaviorGraph != null)
|
||||
if (IsServer && drogPatternController != null && drogPatternController.DisableBehaviorGraph)
|
||||
{
|
||||
behaviorAgent.enabled = false;
|
||||
behaviorAgent.Graph = null;
|
||||
}
|
||||
else if (IsServer && initialBehaviorGraph != null)
|
||||
{
|
||||
behaviorAgent.Graph = initialBehaviorGraph;
|
||||
}
|
||||
|
||||
408
Assets/_Game/Scripts/Enemy/DrogPatternController.cs
Normal file
408
Assets/_Game/Scripts/Enemy/DrogPatternController.cs
Normal file
@@ -0,0 +1,408 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using UnityEngine;
|
||||
using Unity.Netcode;
|
||||
|
||||
using Colosseum.AI;
|
||||
using Colosseum.Player;
|
||||
using Colosseum.Skills;
|
||||
|
||||
namespace Colosseum.Enemy
|
||||
{
|
||||
/// <summary>
|
||||
/// 드로그 전용 패턴 선택 컨트롤러입니다.
|
||||
/// 기본 루프, 도약, 다운 추가타 같은 고우선 패턴을 직접 선택합니다.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
[RequireComponent(typeof(BossEnemy))]
|
||||
[RequireComponent(typeof(SkillController))]
|
||||
public class DrogPatternController : NetworkBehaviour
|
||||
{
|
||||
[Header("References")]
|
||||
[SerializeField] private BossEnemy bossEnemy;
|
||||
[SerializeField] private EnemyBase enemyBase;
|
||||
[SerializeField] private SkillController skillController;
|
||||
[SerializeField] private UnityEngine.AI.NavMeshAgent navMeshAgent;
|
||||
|
||||
[Header("Pattern Data")]
|
||||
[Tooltip("기본 근접 압박 패턴")]
|
||||
[SerializeField] private BossPatternData mainPattern;
|
||||
|
||||
[Tooltip("먼 대상 징벌용 도약 패턴")]
|
||||
[SerializeField] private BossPatternData leapPattern;
|
||||
|
||||
[Tooltip("다운 대상이 있을 때 우선 발동하는 광역 추가타 패턴")]
|
||||
[SerializeField] private BossPatternData downPunishPattern;
|
||||
|
||||
[Header("Phase Thresholds")]
|
||||
[Tooltip("2페이즈 진입 체력 비율")]
|
||||
[Range(0f, 1f)] [SerializeField] private float phase2HealthThreshold = 0.75f;
|
||||
|
||||
[Tooltip("3페이즈 진입 체력 비율")]
|
||||
[Range(0f, 1f)] [SerializeField] private float phase3HealthThreshold = 0.4f;
|
||||
|
||||
[Header("Targeting")]
|
||||
[Tooltip("타겟 재탐색 주기")]
|
||||
[Min(0.05f)] [SerializeField] private float targetRefreshInterval = 0.2f;
|
||||
|
||||
[Tooltip("도약 패턴을 고려하기 시작하는 거리")]
|
||||
[Min(0f)] [SerializeField] private float leapDistanceThreshold = 8f;
|
||||
|
||||
[Tooltip("다운 추가타를 고려할 최대 반경")]
|
||||
[Min(0f)] [SerializeField] private float downPunishSearchRadius = 6f;
|
||||
|
||||
[Header("Behavior")]
|
||||
[Tooltip("드로그 전용 컨트롤러 사용 시 기존 BehaviorGraph를 비활성화할지 여부")]
|
||||
[SerializeField] private bool disableBehaviorGraph = true;
|
||||
|
||||
[Tooltip("디버그 로그 출력 여부")]
|
||||
[SerializeField] private bool debugMode = false;
|
||||
|
||||
private readonly Dictionary<BossPatternData, float> patternCooldownTracker = new Dictionary<BossPatternData, float>();
|
||||
|
||||
private Coroutine activePatternCoroutine;
|
||||
private GameObject currentTarget;
|
||||
private float nextTargetRefreshTime;
|
||||
|
||||
/// <summary>
|
||||
/// 드로그 컨트롤러 사용 시 BehaviorGraph를 비활성화할지 여부
|
||||
/// </summary>
|
||||
public bool DisableBehaviorGraph => disableBehaviorGraph;
|
||||
|
||||
/// <summary>
|
||||
/// 현재 전용 패턴 실행 중인지 여부
|
||||
/// </summary>
|
||||
public bool IsExecutingPattern => activePatternCoroutine != null;
|
||||
|
||||
/// <summary>
|
||||
/// 현재 드로그 패턴 페이즈
|
||||
/// </summary>
|
||||
public int CurrentPatternPhase
|
||||
{
|
||||
get
|
||||
{
|
||||
float healthRatio = bossEnemy != null && bossEnemy.MaxHealth > 0f
|
||||
? bossEnemy.CurrentHealth / bossEnemy.MaxHealth
|
||||
: 1f;
|
||||
|
||||
if (healthRatio <= phase3HealthThreshold)
|
||||
return 3;
|
||||
|
||||
if (healthRatio <= phase2HealthThreshold)
|
||||
return 2;
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
ResolveReferences();
|
||||
}
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
ResolveReferences();
|
||||
|
||||
if (!IsServer)
|
||||
{
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!IsServer)
|
||||
return;
|
||||
|
||||
ResolveReferences();
|
||||
|
||||
if (bossEnemy == null || enemyBase == null || skillController == null)
|
||||
return;
|
||||
|
||||
if (bossEnemy.IsDead || bossEnemy.IsTransitioning)
|
||||
return;
|
||||
|
||||
RefreshTargetIfNeeded();
|
||||
UpdateMovement();
|
||||
|
||||
if (activePatternCoroutine != null || skillController.IsPlayingAnimation)
|
||||
return;
|
||||
|
||||
if (TryStartDownPunishPattern())
|
||||
return;
|
||||
|
||||
TryStartMainPattern();
|
||||
}
|
||||
|
||||
private bool TryStartDownPunishPattern()
|
||||
{
|
||||
if (!IsPatternReady(downPunishPattern))
|
||||
return false;
|
||||
|
||||
GameObject downedTarget = FindNearestDownedTarget();
|
||||
if (downedTarget == null)
|
||||
return false;
|
||||
|
||||
StartPattern(downPunishPattern, downedTarget);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryStartMainPattern()
|
||||
{
|
||||
if (currentTarget == null)
|
||||
return false;
|
||||
|
||||
float distanceToTarget = Vector3.Distance(transform.position, currentTarget.transform.position);
|
||||
|
||||
if (distanceToTarget >= leapDistanceThreshold && IsPatternReady(leapPattern))
|
||||
{
|
||||
StartPattern(leapPattern, currentTarget);
|
||||
return true;
|
||||
}
|
||||
|
||||
float attackRange = enemyBase.Data != null ? enemyBase.Data.AttackRange : 2f;
|
||||
if (distanceToTarget <= attackRange + 0.25f && IsPatternReady(mainPattern))
|
||||
{
|
||||
StartPattern(mainPattern, currentTarget);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void StartPattern(BossPatternData pattern, GameObject target)
|
||||
{
|
||||
if (pattern == null || activePatternCoroutine != null)
|
||||
return;
|
||||
|
||||
if (debugMode)
|
||||
{
|
||||
string targetName = target != null ? target.name : "None";
|
||||
Debug.Log($"[DrogPattern] 패턴 시작: {pattern.PatternName} / Target={targetName} / Phase={CurrentPatternPhase}");
|
||||
}
|
||||
|
||||
activePatternCoroutine = StartCoroutine(RunPatternCoroutine(pattern, target));
|
||||
}
|
||||
|
||||
private IEnumerator RunPatternCoroutine(BossPatternData pattern, GameObject target)
|
||||
{
|
||||
StopMovement();
|
||||
|
||||
bool completed = true;
|
||||
BossPatternData chainedPattern = null;
|
||||
GameObject chainedTarget = null;
|
||||
|
||||
for (int i = 0; i < pattern.Steps.Count; i++)
|
||||
{
|
||||
PatternStep step = pattern.Steps[i];
|
||||
|
||||
if (step.Type == PatternStepType.Wait)
|
||||
{
|
||||
yield return new WaitForSeconds(step.Duration);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (step.Skill == null)
|
||||
{
|
||||
completed = false;
|
||||
Debug.LogWarning($"[DrogPattern] 패턴 스텝 스킬이 비어 있습니다. Pattern={pattern.PatternName}, Index={i}");
|
||||
break;
|
||||
}
|
||||
|
||||
if (step.Skill.JumpToTarget && target != null)
|
||||
{
|
||||
enemyBase?.SetJumpTarget(target.transform.position);
|
||||
}
|
||||
|
||||
if (!skillController.ExecuteSkill(step.Skill))
|
||||
{
|
||||
completed = false;
|
||||
if (debugMode)
|
||||
{
|
||||
Debug.LogWarning($"[DrogPattern] 스킬 실행 실패: {step.Skill.SkillName}");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
yield return new WaitUntil(() => skillController == null || !skillController.IsPlayingAnimation || bossEnemy == null || bossEnemy.IsDead);
|
||||
|
||||
if (bossEnemy == null || bossEnemy.IsDead)
|
||||
{
|
||||
completed = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (pattern != downPunishPattern && TryPrepareDownPunishChain(out chainedTarget))
|
||||
{
|
||||
chainedPattern = downPunishPattern;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (completed)
|
||||
{
|
||||
patternCooldownTracker[pattern] = Time.time + pattern.Cooldown;
|
||||
}
|
||||
|
||||
activePatternCoroutine = null;
|
||||
|
||||
if (chainedPattern != null && chainedTarget != null && bossEnemy != null && !bossEnemy.IsDead)
|
||||
{
|
||||
StartPattern(chainedPattern, chainedTarget);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsPatternReady(BossPatternData pattern)
|
||||
{
|
||||
if (pattern == null || pattern.Steps == null || pattern.Steps.Count == 0)
|
||||
return false;
|
||||
|
||||
if (!patternCooldownTracker.TryGetValue(pattern, out float readyTime))
|
||||
return true;
|
||||
|
||||
return Time.time >= readyTime;
|
||||
}
|
||||
|
||||
private void RefreshTargetIfNeeded()
|
||||
{
|
||||
if (Time.time < nextTargetRefreshTime)
|
||||
return;
|
||||
|
||||
nextTargetRefreshTime = Time.time + targetRefreshInterval;
|
||||
|
||||
GameObject highestThreatTarget = enemyBase.GetHighestThreatTarget(currentTarget, null, enemyBase.Data != null ? enemyBase.Data.AggroRange : Mathf.Infinity);
|
||||
currentTarget = highestThreatTarget != null ? highestThreatTarget : FindNearestLivingPlayer();
|
||||
}
|
||||
|
||||
private void UpdateMovement()
|
||||
{
|
||||
if (navMeshAgent == null || !navMeshAgent.enabled)
|
||||
return;
|
||||
|
||||
if (activePatternCoroutine != null || (skillController != null && skillController.IsPlayingAnimation))
|
||||
{
|
||||
StopMovement();
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentTarget == null)
|
||||
{
|
||||
StopMovement();
|
||||
return;
|
||||
}
|
||||
|
||||
float attackRange = enemyBase.Data != null ? enemyBase.Data.AttackRange : 2f;
|
||||
float distanceToTarget = Vector3.Distance(transform.position, currentTarget.transform.position);
|
||||
if (distanceToTarget <= attackRange)
|
||||
{
|
||||
StopMovement();
|
||||
return;
|
||||
}
|
||||
|
||||
navMeshAgent.isStopped = false;
|
||||
navMeshAgent.SetDestination(currentTarget.transform.position);
|
||||
}
|
||||
|
||||
private void StopMovement()
|
||||
{
|
||||
if (navMeshAgent == null || !navMeshAgent.enabled)
|
||||
return;
|
||||
|
||||
navMeshAgent.isStopped = true;
|
||||
navMeshAgent.ResetPath();
|
||||
}
|
||||
|
||||
private GameObject FindNearestDownedTarget()
|
||||
{
|
||||
HitReactionController[] hitReactionControllers = FindObjectsByType<HitReactionController>(FindObjectsSortMode.None);
|
||||
|
||||
GameObject bestTarget = null;
|
||||
float bestDistance = float.MaxValue;
|
||||
|
||||
for (int i = 0; i < hitReactionControllers.Length; i++)
|
||||
{
|
||||
HitReactionController controller = hitReactionControllers[i];
|
||||
if (controller == null || !controller.IsDowned)
|
||||
continue;
|
||||
|
||||
GameObject candidate = controller.gameObject;
|
||||
if (candidate == null || !candidate.activeInHierarchy || Team.IsSameTeam(gameObject, candidate))
|
||||
continue;
|
||||
|
||||
float distance = Vector3.Distance(transform.position, candidate.transform.position);
|
||||
if (distance > downPunishSearchRadius || distance >= bestDistance)
|
||||
continue;
|
||||
|
||||
bestDistance = distance;
|
||||
bestTarget = candidate;
|
||||
}
|
||||
|
||||
return bestTarget;
|
||||
}
|
||||
|
||||
private bool TryPrepareDownPunishChain(out GameObject downedTarget)
|
||||
{
|
||||
downedTarget = null;
|
||||
|
||||
if (!IsPatternReady(downPunishPattern))
|
||||
return false;
|
||||
|
||||
downedTarget = FindNearestDownedTarget();
|
||||
if (downedTarget == null)
|
||||
return false;
|
||||
|
||||
if (debugMode)
|
||||
{
|
||||
Debug.Log($"[DrogPattern] 다운 대상 감지, 다운 추가타 연계 준비: {downedTarget.name}");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private GameObject FindNearestLivingPlayer()
|
||||
{
|
||||
PlayerNetworkController[] players = FindObjectsByType<PlayerNetworkController>(FindObjectsSortMode.None);
|
||||
|
||||
GameObject bestTarget = null;
|
||||
float bestDistance = float.MaxValue;
|
||||
float aggroRange = enemyBase.Data != null ? enemyBase.Data.AggroRange : Mathf.Infinity;
|
||||
|
||||
for (int i = 0; i < players.Length; i++)
|
||||
{
|
||||
PlayerNetworkController player = players[i];
|
||||
if (player == null || player.IsDead || !player.gameObject.activeInHierarchy)
|
||||
continue;
|
||||
|
||||
GameObject candidate = player.gameObject;
|
||||
if (Team.IsSameTeam(gameObject, candidate))
|
||||
continue;
|
||||
|
||||
float distance = Vector3.Distance(transform.position, candidate.transform.position);
|
||||
if (distance > aggroRange || distance >= bestDistance)
|
||||
continue;
|
||||
|
||||
bestDistance = distance;
|
||||
bestTarget = candidate;
|
||||
}
|
||||
|
||||
return bestTarget;
|
||||
}
|
||||
|
||||
private void ResolveReferences()
|
||||
{
|
||||
if (bossEnemy == null)
|
||||
bossEnemy = GetComponent<BossEnemy>();
|
||||
|
||||
if (enemyBase == null)
|
||||
enemyBase = GetComponent<EnemyBase>();
|
||||
|
||||
if (skillController == null)
|
||||
skillController = GetComponent<SkillController>();
|
||||
|
||||
if (navMeshAgent == null)
|
||||
navMeshAgent = GetComponent<UnityEngine.AI.NavMeshAgent>();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Enemy/DrogPatternController.cs.meta
Normal file
11
Assets/_Game/Scripts/Enemy/DrogPatternController.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c5b2d4ef2f1b4ee49b5f7f2c7175fd10
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,8 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
using UnityEngine;
|
||||
using Unity.Netcode;
|
||||
|
||||
using Colosseum.Stats;
|
||||
using Colosseum.Combat;
|
||||
using Colosseum.Skills;
|
||||
|
||||
namespace Colosseum.Enemy
|
||||
{
|
||||
@@ -23,6 +28,15 @@ namespace Colosseum.Enemy
|
||||
[Header("Data")]
|
||||
[SerializeField] protected EnemyData enemyData;
|
||||
|
||||
[Header("Threat Settings")]
|
||||
[Tooltip("피격 시 공격자 기준 위협 수치를 누적할지 여부")]
|
||||
[SerializeField] private bool useThreatSystem = true;
|
||||
[Tooltip("실제 적용된 피해량에 곱해지는 위협 배율")]
|
||||
[Min(0f)] [SerializeField] private float damageThreatMultiplier = 1f;
|
||||
[Tooltip("초당 감소하는 위협 수치")]
|
||||
[Min(0f)] [SerializeField] private float threatDecayPerSecond = 0f;
|
||||
[Tooltip("현재 타겟보다 이 값 이상 높을 때만 새 타겟으로 전환합니다.")]
|
||||
[Min(0f)] [SerializeField] private float retargetThreshold = 0f;
|
||||
|
||||
|
||||
// 네트워크 동기화 변수
|
||||
@@ -32,6 +46,8 @@ namespace Colosseum.Enemy
|
||||
|
||||
// 플레이어 분리용 (레이어 의존 없이 CharacterController로 식별)
|
||||
private readonly Collider[] overlapBuffer = new Collider[8];
|
||||
private readonly Dictionary<GameObject, float> threatTable = new Dictionary<GameObject, float>();
|
||||
private readonly List<GameObject> threatTargetBuffer = new List<GameObject>();
|
||||
|
||||
// 점프 등 Y 루트모션 스킬 중 NavMeshAgent 비활성화 상태 추적
|
||||
private bool isAirborne = false;
|
||||
@@ -55,6 +71,7 @@ namespace Colosseum.Enemy
|
||||
public CharacterStats Stats => characterStats;
|
||||
public EnemyData Data => enemyData;
|
||||
public Animator Animator => animator;
|
||||
public bool UseThreatSystem => useThreatSystem;
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
@@ -80,6 +97,7 @@ namespace Colosseum.Enemy
|
||||
{
|
||||
if (!IsServer || IsDead) return;
|
||||
|
||||
UpdateThreatState(Time.deltaTime);
|
||||
OnServerUpdate();
|
||||
}
|
||||
|
||||
@@ -250,6 +268,7 @@ namespace Colosseum.Enemy
|
||||
float actualDamage = Mathf.Min(damage, currentHealth.Value);
|
||||
currentHealth.Value = Mathf.Max(0f, currentHealth.Value - actualDamage);
|
||||
|
||||
RegisterThreatFromDamage(actualDamage, source);
|
||||
OnDamageTaken?.Invoke(actualDamage);
|
||||
|
||||
// 대미지 피드백 (애니메이션, 이펙트 등)
|
||||
@@ -323,12 +342,13 @@ namespace Colosseum.Enemy
|
||||
protected virtual void HandleDeath()
|
||||
{
|
||||
isDead.Value = true;
|
||||
ClearAllThreat();
|
||||
|
||||
// 실행 중인 스킬 즉시 취소
|
||||
var skillController = GetComponent<Colosseum.Skills.SkillController>();
|
||||
if (skillController != null)
|
||||
{
|
||||
skillController.CancelSkill();
|
||||
skillController.CancelSkill(SkillCancelReason.Death);
|
||||
}
|
||||
|
||||
// 모든 클라이언트에서 사망 애니메이션 재생
|
||||
@@ -352,6 +372,7 @@ namespace Colosseum.Enemy
|
||||
if (!IsServer) return;
|
||||
|
||||
isDead.Value = false;
|
||||
ClearAllThreat();
|
||||
InitializeStats();
|
||||
|
||||
if (navMeshAgent != null)
|
||||
@@ -363,6 +384,12 @@ namespace Colosseum.Enemy
|
||||
{
|
||||
animator.Rebind();
|
||||
}
|
||||
|
||||
var skillController = GetComponent<SkillController>();
|
||||
if (skillController != null)
|
||||
{
|
||||
skillController.CancelSkill(SkillCancelReason.Respawn);
|
||||
}
|
||||
}
|
||||
|
||||
// 체력 변화 이벤트 전파
|
||||
@@ -370,5 +397,280 @@ namespace Colosseum.Enemy
|
||||
{
|
||||
OnHealthChanged?.Invoke(newValue, MaxHealth);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 공격자 기준 위협 수치를 누적합니다.
|
||||
/// </summary>
|
||||
public virtual void AddThreat(GameObject source, float amount)
|
||||
{
|
||||
if (!IsServer || !useThreatSystem || amount <= 0f)
|
||||
return;
|
||||
|
||||
if (!IsValidThreatTarget(source))
|
||||
return;
|
||||
|
||||
threatTable.TryGetValue(source, out float currentThreat);
|
||||
threatTable[source] = currentThreat + amount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 대상의 위협 수치를 강제로 설정합니다.
|
||||
/// </summary>
|
||||
public virtual void SetThreat(GameObject source, float amount)
|
||||
{
|
||||
if (!IsServer || !useThreatSystem)
|
||||
return;
|
||||
|
||||
if (!IsValidThreatTarget(source) || amount <= 0f)
|
||||
{
|
||||
ClearThreat(source);
|
||||
return;
|
||||
}
|
||||
|
||||
threatTable[source] = amount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 대상의 위협 수치를 제거합니다.
|
||||
/// </summary>
|
||||
public virtual void ClearThreat(GameObject source)
|
||||
{
|
||||
if (source == null)
|
||||
return;
|
||||
|
||||
threatTable.Remove(source);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모든 위협 수치를 초기화합니다.
|
||||
/// </summary>
|
||||
public virtual void ClearAllThreat()
|
||||
{
|
||||
threatTable.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 가장 높은 위협 수치를 가진 타겟을 반환합니다.
|
||||
/// </summary>
|
||||
public virtual GameObject GetHighestThreatTarget(GameObject currentTarget = null, string requiredTag = null, float maxDistance = Mathf.Infinity)
|
||||
{
|
||||
if (!useThreatSystem)
|
||||
return null;
|
||||
|
||||
CleanupThreatTable();
|
||||
|
||||
GameObject highestThreatTarget = null;
|
||||
float highestThreat = float.MinValue;
|
||||
float currentThreat = -1f;
|
||||
|
||||
if (currentTarget != null
|
||||
&& threatTable.TryGetValue(currentTarget, out float cachedCurrentThreat)
|
||||
&& IsSelectableThreatTarget(currentTarget, requiredTag, maxDistance))
|
||||
{
|
||||
currentThreat = cachedCurrentThreat;
|
||||
}
|
||||
|
||||
foreach (var pair in threatTable)
|
||||
{
|
||||
if (!IsSelectableThreatTarget(pair.Key, requiredTag, maxDistance))
|
||||
continue;
|
||||
|
||||
if (highestThreatTarget == null || pair.Value > highestThreat)
|
||||
{
|
||||
highestThreatTarget = pair.Key;
|
||||
highestThreat = pair.Value;
|
||||
}
|
||||
}
|
||||
|
||||
if (highestThreatTarget == null)
|
||||
return null;
|
||||
|
||||
if (currentThreat >= 0f
|
||||
&& currentTarget != null
|
||||
&& currentTarget != highestThreatTarget
|
||||
&& highestThreat < currentThreat + retargetThreshold)
|
||||
{
|
||||
return currentTarget;
|
||||
}
|
||||
|
||||
return highestThreatTarget;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 대상의 현재 위협 수치를 반환합니다.
|
||||
/// </summary>
|
||||
public float GetThreat(GameObject source)
|
||||
{
|
||||
if (source == null)
|
||||
return 0f;
|
||||
|
||||
return threatTable.TryGetValue(source, out float threat) ? threat : 0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 런타임 디버그용 위협 요약 문자열을 반환합니다.
|
||||
/// </summary>
|
||||
public string GetThreatDebugSummary()
|
||||
{
|
||||
if (!useThreatSystem)
|
||||
return "위협 시스템이 비활성화되어 있습니다.";
|
||||
|
||||
CleanupThreatTable();
|
||||
|
||||
if (threatTable.Count == 0)
|
||||
return "등록된 위협 대상이 없습니다.";
|
||||
|
||||
threatTargetBuffer.Clear();
|
||||
foreach (var pair in threatTable)
|
||||
{
|
||||
threatTargetBuffer.Add(pair.Key);
|
||||
}
|
||||
|
||||
threatTargetBuffer.Sort((a, b) => GetThreat(b).CompareTo(GetThreat(a)));
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (int i = 0; i < threatTargetBuffer.Count; i++)
|
||||
{
|
||||
GameObject target = threatTargetBuffer[i];
|
||||
if (target == null)
|
||||
continue;
|
||||
|
||||
if (builder.Length > 0)
|
||||
{
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
builder.Append(i + 1);
|
||||
builder.Append(". ");
|
||||
builder.Append(target.name);
|
||||
builder.Append(" : ");
|
||||
builder.Append(GetThreat(target).ToString("F1"));
|
||||
}
|
||||
|
||||
return builder.Length > 0 ? builder.ToString() : "등록된 위협 대상이 없습니다.";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 피격 정보를 위협 수치로 변환합니다.
|
||||
/// </summary>
|
||||
protected virtual void RegisterThreatFromDamage(float damage, object source)
|
||||
{
|
||||
if (!useThreatSystem || damage <= 0f)
|
||||
return;
|
||||
|
||||
GameObject sourceObject = ResolveThreatSource(source);
|
||||
if (sourceObject == null)
|
||||
return;
|
||||
|
||||
AddThreat(sourceObject, damage * damageThreatMultiplier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 위협 테이블의 무효 대상을 정리하고 자연 감소를 적용합니다.
|
||||
/// </summary>
|
||||
private void UpdateThreatState(float deltaTime)
|
||||
{
|
||||
if (!useThreatSystem || threatTable.Count == 0)
|
||||
return;
|
||||
|
||||
CleanupThreatTable();
|
||||
|
||||
if (threatDecayPerSecond <= 0f || threatTable.Count == 0)
|
||||
return;
|
||||
|
||||
threatTargetBuffer.Clear();
|
||||
foreach (var pair in threatTable)
|
||||
{
|
||||
threatTargetBuffer.Add(pair.Key);
|
||||
}
|
||||
|
||||
for (int i = 0; i < threatTargetBuffer.Count; i++)
|
||||
{
|
||||
GameObject target = threatTargetBuffer[i];
|
||||
if (target == null || !threatTable.TryGetValue(target, out float currentThreat))
|
||||
continue;
|
||||
|
||||
float nextThreat = Mathf.Max(0f, currentThreat - (threatDecayPerSecond * deltaTime));
|
||||
if (nextThreat <= 0f)
|
||||
{
|
||||
threatTable.Remove(target);
|
||||
continue;
|
||||
}
|
||||
|
||||
threatTable[target] = nextThreat;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 위협 대상이 유효한 선택 후보인지 확인합니다.
|
||||
/// </summary>
|
||||
private bool IsSelectableThreatTarget(GameObject target, string requiredTag, float maxDistance)
|
||||
{
|
||||
if (!IsValidThreatTarget(target))
|
||||
return false;
|
||||
|
||||
if (!string.IsNullOrEmpty(requiredTag) && !target.CompareTag(requiredTag))
|
||||
return false;
|
||||
|
||||
if (!float.IsInfinity(maxDistance))
|
||||
{
|
||||
float distance = Vector3.Distance(transform.position, target.transform.position);
|
||||
if (distance > maxDistance)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 위협 누적 대상이 유효한지 확인합니다.
|
||||
/// </summary>
|
||||
private bool IsValidThreatTarget(GameObject target)
|
||||
{
|
||||
if (target == null || !target.activeInHierarchy)
|
||||
return false;
|
||||
|
||||
if (Colosseum.Team.IsSameTeam(gameObject, target))
|
||||
return false;
|
||||
|
||||
IDamageable damageable = target.GetComponent<IDamageable>();
|
||||
return damageable != null && !damageable.IsDead;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 위협 테이블에서 무효한 대상을 제거합니다.
|
||||
/// </summary>
|
||||
private void CleanupThreatTable()
|
||||
{
|
||||
if (threatTable.Count == 0)
|
||||
return;
|
||||
|
||||
threatTargetBuffer.Clear();
|
||||
foreach (var pair in threatTable)
|
||||
{
|
||||
if (!IsValidThreatTarget(pair.Key))
|
||||
{
|
||||
threatTargetBuffer.Add(pair.Key);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < threatTargetBuffer.Count; i++)
|
||||
{
|
||||
threatTable.Remove(threatTargetBuffer[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 다양한 source 타입에서 실제 GameObject를 추출합니다.
|
||||
/// </summary>
|
||||
private static GameObject ResolveThreatSource(object source)
|
||||
{
|
||||
return source switch
|
||||
{
|
||||
GameObject sourceObject => sourceObject,
|
||||
Component component => component.gameObject,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4
Assets/_Game/Scripts/Network/EditorPlayHostBootstrap.cs
Normal file
4
Assets/_Game/Scripts/Network/EditorPlayHostBootstrap.cs
Normal file
@@ -0,0 +1,4 @@
|
||||
namespace Colosseum.Network
|
||||
{
|
||||
// 임시 삭제 후 Unity 컴파일 캐시 정리를 위한 빈 파일
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f55c7f03b6f5b6e448b8d03880306bb3
|
||||
211
Assets/_Game/Scripts/Player/HitReactionController.cs
Normal file
211
Assets/_Game/Scripts/Player/HitReactionController.cs
Normal file
@@ -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
|
||||
@@ -91,6 +91,10 @@ namespace Colosseum.Player
|
||||
GUILayout.BeginVertical();
|
||||
|
||||
GUILayout.Label($"사망 상태: {(networkController != null && networkController.IsDead ? "Dead" : "Alive")}");
|
||||
if (TryGetComponent<PlayerActionState>(out var actionState))
|
||||
{
|
||||
GUILayout.Label($"무적:{(actionState.IsDamageImmune ? "On" : "Off")} / 다운:{(actionState.IsDowned ? "On" : "Off")} / 이동:{actionState.CanMove} / 스킬:{actionState.CanUseSkills}");
|
||||
}
|
||||
GUILayout.Label("입력 예시: 기절 / Data_Abnormality_Player_Stun / 0");
|
||||
|
||||
GUI.SetNextControlName("AbnormalityInputField");
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
using UnityEngine;
|
||||
@@ -5,6 +6,7 @@ using UnityEngine;
|
||||
using Unity.Netcode;
|
||||
|
||||
using Colosseum.Abnormalities;
|
||||
using Colosseum.Skills;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
@@ -23,6 +25,10 @@ namespace Colosseum.Player
|
||||
[SerializeField] private AbnormalityManager abnormalityManager;
|
||||
[SerializeField] private PlayerActionState actionState;
|
||||
[SerializeField] private PlayerNetworkController networkController;
|
||||
[SerializeField] private PlayerSkillInput skillInput;
|
||||
[SerializeField] private SkillController skillController;
|
||||
[SerializeField] private PlayerMovement playerMovement;
|
||||
[SerializeField] private HitReactionController hitReactionController;
|
||||
|
||||
[Header("Test Data")]
|
||||
[SerializeField] private AbnormalityData stunData;
|
||||
@@ -80,7 +86,7 @@ namespace Colosseum.Player
|
||||
ResolveReferences();
|
||||
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.");
|
||||
yield break;
|
||||
@@ -100,6 +106,16 @@ namespace Colosseum.Player
|
||||
Verify("초기 상태: 사망 아님", !networkController.IsDead);
|
||||
Verify("초기 상태: 이동 가능", actionState.CanMove);
|
||||
Verify("초기 상태: 스킬 사용 가능", actionState.CanUseSkills);
|
||||
Verify("초기 상태: 무적 상태 아님", !actionState.IsDamageImmune);
|
||||
Verify("초기 상태: 마지막 취소 이유 없음", skillController == null || skillController.LastCancelReason == SkillCancelReason.None);
|
||||
|
||||
yield return RunInvincibilitySkillVerification();
|
||||
|
||||
yield return RunStunCancellationVerification();
|
||||
|
||||
yield return RunDownVerification();
|
||||
|
||||
yield return RunKnockbackVerification();
|
||||
|
||||
abnormalityManager.ApplyAbnormality(stunData, gameObject);
|
||||
yield return new WaitForSeconds(settleDelay);
|
||||
@@ -176,6 +192,14 @@ namespace Colosseum.Player
|
||||
actionState = GetComponent<PlayerActionState>();
|
||||
if (networkController == null)
|
||||
networkController = GetComponent<PlayerNetworkController>();
|
||||
if (skillInput == null)
|
||||
skillInput = GetComponent<PlayerSkillInput>();
|
||||
if (skillController == null)
|
||||
skillController = GetComponent<SkillController>();
|
||||
if (playerMovement == null)
|
||||
playerMovement = GetComponent<PlayerMovement>();
|
||||
if (hitReactionController == null)
|
||||
hitReactionController = GetComponent<HitReactionController>();
|
||||
}
|
||||
|
||||
private void LoadDefaultAssetsIfNeeded()
|
||||
@@ -222,5 +246,186 @@ namespace Colosseum.Player
|
||||
return Debug.isDebugBuild;
|
||||
#endif
|
||||
}
|
||||
|
||||
private IEnumerator RunInvincibilitySkillVerification()
|
||||
{
|
||||
SkillData invincibilitySkill = skillInput != null ? skillInput.GetSkill(6) : null;
|
||||
if (invincibilitySkill == null)
|
||||
{
|
||||
AppendLine("[SKIP] 무적 스킬 검증: 7번 슬롯 스킬이 없습니다.");
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (skillController == null || !skillController.ExecuteSkill(invincibilitySkill))
|
||||
{
|
||||
Verify("무적 스킬 검증: 스킬 실행 성공", false);
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return WaitForConditionOrTimeout(() => actionState.IsDamageImmune, GetSkillDuration(invincibilitySkill) + 0.5f);
|
||||
|
||||
float healthBeforeDamage = networkController.Health;
|
||||
|
||||
Verify("무적 적용: IsDamageImmune", actionState.IsDamageImmune);
|
||||
networkController.TakeDamageRpc(15f);
|
||||
yield return new WaitForSeconds(settleDelay);
|
||||
Verify("무적 적용: 대미지 무시", Mathf.Approximately(networkController.Health, healthBeforeDamage));
|
||||
|
||||
if (silenceData != null)
|
||||
{
|
||||
abnormalityManager.ApplyAbnormality(silenceData, gameObject);
|
||||
yield return WaitForConditionOrTimeout(() => actionState.IsSilenced, settleDelay + 0.5f);
|
||||
|
||||
Verify("무적 중 침묵 적용: IsSilenced", actionState.IsSilenced);
|
||||
Verify("무적 중 침묵 적용: 무적 상태 유지", actionState.IsDamageImmune);
|
||||
Verify("무적 중 침묵 적용: 스킬 신규 사용 불가", !actionState.CanStartSkill(invincibilitySkill));
|
||||
|
||||
abnormalityManager.RemoveAbnormality(silenceData);
|
||||
yield return new WaitForSeconds(settleDelay);
|
||||
}
|
||||
|
||||
yield return WaitForConditionOrTimeout(() => !actionState.IsDamageImmune, GetSkillDuration(invincibilitySkill) + 1.5f);
|
||||
|
||||
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)
|
||||
{
|
||||
if (skill == null || skill.SkillClip == null)
|
||||
return settleDelay;
|
||||
|
||||
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)
|
||||
{
|
||||
float elapsed = 0f;
|
||||
while (elapsed < timeout)
|
||||
{
|
||||
if (predicate())
|
||||
yield break;
|
||||
|
||||
elapsed += Time.deltaTime;
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ namespace Colosseum.Player
|
||||
[Tooltip("스킬 실행 관리자")]
|
||||
[SerializeField] private SkillController skillController;
|
||||
|
||||
[Tooltip("피격 제어 관리자")]
|
||||
[SerializeField] private HitReactionController hitReactionController;
|
||||
|
||||
[Tooltip("관전 관리자")]
|
||||
[SerializeField] private PlayerSpectator spectator;
|
||||
|
||||
@@ -35,6 +38,11 @@ namespace Colosseum.Player
|
||||
/// </summary>
|
||||
public bool IsStunned => abnormalityManager != null && abnormalityManager.IsStunned;
|
||||
|
||||
/// <summary>
|
||||
/// 다운 상태 여부
|
||||
/// </summary>
|
||||
public bool IsDowned => hitReactionController != null && hitReactionController.IsDowned;
|
||||
|
||||
/// <summary>
|
||||
/// 침묵 상태 여부
|
||||
/// </summary>
|
||||
@@ -50,6 +58,16 @@ namespace Colosseum.Player
|
||||
/// </summary>
|
||||
public bool IsCastingSkill => skillController != null && skillController.IsPlayingAnimation;
|
||||
|
||||
/// <summary>
|
||||
/// 현재 시전 중인 스킬
|
||||
/// </summary>
|
||||
public SkillData CurrentSkill => skillController != null ? skillController.CurrentSkill : null;
|
||||
|
||||
/// <summary>
|
||||
/// 무적 이상상태 여부
|
||||
/// </summary>
|
||||
public bool IsDamageImmune => abnormalityManager != null && abnormalityManager.IsInvincible;
|
||||
|
||||
/// <summary>
|
||||
/// 입력을 받아도 되는지 여부
|
||||
/// </summary>
|
||||
@@ -58,22 +76,32 @@ namespace Colosseum.Player
|
||||
/// <summary>
|
||||
/// 플레이어가 직접 이동 입력을 사용할 수 있는지 여부
|
||||
/// </summary>
|
||||
public bool CanMove => CanReceiveInput && !IsStunned && !IsCastingSkill;
|
||||
public bool CanMove => CanReceiveInput && !IsStunned && !IsDowned && !BlocksMovementForCurrentSkill();
|
||||
|
||||
/// <summary>
|
||||
/// 점프 가능 여부
|
||||
/// </summary>
|
||||
public bool CanJump => CanMove;
|
||||
public bool CanJump => CanReceiveInput && !IsStunned && !IsDowned && !BlocksJumpForCurrentSkill();
|
||||
|
||||
/// <summary>
|
||||
/// 스킬 사용 가능 여부
|
||||
/// 일반 스킬 시작 가능 여부
|
||||
/// </summary>
|
||||
public bool CanUseSkills => CanReceiveInput && !IsStunned && !IsSilenced && !IsCastingSkill;
|
||||
public bool CanUseSkills => CanReceiveInput && !IsStunned && !IsDowned && !IsSilenced && !BlocksSkillUseForCurrentSkill();
|
||||
|
||||
/// <summary>
|
||||
/// 회피 사용 가능 여부
|
||||
/// 특정 스킬의 시작 가능 여부.
|
||||
/// 스킬 이름과 무관하게 동일한 시작 규칙을 사용합니다.
|
||||
/// </summary>
|
||||
public bool CanEvade => CanUseSkills;
|
||||
public bool CanStartSkill(SkillData skill)
|
||||
{
|
||||
if (skill == null)
|
||||
return false;
|
||||
|
||||
if (!CanReceiveInput || IsStunned || IsDowned || IsSilenced)
|
||||
return false;
|
||||
|
||||
return !BlocksSkillUseForCurrentSkill();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 이동 속도 배율
|
||||
@@ -82,7 +110,7 @@ namespace Colosseum.Player
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!CanReceiveInput || IsStunned)
|
||||
if (!CanReceiveInput || IsStunned || IsDowned)
|
||||
return 0f;
|
||||
|
||||
return abnormalityManager != null ? abnormalityManager.MoveSpeedMultiplier : 1f;
|
||||
@@ -97,8 +125,43 @@ namespace Colosseum.Player
|
||||
abnormalityManager = GetComponent<AbnormalityManager>();
|
||||
if (skillController == null)
|
||||
skillController = GetComponent<SkillController>();
|
||||
if (hitReactionController == null)
|
||||
hitReactionController = GetOrCreateHitReactionController();
|
||||
if (spectator == null)
|
||||
spectator = GetComponentInChildren<PlayerSpectator>();
|
||||
}
|
||||
|
||||
private bool BlocksMovementForCurrentSkill()
|
||||
{
|
||||
if (!IsCastingSkill)
|
||||
return false;
|
||||
|
||||
return CurrentSkill == null || CurrentSkill.BlockMovementWhileCasting;
|
||||
}
|
||||
|
||||
private bool BlocksJumpForCurrentSkill()
|
||||
{
|
||||
if (!IsCastingSkill)
|
||||
return false;
|
||||
|
||||
return CurrentSkill == null || CurrentSkill.BlockJumpWhileCasting;
|
||||
}
|
||||
|
||||
private bool BlocksSkillUseForCurrentSkill()
|
||||
{
|
||||
if (!IsCastingSkill)
|
||||
return false;
|
||||
|
||||
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 bool isJumping;
|
||||
private bool wasGrounded;
|
||||
private Vector3 forcedMovementVelocity;
|
||||
private float forcedMovementRemaining;
|
||||
|
||||
// 클라이언트가 기록, 서버가 소비하는 월드 스페이스 이동 방향
|
||||
private NetworkVariable<Vector2> netMoveInput = new NetworkVariable<Vector2>(
|
||||
@@ -48,6 +50,7 @@ namespace Colosseum.Player
|
||||
public float CurrentMoveSpeed => netMoveInput.Value.magnitude * moveSpeed * GetMoveSpeedMultiplier();
|
||||
public bool IsGrounded => characterController != null ? characterController.isGrounded : false;
|
||||
public bool IsJumping => isJumping;
|
||||
public bool IsForcedMoving => forcedMovementRemaining > 0f && forcedMovementVelocity.sqrMagnitude > 0.0001f;
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
@@ -115,6 +118,7 @@ namespace Colosseum.Player
|
||||
{
|
||||
CleanupInputActions();
|
||||
moveInput = Vector2.zero;
|
||||
ClearForcedMovement();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
@@ -143,6 +147,7 @@ namespace Colosseum.Player
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
CleanupInputActions();
|
||||
ClearForcedMovement();
|
||||
}
|
||||
|
||||
private void OnMovePerformed(InputAction.CallbackContext context) => moveInput = context.ReadValue<Vector2>();
|
||||
@@ -259,10 +264,12 @@ namespace Colosseum.Player
|
||||
{
|
||||
if (characterController == null) return;
|
||||
|
||||
Vector3 forcedDelta = ConsumeForcedMovementDelta(Time.deltaTime);
|
||||
|
||||
if (skillController != null && skillController.IsPlayingAnimation)
|
||||
{
|
||||
if (!skillController.UsesRootMotion)
|
||||
characterController.Move(velocity * Time.deltaTime);
|
||||
characterController.Move(velocity * Time.deltaTime + forcedDelta);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -278,7 +285,7 @@ namespace Colosseum.Player
|
||||
moveDirection = Vector3.zero;
|
||||
|
||||
float actualMoveSpeed = moveSpeed * GetMoveSpeedMultiplier();
|
||||
characterController.Move((moveDirection * actualMoveSpeed + velocity) * Time.deltaTime);
|
||||
characterController.Move((moveDirection * actualMoveSpeed + velocity) * Time.deltaTime + forcedDelta);
|
||||
|
||||
if (moveDirection != Vector3.zero)
|
||||
{
|
||||
@@ -323,6 +330,33 @@ namespace Colosseum.Player
|
||||
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()
|
||||
{
|
||||
var foundState = GetComponent<PlayerActionState>();
|
||||
@@ -343,6 +377,7 @@ namespace Colosseum.Player
|
||||
if (!skillController.UsesRootMotion) return;
|
||||
|
||||
Vector3 deltaPosition = animator.deltaPosition;
|
||||
Vector3 forcedDelta = ConsumeForcedMovementDelta(Time.deltaTime);
|
||||
|
||||
if (blockedDirection != Vector3.zero)
|
||||
{
|
||||
@@ -357,11 +392,11 @@ namespace Colosseum.Player
|
||||
if (skillController.IgnoreRootMotionY)
|
||||
{
|
||||
deltaPosition.y = 0f;
|
||||
characterController.Move(deltaPosition + velocity * Time.deltaTime);
|
||||
characterController.Move(deltaPosition + velocity * Time.deltaTime + forcedDelta);
|
||||
}
|
||||
else
|
||||
{
|
||||
characterController.Move(deltaPosition);
|
||||
characterController.Move(deltaPosition + forcedDelta);
|
||||
}
|
||||
|
||||
if (animator.deltaRotation != Quaternion.identity)
|
||||
@@ -372,5 +407,22 @@ namespace Colosseum.Player
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,9 @@ namespace Colosseum.Player
|
||||
[Tooltip("CharacterStats 컴포넌트 (없으면 자동 검색)")]
|
||||
[SerializeField] private CharacterStats characterStats;
|
||||
|
||||
[Tooltip("이상상태 관리자 (없으면 자동 검색)")]
|
||||
[SerializeField] private AbnormalityManager abnormalityManager;
|
||||
|
||||
// 네트워크 동기화 변수
|
||||
private NetworkVariable<float> currentHealth = new NetworkVariable<float>(100f);
|
||||
private NetworkVariable<float> currentMana = new NetworkVariable<float>(50f);
|
||||
@@ -50,6 +53,11 @@ namespace Colosseum.Player
|
||||
characterStats = GetComponent<CharacterStats>();
|
||||
}
|
||||
|
||||
if (abnormalityManager == null)
|
||||
{
|
||||
abnormalityManager = GetComponent<AbnormalityManager>();
|
||||
}
|
||||
|
||||
// 네트워크 변수 변경 콜백 등록
|
||||
currentHealth.OnValueChanged += HandleHealthChanged;
|
||||
currentMana.OnValueChanged += HandleManaChanged;
|
||||
@@ -93,7 +101,7 @@ namespace Colosseum.Player
|
||||
[Rpc(SendTo.Server)]
|
||||
public void TakeDamageRpc(float damage)
|
||||
{
|
||||
if (isDead.Value) return;
|
||||
if (isDead.Value || IsDamageImmune()) return;
|
||||
|
||||
currentHealth.Value = Mathf.Max(0f, currentHealth.Value - damage);
|
||||
|
||||
@@ -159,7 +167,6 @@ namespace Colosseum.Player
|
||||
isDead.Value = true;
|
||||
|
||||
// 사망 시 활성 이상 상태를 정리해 리스폰 시 잔존하지 않게 합니다.
|
||||
var abnormalityManager = GetComponent<AbnormalityManager>();
|
||||
if (abnormalityManager != null)
|
||||
{
|
||||
abnormalityManager.RemoveAllAbnormalities();
|
||||
@@ -169,9 +176,16 @@ namespace Colosseum.Player
|
||||
var movement = GetComponent<PlayerMovement>();
|
||||
if (movement != null)
|
||||
{
|
||||
movement.ClearForcedMovement();
|
||||
movement.enabled = false;
|
||||
}
|
||||
|
||||
var hitReactionController = GetComponent<HitReactionController>();
|
||||
if (hitReactionController != null)
|
||||
{
|
||||
hitReactionController.ClearHitReactionState();
|
||||
}
|
||||
|
||||
// 스킬 입력 비활성화
|
||||
var skillInput = GetComponent<PlayerSkillInput>();
|
||||
if (skillInput != null)
|
||||
@@ -183,7 +197,7 @@ namespace Colosseum.Player
|
||||
var skillController = GetComponent<SkillController>();
|
||||
if (skillController != null)
|
||||
{
|
||||
skillController.CancelSkill();
|
||||
skillController.CancelSkill(SkillCancelReason.Death);
|
||||
}
|
||||
|
||||
// 모든 클라이언트에서 사망 애니메이션 재생
|
||||
@@ -202,7 +216,6 @@ namespace Colosseum.Player
|
||||
{
|
||||
if (!IsServer) return;
|
||||
|
||||
var abnormalityManager = GetComponent<AbnormalityManager>();
|
||||
if (abnormalityManager != null)
|
||||
{
|
||||
abnormalityManager.RemoveAllAbnormalities();
|
||||
@@ -216,9 +229,16 @@ namespace Colosseum.Player
|
||||
var movement = GetComponent<PlayerMovement>();
|
||||
if (movement != null)
|
||||
{
|
||||
movement.ClearForcedMovement();
|
||||
movement.enabled = true;
|
||||
}
|
||||
|
||||
var hitReactionController = GetComponent<HitReactionController>();
|
||||
if (hitReactionController != null)
|
||||
{
|
||||
hitReactionController.ClearHitReactionState();
|
||||
}
|
||||
|
||||
// 스킬 입력 재활성화
|
||||
var skillInput = GetComponent<PlayerSkillInput>();
|
||||
if (skillInput != null)
|
||||
@@ -233,6 +253,12 @@ namespace Colosseum.Player
|
||||
animator.Rebind();
|
||||
}
|
||||
|
||||
var skillController = GetComponent<SkillController>();
|
||||
if (skillController != null)
|
||||
{
|
||||
skillController.CancelSkill(SkillCancelReason.Respawn);
|
||||
}
|
||||
|
||||
OnRespawned?.Invoke(this);
|
||||
|
||||
Debug.Log($"[Player] Player {OwnerClientId} respawned!");
|
||||
@@ -244,7 +270,7 @@ namespace Colosseum.Player
|
||||
/// </summary>
|
||||
public float TakeDamage(float damage, object source = null)
|
||||
{
|
||||
if (!IsServer || isDead.Value) return 0f;
|
||||
if (!IsServer || isDead.Value || IsDamageImmune()) return 0f;
|
||||
|
||||
float actualDamage = Mathf.Min(damage, currentHealth.Value);
|
||||
currentHealth.Value = Mathf.Max(0f, currentHealth.Value - damage);
|
||||
@@ -269,6 +295,11 @@ namespace Colosseum.Player
|
||||
|
||||
return actualHeal;
|
||||
}
|
||||
|
||||
private bool IsDamageImmune()
|
||||
{
|
||||
return abnormalityManager != null && abnormalityManager.IsInvincible;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Colosseum.Player
|
||||
public class PlayerSkillInput : NetworkBehaviour
|
||||
{
|
||||
[Header("Skill Slots")]
|
||||
[Tooltip("각 슬롯에 등록할 스킬 데이터 (6개 + 긴급회피)")]
|
||||
[Tooltip("각 슬롯에 등록할 스킬 데이터 (6개 + 추가 슬롯)")]
|
||||
[SerializeField] private SkillData[] skillSlots = new SkillData[7];
|
||||
|
||||
[Header("References")]
|
||||
@@ -131,7 +131,7 @@ namespace Colosseum.Player
|
||||
}
|
||||
|
||||
// 사망 상태 체크
|
||||
if (actionState != null && !actionState.CanUseSkills)
|
||||
if (actionState != null && !actionState.CanStartSkill(skill))
|
||||
return;
|
||||
|
||||
// 로컬 체크 (빠른 피드백용)
|
||||
@@ -172,8 +172,7 @@ namespace Colosseum.Player
|
||||
if (skill == null) return;
|
||||
|
||||
// 서버에서 다시 검증
|
||||
// 사망 상태 체크
|
||||
if (actionState != null && !actionState.CanUseSkills)
|
||||
if (actionState != null && !actionState.CanStartSkill(skill))
|
||||
return;
|
||||
|
||||
if (skillController.IsExecutingSkill || skillController.IsOnCooldown(skill))
|
||||
@@ -263,7 +262,7 @@ namespace Colosseum.Player
|
||||
SkillData skill = GetSkill(slotIndex);
|
||||
if (skill == null) return false;
|
||||
|
||||
if (actionState != null && !actionState.CanUseSkills)
|
||||
if (actionState != null && !actionState.CanStartSkill(skill))
|
||||
return false;
|
||||
|
||||
return !skillController.IsOnCooldown(skill) && !skillController.IsExecutingSkill;
|
||||
|
||||
46
Assets/_Game/Scripts/Skills/Effects/DownEffect.cs
Normal file
46
Assets/_Game/Scripts/Skills/Effects/DownEffect.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
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;
|
||||
}
|
||||
|
||||
HitReactionController hitReactionController = target.GetComponent<HitReactionController>();
|
||||
if (hitReactionController == null)
|
||||
hitReactionController = target.GetComponentInParent<HitReactionController>();
|
||||
|
||||
if (hitReactionController == null)
|
||||
{
|
||||
Debug.LogWarning($"[DownEffect] HitReactionController not found on target: {target.name}");
|
||||
return;
|
||||
}
|
||||
|
||||
hitReactionController.ApplyDown(duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/_Game/Scripts/Skills/Effects/DownEffect.cs.meta
Normal file
2
Assets/_Game/Scripts/Skills/Effects/DownEffect.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 41c96b54a96cdb84c9bda774775b0a1a
|
||||
@@ -0,0 +1,93 @@
|
||||
using UnityEngine;
|
||||
|
||||
using Colosseum.Combat;
|
||||
using Colosseum.Player;
|
||||
using Colosseum.Stats;
|
||||
using Colosseum.Weapons;
|
||||
|
||||
namespace Colosseum.Skills.Effects
|
||||
{
|
||||
/// <summary>
|
||||
/// 피격 제어 상태에 따라 추가 피해를 줄 수 있는 대미지 효과입니다.
|
||||
/// 현재는 다운 상태인 대상에게 추가 배율을 적용합니다.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "HitReactionDamageEffect", menuName = "Colosseum/Skills/Effects/HitReaction Damage")]
|
||||
public class HitReactionDamageEffect : SkillEffect
|
||||
{
|
||||
[Header("Damage Settings")]
|
||||
[Min(0f)] [SerializeField] private float baseDamage = 10f;
|
||||
[SerializeField] private DamageType damageType = DamageType.Physical;
|
||||
[Tooltip("스탯 계수 (1.0 = 100%)")]
|
||||
[Min(0f)] [SerializeField] private float statScaling = 1f;
|
||||
|
||||
[Header("Hit Reaction Bonus")]
|
||||
[Tooltip("다운 상태인 대상에게 추가 배율을 적용할지 여부")]
|
||||
[SerializeField] private bool bonusAgainstDownedTarget = true;
|
||||
|
||||
[Tooltip("다운 상태 대상에게 적용되는 추가 피해 배율")]
|
||||
[Min(1f)] [SerializeField] private float downedDamageMultiplier = 1.5f;
|
||||
|
||||
protected override void ApplyEffect(GameObject caster, GameObject target)
|
||||
{
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
IDamageable damageable = target.GetComponent<IDamageable>();
|
||||
if (damageable == null)
|
||||
return;
|
||||
|
||||
float totalDamage = CalculateDamage(caster);
|
||||
|
||||
HitReactionController hitReactionController = target.GetComponent<HitReactionController>();
|
||||
if (hitReactionController == null)
|
||||
hitReactionController = target.GetComponentInParent<HitReactionController>();
|
||||
|
||||
if (bonusAgainstDownedTarget
|
||||
&& hitReactionController != null
|
||||
&& hitReactionController.IsDowned)
|
||||
{
|
||||
totalDamage *= downedDamageMultiplier;
|
||||
}
|
||||
|
||||
damageable.TakeDamage(totalDamage, caster);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 시전자 스탯 기반 대미지 계산
|
||||
/// 공식: baseDamage + (statDamage * scaling)
|
||||
/// </summary>
|
||||
private float CalculateDamage(GameObject caster)
|
||||
{
|
||||
if (damageType == DamageType.True)
|
||||
{
|
||||
return baseDamage;
|
||||
}
|
||||
|
||||
CharacterStats stats = caster != null ? caster.GetComponent<CharacterStats>() : null;
|
||||
if (stats == null)
|
||||
{
|
||||
return baseDamage;
|
||||
}
|
||||
|
||||
float statDamage = damageType switch
|
||||
{
|
||||
DamageType.Physical => stats.PhysicalDamage,
|
||||
DamageType.Magical => stats.MagicDamage,
|
||||
DamageType.Ranged => stats.Dexterity.FinalValue * 2f,
|
||||
_ => 0f,
|
||||
};
|
||||
|
||||
float baseTotal = baseDamage + (statDamage * statScaling);
|
||||
return baseTotal * GetDamageMultiplier(caster);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 시전자의 무기 데미지 배율 조회
|
||||
/// </summary>
|
||||
private float GetDamageMultiplier(GameObject caster)
|
||||
{
|
||||
WeaponEquipment weaponEquipment = caster != null ? caster.GetComponent<WeaponEquipment>() : null;
|
||||
return weaponEquipment != null ? weaponEquipment.DamageMultiplier : 1f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6fd8c6f4c4454ecdb111d13dc8471024
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,5 +1,7 @@
|
||||
using UnityEngine;
|
||||
|
||||
using Colosseum.Player;
|
||||
|
||||
namespace Colosseum.Skills.Effects
|
||||
{
|
||||
/// <summary>
|
||||
@@ -11,6 +13,7 @@ namespace Colosseum.Skills.Effects
|
||||
[Header("Knockback Settings")]
|
||||
[Min(0f)] [SerializeField] private float force = 5f;
|
||||
[SerializeField] private float upwardForce = 2f;
|
||||
[Min(0.05f)] [SerializeField] private float duration = 0.2f;
|
||||
|
||||
protected override void ApplyEffect(GameObject caster, GameObject target)
|
||||
{
|
||||
@@ -18,13 +21,31 @@ namespace Colosseum.Skills.Effects
|
||||
|
||||
Vector3 direction = target.transform.position - caster.transform.position;
|
||||
direction.y = 0f;
|
||||
if (direction.sqrMagnitude <= 0.0001f)
|
||||
direction = caster.transform.forward;
|
||||
else
|
||||
direction.Normalize();
|
||||
|
||||
Vector3 knockback = direction * force + Vector3.up * upwardForce;
|
||||
Vector3 knockbackVelocity = direction * force + Vector3.up * upwardForce;
|
||||
|
||||
// TODO: 실제 물리 시스템 연동
|
||||
// if (target.TryGetComponent<Rigidbody>(out var rb))
|
||||
// rb.AddForce(knockback, ForceMode.Impulse);
|
||||
HitReactionController hitReactionController = target.GetComponent<HitReactionController>();
|
||||
if (hitReactionController == null)
|
||||
hitReactionController = target.GetComponentInParent<HitReactionController>();
|
||||
|
||||
if (hitReactionController != null)
|
||||
{
|
||||
hitReactionController.ApplyKnockback(knockbackVelocity, duration);
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerMovement playerMovement = target.GetComponent<PlayerMovement>();
|
||||
if (playerMovement == null)
|
||||
playerMovement = target.GetComponentInParent<PlayerMovement>();
|
||||
|
||||
if (playerMovement != null)
|
||||
{
|
||||
playerMovement.ApplyForcedMovement(knockbackVelocity, duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,19 @@ using Unity.Netcode;
|
||||
|
||||
namespace Colosseum.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// 스킬 강제 취소 이유
|
||||
/// </summary>
|
||||
public enum SkillCancelReason
|
||||
{
|
||||
None,
|
||||
Manual,
|
||||
Death,
|
||||
Stun,
|
||||
HitReaction,
|
||||
Respawn,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스킬 실행을 관리하는 컴포넌트.
|
||||
/// 애니메이션 이벤트 기반으로 효과가 발동됩니다.
|
||||
@@ -31,6 +44,12 @@ namespace Colosseum.Skills
|
||||
[Tooltip("범위 표시 지속 시간")]
|
||||
[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 bool skillEndRequested; // OnSkillEnd 이벤트 호출 여부
|
||||
@@ -47,9 +66,14 @@ namespace Colosseum.Skills
|
||||
public bool IgnoreRootMotionY => currentSkill != null && currentSkill.IgnoreRootMotionY;
|
||||
public SkillData CurrentSkill => currentSkill;
|
||||
public Animator Animator => animator;
|
||||
public SkillCancelReason LastCancelReason => lastCancelReason;
|
||||
public string LastCancelledSkillName => lastCancelledSkillName;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
lastCancelledSkillName = string.Empty;
|
||||
lastCancelReason = SkillCancelReason.None;
|
||||
|
||||
if (animator == null)
|
||||
{
|
||||
animator = GetComponentInChildren<Animator>();
|
||||
@@ -135,6 +159,7 @@ namespace Colosseum.Skills
|
||||
currentSkill = skill;
|
||||
skillEndRequested = false;
|
||||
waitingForEndAnimation = false;
|
||||
lastCancelReason = SkillCancelReason.None;
|
||||
|
||||
if (debugMode) Debug.Log($"[Skill] Cast: {skill.SkillName}");
|
||||
|
||||
@@ -148,9 +173,34 @@ namespace Colosseum.Skills
|
||||
PlaySkillClip(skill.SkillClip);
|
||||
}
|
||||
|
||||
TriggerImmediateSelfEffectsIfNeeded(skill);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 애니메이션 이벤트가 없는 자기 자신 대상 효과는 시전 즉시 발동합니다.
|
||||
/// 버프/무적 같은 자기 강화 스킬이 이벤트 누락으로 동작하지 않는 상황을 막기 위한 보정입니다.
|
||||
/// </summary>
|
||||
private void TriggerImmediateSelfEffectsIfNeeded(SkillData skill)
|
||||
{
|
||||
if (skill == null || skill.Effects == null || skill.Effects.Count == 0)
|
||||
return;
|
||||
|
||||
if (skill.SkillClip != null && skill.SkillClip.events != null && skill.SkillClip.events.Length > 0)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < skill.Effects.Count; i++)
|
||||
{
|
||||
SkillEffect effect = skill.Effects[i];
|
||||
if (effect == null || effect.TargetType != TargetType.Self)
|
||||
continue;
|
||||
|
||||
if (debugMode) Debug.Log($"[Skill] Immediate self effect: {effect.name} (index {i})");
|
||||
effect.ExecuteOnCast(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스킬 클립으로 Override Controller 생성 후 재생
|
||||
/// </summary>
|
||||
@@ -314,16 +364,24 @@ namespace Colosseum.Skills
|
||||
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 (debugMode) Debug.Log($"Skill cancelled: {currentSkill.SkillName}");
|
||||
if (currentSkill == null)
|
||||
return false;
|
||||
|
||||
lastCancelledSkillName = currentSkill.SkillName;
|
||||
lastCancelReason = reason;
|
||||
|
||||
Debug.Log($"[Skill] Cancelled: {currentSkill.SkillName} / reason={reason}");
|
||||
|
||||
RestoreBaseController();
|
||||
currentSkill = null;
|
||||
skillEndRequested = false;
|
||||
waitingForEndAnimation = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsOnCooldown(SkillData skill)
|
||||
|
||||
@@ -31,6 +31,14 @@ namespace Colosseum.Skills
|
||||
[Tooltip("스킬 시전 시 대상 위치로 점프 이동 (UseRootMotion + IgnoreRootMotionY=false 필요)")]
|
||||
[SerializeField] private bool jumpToTarget = false;
|
||||
|
||||
[Header("행동 제한")]
|
||||
[Tooltip("시전 중 이동 입력 차단 여부")]
|
||||
[SerializeField] private bool blockMovementWhileCasting = true;
|
||||
[Tooltip("시전 중 점프 입력 차단 여부")]
|
||||
[SerializeField] private bool blockJumpWhileCasting = true;
|
||||
[Tooltip("시전 중 다른 스킬 입력 차단 여부")]
|
||||
[SerializeField] private bool blockOtherSkillsWhileCasting = true;
|
||||
|
||||
[Header("쿨타임 & 비용")]
|
||||
[Min(0f)] [SerializeField] private float cooldown = 1f;
|
||||
[Min(0f)] [SerializeField] private float manaCost = 0f;
|
||||
@@ -51,6 +59,9 @@ namespace Colosseum.Skills
|
||||
public bool UseRootMotion => useRootMotion;
|
||||
public bool IgnoreRootMotionY => ignoreRootMotionY;
|
||||
public bool JumpToTarget => jumpToTarget;
|
||||
public bool BlockMovementWhileCasting => blockMovementWhileCasting;
|
||||
public bool BlockJumpWhileCasting => blockJumpWhileCasting;
|
||||
public bool BlockOtherSkillsWhileCasting => blockOtherSkillsWhileCasting;
|
||||
public IReadOnlyList<SkillEffect> Effects => effects;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user