Compare commits
4 Commits
bd99283f17
...
52b0e682a8
| Author | SHA1 | Date | |
|---|---|---|---|
| 52b0e682a8 | |||
| 08b1e3d95a | |||
| d6d120cb61 | |||
| 0402ca9b6c |
@@ -6,7 +6,7 @@ AnimationClip:
|
|||||||
m_CorrespondingSourceObject: {fileID: 0}
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
m_PrefabInstance: {fileID: 0}
|
m_PrefabInstance: {fileID: 0}
|
||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
m_Name: "Anim_Player_\uACF5\uC911"
|
m_Name: "Anim_Common_\uACF5\uC911"
|
||||||
serializedVersion: 7
|
serializedVersion: 7
|
||||||
m_Legacy: 0
|
m_Legacy: 0
|
||||||
m_Compressed: 0
|
m_Compressed: 0
|
||||||
@@ -36709,5 +36709,5 @@ AnimationClip:
|
|||||||
m_EditorCurves: []
|
m_EditorCurves: []
|
||||||
m_EulerEditorCurves: []
|
m_EulerEditorCurves: []
|
||||||
m_HasGenericRootTransform: 0
|
m_HasGenericRootTransform: 0
|
||||||
m_HasMotionFloatCurves: 0
|
m_HasMotionFloatCurves: 1
|
||||||
m_Events: []
|
m_Events: []
|
||||||
@@ -6,7 +6,7 @@ AnimationClip:
|
|||||||
m_CorrespondingSourceObject: {fileID: 0}
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
m_PrefabInstance: {fileID: 0}
|
m_PrefabInstance: {fileID: 0}
|
||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
m_Name: "Anim_Player_\uC810\uD504"
|
m_Name: "Anim_Common_\uC810\uD504"
|
||||||
serializedVersion: 7
|
serializedVersion: 7
|
||||||
m_Legacy: 0
|
m_Legacy: 0
|
||||||
m_Compressed: 0
|
m_Compressed: 0
|
||||||
@@ -17989,5 +17989,5 @@ AnimationClip:
|
|||||||
m_EditorCurves: []
|
m_EditorCurves: []
|
||||||
m_EulerEditorCurves: []
|
m_EulerEditorCurves: []
|
||||||
m_HasGenericRootTransform: 0
|
m_HasGenericRootTransform: 0
|
||||||
m_HasMotionFloatCurves: 0
|
m_HasMotionFloatCurves: 1
|
||||||
m_Events: []
|
m_Events: []
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ AnimationClip:
|
|||||||
m_CorrespondingSourceObject: {fileID: 0}
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
m_PrefabInstance: {fileID: 0}
|
m_PrefabInstance: {fileID: 0}
|
||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
m_Name: "Anim_Drog_\uC810\uD504"
|
m_Name: "Anim_Drog_\uC810\uD504\uACF5\uC911"
|
||||||
serializedVersion: 7
|
serializedVersion: 7
|
||||||
m_Legacy: 0
|
m_Legacy: 0
|
||||||
m_Compressed: 0
|
m_Compressed: 0
|
||||||
@@ -28819,5 +28819,5 @@ AnimationClip:
|
|||||||
m_EditorCurves: []
|
m_EditorCurves: []
|
||||||
m_EulerEditorCurves: []
|
m_EulerEditorCurves: []
|
||||||
m_HasGenericRootTransform: 0
|
m_HasGenericRootTransform: 0
|
||||||
m_HasMotionFloatCurves: 0
|
m_HasMotionFloatCurves: 1
|
||||||
m_Events: []
|
m_Events: []
|
||||||
@@ -6,7 +6,7 @@ AnimationClip:
|
|||||||
m_CorrespondingSourceObject: {fileID: 0}
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
m_PrefabInstance: {fileID: 0}
|
m_PrefabInstance: {fileID: 0}
|
||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
m_Name: "Anim_Drog_\uC810\uD504 1"
|
m_Name: "Anim_Drog_\uC810\uD504\uC2DC\uC791"
|
||||||
serializedVersion: 7
|
serializedVersion: 7
|
||||||
m_Legacy: 0
|
m_Legacy: 0
|
||||||
m_Compressed: 0
|
m_Compressed: 0
|
||||||
@@ -27586,5 +27586,5 @@ AnimationClip:
|
|||||||
m_EditorCurves: []
|
m_EditorCurves: []
|
||||||
m_EulerEditorCurves: []
|
m_EulerEditorCurves: []
|
||||||
m_HasGenericRootTransform: 0
|
m_HasGenericRootTransform: 0
|
||||||
m_HasMotionFloatCurves: 0
|
m_HasMotionFloatCurves: 1
|
||||||
m_Events: []
|
m_Events: []
|
||||||
@@ -6,7 +6,7 @@ AnimationClip:
|
|||||||
m_CorrespondingSourceObject: {fileID: 0}
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
m_PrefabInstance: {fileID: 0}
|
m_PrefabInstance: {fileID: 0}
|
||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
m_Name: "Anim_Drog_\uC810\uD504 2"
|
m_Name: "Anim_Drog_\uC810\uD504\uCC29\uC9C0"
|
||||||
serializedVersion: 7
|
serializedVersion: 7
|
||||||
m_Legacy: 0
|
m_Legacy: 0
|
||||||
m_Compressed: 0
|
m_Compressed: 0
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 16f9ef4ddd32aba44a2d0c691957e72f
|
guid: 920ea8a73bbf84849b01d3875ff4e4c3
|
||||||
NativeFormatImporter:
|
NativeFormatImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
mainObjectFileID: 7400000
|
mainObjectFileID: 7400000
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 7a180d15c7b07a64485c8dd4ec7a1fa7
|
|
||||||
NativeFormatImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
mainObjectFileID: 7400000
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: a8845febff04ecb48b25dac5321c4481
|
|
||||||
NativeFormatImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
mainObjectFileID: 7400000
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 190622b0cba48234ba7fc295facac207
|
|
||||||
NativeFormatImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
mainObjectFileID: 7400000
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: f43438b6095588f4fb4715bd6df16df8
|
|
||||||
NativeFormatImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
mainObjectFileID: 7400000
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 38a21eded51c5b24bb70a48d387aa565
|
|
||||||
NativeFormatImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
mainObjectFileID: 7400000
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 92a17c8d63463f741a0e9d305a838993
|
|
||||||
NativeFormatImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
mainObjectFileID: 7400000
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 79ef70f9bb079cf4799a4f6935b8d984
|
|
||||||
NativeFormatImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
mainObjectFileID: 7400000
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -6,7 +6,7 @@ AnimationClip:
|
|||||||
m_CorrespondingSourceObject: {fileID: 0}
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
m_PrefabInstance: {fileID: 0}
|
m_PrefabInstance: {fileID: 0}
|
||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
m_Name: "Anim_Player_\uAC15\uD0C0"
|
m_Name: "Anim_Player_\uC591\uC190_\uAC15\uD0C0"
|
||||||
serializedVersion: 7
|
serializedVersion: 7
|
||||||
m_Legacy: 0
|
m_Legacy: 0
|
||||||
m_Compressed: 0
|
m_Compressed: 0
|
||||||
@@ -147393,5 +147393,5 @@ AnimationClip:
|
|||||||
m_EditorCurves: []
|
m_EditorCurves: []
|
||||||
m_EulerEditorCurves: []
|
m_EulerEditorCurves: []
|
||||||
m_HasGenericRootTransform: 0
|
m_HasGenericRootTransform: 0
|
||||||
m_HasMotionFloatCurves: 0
|
m_HasMotionFloatCurves: 1
|
||||||
m_Events: []
|
m_Events: []
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 15519f6d5b8e5ba4194d4b8256ebb60d
|
guid: d6622f6bb6d593c48b903000286de0b6
|
||||||
NativeFormatImporter:
|
NativeFormatImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
mainObjectFileID: 7400000
|
mainObjectFileID: 7400000
|
||||||
576929
Assets/_Game/Animations/Anim_Player_양손_올려베기.anim
Normal file
576929
Assets/_Game/Animations/Anim_Player_양손_올려베기.anim
Normal file
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ AnimationClip:
|
|||||||
m_CorrespondingSourceObject: {fileID: 0}
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
m_PrefabInstance: {fileID: 0}
|
m_PrefabInstance: {fileID: 0}
|
||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
m_Name: "Anim_Common_\uCC0C\uB974\uAE30"
|
m_Name: "Anim_Player_\uC591\uC190_\uCC0C\uB974\uAE30"
|
||||||
serializedVersion: 7
|
serializedVersion: 7
|
||||||
m_Legacy: 0
|
m_Legacy: 0
|
||||||
m_Compressed: 0
|
m_Compressed: 0
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 27f34978bd8e5174cb07562401cea581
|
|
||||||
NativeFormatImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
mainObjectFileID: 7400000
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 12bfabc84bb078b41b91dcb0e73034ff
|
|
||||||
NativeFormatImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
mainObjectFileID: 7400000
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 47db64c106703ee498e4495d1c434b77
|
|
||||||
NativeFormatImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
mainObjectFileID: 7400000
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 665885351f6fc9d4c8b188498edb3d7d
|
|
||||||
NativeFormatImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
mainObjectFileID: 7400000
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: c2676ac491a6fc94eb042b76a9c3406e
|
|
||||||
NativeFormatImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
mainObjectFileID: 7400000
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 6e73cca667dfcd4499c84d1b6ba9d531
|
guid: 30b04e3f2e060db4aabfd308ac605a62
|
||||||
NativeFormatImporter:
|
NativeFormatImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
mainObjectFileID: 7400000
|
mainObjectFileID: 7400000
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: fee9942923e37e64eb04557cd4e28cdf
|
guid: 9b19fd7edff49ae4681b653285f3a162
|
||||||
NativeFormatImporter:
|
NativeFormatImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
mainObjectFileID: 7400000
|
mainObjectFileID: 7400000
|
||||||
@@ -2144,15 +2144,16 @@ MonoBehaviour:
|
|||||||
ShowTopMostFoldoutHeaderGroup: 1
|
ShowTopMostFoldoutHeaderGroup: 1
|
||||||
animator: {fileID: 4019041888965840580}
|
animator: {fileID: 4019041888965840580}
|
||||||
baseController: {fileID: 9100000, guid: 4bd980f1a222c5b468136f7e717925d5, type: 2}
|
baseController: {fileID: 9100000, guid: 4bd980f1a222c5b468136f7e717925d5, type: 2}
|
||||||
baseSkillClip: {fileID: -7717634560727564301, guid: 4005a77aa7d531742b1de1bec27001b1, type: 3}
|
baseSkillClip: {fileID: 7400000, guid: ace5d7e8f405a8846b98bb956f0a9313, type: 2}
|
||||||
|
clipAutoRegisterFilter: _Drog_
|
||||||
registeredClips:
|
registeredClips:
|
||||||
- {fileID: -242498254790479478, guid: 585e8961b6c6e9f4ba96bdb4ffb2cbc3, type: 3}
|
- {fileID: 7400000, guid: 58847e89d27d1b140b1075bba68445c0, type: 2}
|
||||||
- {fileID: 3627526391332626453, guid: 39aaec38fc96c4842b972f1e991e5a46, type: 3}
|
- {fileID: 7400000, guid: 6e73cca667dfcd4499c84d1b6ba9d531, type: 2}
|
||||||
- {fileID: -7717634560727564301, guid: 4005a77aa7d531742b1de1bec27001b1, type: 3}
|
- {fileID: 7400000, guid: ace5d7e8f405a8846b98bb956f0a9313, type: 2}
|
||||||
- {fileID: -8265974341663887746, guid: d3e4690f866332b43b86ee7005291cd0, type: 3}
|
- {fileID: 7400000, guid: 006b9541ce7dcb3469d79f9d9df3d6df, type: 2}
|
||||||
- {fileID: 712281148059590495, guid: b590c58b50c3b554687b172862fa5d9d, type: 3}
|
- {fileID: 7400000, guid: 0b70d6464b876144c84f2410c0359a4f, type: 2}
|
||||||
- {fileID: 6888780564265376159, guid: 827dfeae95fdf6b41b78698f2e846b5f, type: 3}
|
- {fileID: 7400000, guid: e35d6eb3ae2c5a146801c9dd399acd52, type: 2}
|
||||||
- {fileID: -8752051743343580635, guid: 5eaeca917bbeb494eb14ad0e0552c42f, type: 3}
|
- {fileID: 7400000, guid: 4ff85a68bb491e143a001f3af82639ed, type: 2}
|
||||||
- {fileID: 7400000, guid: c8fdea7dee0c6f04bbd27fe565071682, type: 2}
|
- {fileID: 7400000, guid: c8fdea7dee0c6f04bbd27fe565071682, type: 2}
|
||||||
debugMode: 1
|
debugMode: 1
|
||||||
showAreaDebug: 1
|
showAreaDebug: 1
|
||||||
|
|||||||
@@ -257,15 +257,14 @@ namespace Colosseum.Combat.Simulation
|
|||||||
};
|
};
|
||||||
|
|
||||||
float resolvedAnimationSpeed = loadoutEntry.GetResolvedAnimationSpeed();
|
float resolvedAnimationSpeed = loadoutEntry.GetResolvedAnimationSpeed();
|
||||||
float mainClipDuration = ResolveClipDuration(skill.SkillClip, resolvedAnimationSpeed);
|
float totalClipDuration = ResolveTotalClipDuration(skill.AnimationClips, resolvedAnimationSpeed);
|
||||||
float endClipDuration = ResolveClipDuration(skill.EndClip, 1f);
|
|
||||||
int repeatCount = loadoutEntry.GetResolvedRepeatCount();
|
int repeatCount = loadoutEntry.GetResolvedRepeatCount();
|
||||||
snapshot.castDuration = Mathf.Max(MinimumActionDuration, (mainClipDuration * repeatCount) + endClipDuration + ruleSet.MovementLossSecondsPerCast);
|
snapshot.castDuration = Mathf.Max(MinimumActionDuration, (totalClipDuration * repeatCount) + ruleSet.MovementLossSecondsPerCast);
|
||||||
|
|
||||||
Dictionary<int, List<SkillEffect>> effectMap = new Dictionary<int, List<SkillEffect>>();
|
Dictionary<int, List<SkillEffect>> effectMap = new Dictionary<int, List<SkillEffect>>();
|
||||||
loadoutEntry.CollectTriggeredEffects(effectMap);
|
loadoutEntry.CollectTriggeredEffects(effectMap);
|
||||||
|
|
||||||
BuildDamageEvents(snapshot, effectMap, context, weaponDamageMultiplier, ruleSet, mainClipDuration, resolvedAnimationSpeed, repeatCount, warnings);
|
BuildDamageEvents(snapshot, effectMap, context, weaponDamageMultiplier, ruleSet, totalClipDuration, resolvedAnimationSpeed, repeatCount, warnings);
|
||||||
snapshots[slotIndex] = snapshot;
|
snapshots[slotIndex] = snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,7 +277,7 @@ namespace Colosseum.Combat.Simulation
|
|||||||
SimulationContext context,
|
SimulationContext context,
|
||||||
float weaponDamageMultiplier,
|
float weaponDamageMultiplier,
|
||||||
SimulationRuleSet ruleSet,
|
SimulationRuleSet ruleSet,
|
||||||
float mainClipDuration,
|
float totalClipDuration,
|
||||||
float resolvedAnimationSpeed,
|
float resolvedAnimationSpeed,
|
||||||
int repeatCount,
|
int repeatCount,
|
||||||
List<string> warnings)
|
List<string> warnings)
|
||||||
@@ -286,15 +285,30 @@ namespace Colosseum.Combat.Simulation
|
|||||||
if (snapshot == null || effectMap == null || effectMap.Count == 0)
|
if (snapshot == null || effectMap == null || effectMap.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// 모든 클립에서 OnEffect 이벤트를 수집합니다.
|
||||||
List<AnimationEvent> effectEvents = new List<AnimationEvent>();
|
List<AnimationEvent> effectEvents = new List<AnimationEvent>();
|
||||||
AnimationClip clip = snapshot.skill.SkillClip;
|
IReadOnlyList<AnimationClip> clips = snapshot.skill.AnimationClips;
|
||||||
if (clip != null)
|
if (clips != null)
|
||||||
{
|
{
|
||||||
AnimationEvent[] clipEvents = clip.events;
|
float timeOffset = 0f;
|
||||||
for (int i = 0; i < clipEvents.Length; i++)
|
for (int clipIndex = 0; clipIndex < clips.Count; clipIndex++)
|
||||||
{
|
{
|
||||||
if (string.Equals(clipEvents[i].functionName, "OnEffect", StringComparison.Ordinal))
|
AnimationClip clip = clips[clipIndex];
|
||||||
effectEvents.Add(clipEvents[i]);
|
if (clip == null) continue;
|
||||||
|
|
||||||
|
AnimationEvent[] clipEvents = clip.events;
|
||||||
|
for (int i = 0; i < clipEvents.Length; i++)
|
||||||
|
{
|
||||||
|
if (string.Equals(clipEvents[i].functionName, "OnEffect", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
// 이벤트 시간에 이전 클립들의 누적 길이를 더합니다.
|
||||||
|
AnimationEvent offsetEvent = clipEvents[i];
|
||||||
|
offsetEvent.time += timeOffset;
|
||||||
|
effectEvents.Add(offsetEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
timeOffset += clip.length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,7 +317,7 @@ namespace Colosseum.Combat.Simulation
|
|||||||
|
|
||||||
for (int iteration = 0; iteration < repeatCount; iteration++)
|
for (int iteration = 0; iteration < repeatCount; iteration++)
|
||||||
{
|
{
|
||||||
float iterationOffset = mainClipDuration * iteration;
|
float iterationOffset = totalClipDuration * iteration;
|
||||||
|
|
||||||
for (int eventIndex = 0; eventIndex < effectEvents.Count; eventIndex++)
|
for (int eventIndex = 0; eventIndex < effectEvents.Count; eventIndex++)
|
||||||
{
|
{
|
||||||
@@ -546,6 +560,23 @@ namespace Colosseum.Combat.Simulation
|
|||||||
return clip.length / Mathf.Max(0.05f, speed);
|
return clip.length / Mathf.Max(0.05f, speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 클립 목록 전체의 재생 시간을 합산합니다.
|
||||||
|
/// </summary>
|
||||||
|
private static float ResolveTotalClipDuration(IReadOnlyList<AnimationClip> clips, float speed)
|
||||||
|
{
|
||||||
|
if (clips == null || clips.Count == 0)
|
||||||
|
return 0f;
|
||||||
|
|
||||||
|
float total = 0f;
|
||||||
|
for (int i = 0; i < clips.Count; i++)
|
||||||
|
{
|
||||||
|
total += ResolveClipDuration(clips[i], speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
private static List<int> CollectValidPrioritySlots(RotationPolicy rotationPolicy, SkillRuntimeSnapshot[] snapshots)
|
private static List<int> CollectValidPrioritySlots(RotationPolicy rotationPolicy, SkillRuntimeSnapshot[] snapshots)
|
||||||
{
|
{
|
||||||
List<int> validSlots = new List<int>();
|
List<int> validSlots = new List<int>();
|
||||||
|
|||||||
@@ -345,13 +345,28 @@ public static class AnimationClipExtractor
|
|||||||
SerializedObject so = new SerializedObject(skillData);
|
SerializedObject so = new SerializedObject(skillData);
|
||||||
bool modified = false;
|
bool modified = false;
|
||||||
|
|
||||||
if (TryRemapClip(so.FindProperty("skillClip"), fbxToAnimMap, path, "skillClip"))
|
// animationClips 리스트의 각 요소를 remap합니다.
|
||||||
|
SerializedProperty clipsProp = so.FindProperty("animationClips");
|
||||||
|
if (clipsProp != null)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < clipsProp.arraySize; i++)
|
||||||
|
{
|
||||||
|
if (TryRemapClip(clipsProp.GetArrayElementAtIndex(i), fbxToAnimMap, path, $"animationClips[{i}]"))
|
||||||
|
{
|
||||||
|
modified = true;
|
||||||
|
relinkCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 레거시 필드도 remap (마이그레이션 전 애셋 보호)
|
||||||
|
if (TryRemapClip(so.FindProperty("legacySkillClip"), fbxToAnimMap, path, "legacySkillClip"))
|
||||||
{
|
{
|
||||||
modified = true;
|
modified = true;
|
||||||
relinkCount++;
|
relinkCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TryRemapClip(so.FindProperty("endClip"), fbxToAnimMap, path, "endClip"))
|
if (TryRemapClip(so.FindProperty("legacyEndClip"), fbxToAnimMap, path, "legacyEndClip"))
|
||||||
{
|
{
|
||||||
modified = true;
|
modified = true;
|
||||||
relinkCount++;
|
relinkCount++;
|
||||||
@@ -567,8 +582,11 @@ public static class AnimationClipExtractor
|
|||||||
SkillData skillData = AssetDatabase.LoadAssetAtPath<SkillData>(path);
|
SkillData skillData = AssetDatabase.LoadAssetAtPath<SkillData>(path);
|
||||||
if (skillData == null) continue;
|
if (skillData == null) continue;
|
||||||
|
|
||||||
if (skillData.SkillClip != null) clips.Add(skillData.SkillClip);
|
IReadOnlyList<AnimationClip> skillClips = skillData.AnimationClips;
|
||||||
if (skillData.EndClip != null) clips.Add(skillData.EndClip);
|
for (int i = 0; i < skillClips.Count; i++)
|
||||||
|
{
|
||||||
|
if (skillClips[i] != null) clips.Add(skillClips[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── BossPhaseData ──
|
// ── BossPhaseData ──
|
||||||
|
|||||||
101
Assets/_Game/Scripts/Editor/AnimationClipSkillDataMatcher.cs
Normal file
101
Assets/_Game/Scripts/Editor/AnimationClipSkillDataMatcher.cs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
using UnityEditor;
|
||||||
|
|
||||||
|
using Colosseum.Skills;
|
||||||
|
|
||||||
|
namespace Colosseum.Editor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Animations 폴더 내 애니메이션 클립이 변경(생성/수정/삭제/이동)되면
|
||||||
|
/// 관련 SkillData의 애니메이션 클립 목록을 자동 갱신합니다.
|
||||||
|
/// </summary>
|
||||||
|
public class AnimationClipSkillDataMatcher : AssetPostprocessor
|
||||||
|
{
|
||||||
|
private const string AnimationsFolderPath = "Assets/_Game/Animations";
|
||||||
|
|
||||||
|
private static void OnPostprocessAllAssets(
|
||||||
|
string[] importedAssets,
|
||||||
|
string[] deletedAssets,
|
||||||
|
string[] movedAssets,
|
||||||
|
string[] movedFromAssetPaths)
|
||||||
|
{
|
||||||
|
bool hasAnimationChange = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < importedAssets.Length; i++)
|
||||||
|
{
|
||||||
|
if (IsAnimationClipPath(importedAssets[i]))
|
||||||
|
{
|
||||||
|
hasAnimationChange = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasAnimationChange)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < deletedAssets.Length; i++)
|
||||||
|
{
|
||||||
|
if (IsAnimationClipPath(deletedAssets[i]))
|
||||||
|
{
|
||||||
|
hasAnimationChange = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasAnimationChange)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < movedAssets.Length; i++)
|
||||||
|
{
|
||||||
|
if (IsAnimationClipPath(movedAssets[i]) || IsAnimationClipPath(movedFromAssetPaths[i]))
|
||||||
|
{
|
||||||
|
hasAnimationChange = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasAnimationChange)
|
||||||
|
return;
|
||||||
|
|
||||||
|
RefreshAllSkillDataClips();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 지정한 경로가 Animations 폴더 내의 애니메이션 클립인지 확인합니다.
|
||||||
|
/// </summary>
|
||||||
|
private static bool IsAnimationClipPath(string assetPath)
|
||||||
|
{
|
||||||
|
return assetPath != null
|
||||||
|
&& assetPath.StartsWith(AnimationsFolderPath)
|
||||||
|
&& assetPath.EndsWith(".anim", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 모든 SkillData의 애니메이션 클립 목록을 갱신합니다.
|
||||||
|
/// </summary>
|
||||||
|
private static void RefreshAllSkillDataClips()
|
||||||
|
{
|
||||||
|
string[] skillGuids = AssetDatabase.FindAssets("t:SkillData");
|
||||||
|
int refreshedCount = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < skillGuids.Length; i++)
|
||||||
|
{
|
||||||
|
string path = AssetDatabase.GUIDToAssetPath(skillGuids[i]);
|
||||||
|
SkillData skillData = AssetDatabase.LoadAssetAtPath<SkillData>(path);
|
||||||
|
if (skillData == null) continue;
|
||||||
|
|
||||||
|
int clipCountBefore = skillData.AnimationClips.Count;
|
||||||
|
skillData.RefreshAnimationClips();
|
||||||
|
|
||||||
|
if (skillData.AnimationClips.Count != clipCountBefore)
|
||||||
|
refreshedCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (refreshedCount > 0)
|
||||||
|
{
|
||||||
|
AssetDatabase.SaveAssets();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c4524bdb16f89724b96d588200b8a3e8
|
||||||
@@ -54,18 +54,28 @@ public class AnimationSectionSpeedEditor : EditorWindow
|
|||||||
|
|
||||||
// ── 클립 정보 (실제 키프레임 기반) ──
|
// ── 클립 정보 (실제 키프레임 기반) ──
|
||||||
float actualLength = GetActualLastKeyframeTime(clip);
|
float actualLength = GetActualLastKeyframeTime(clip);
|
||||||
|
bool hasBoneCurves = actualLength > 0f;
|
||||||
|
|
||||||
|
// 본 트랜스폼 곡선이 없으면 m_StopTime을 기준으로 사용
|
||||||
|
// (Mixamo export 클립 등 m_FloatCurves만 있는 경우)
|
||||||
|
float effectiveLength = hasBoneCurves ? actualLength : clip.length;
|
||||||
|
|
||||||
EditorGUILayout.LabelField("Clip Info", EditorStyles.boldLabel);
|
EditorGUILayout.LabelField("Clip Info", EditorStyles.boldLabel);
|
||||||
EditorGUI.indentLevel++;
|
EditorGUI.indentLevel++;
|
||||||
EditorGUILayout.LabelField("Length (keyframes)", $"{actualLength:F3}s");
|
EditorGUILayout.LabelField("Length", $"{effectiveLength:F3}s");
|
||||||
EditorGUILayout.LabelField("m_StopTime", $"{clip.length:F3}s");
|
|
||||||
|
|
||||||
if (Mathf.Abs(clip.length - actualLength) > 0.01f)
|
if (hasBoneCurves && Mathf.Abs(clip.length - actualLength) > 0.01f)
|
||||||
{
|
{
|
||||||
|
EditorGUILayout.LabelField("m_StopTime", $"{clip.length:F3}s");
|
||||||
EditorGUILayout.HelpBox($"m_StopTime({clip.length:F3}s)이 실제 키프레임 길이({actualLength:F3}s)와 다릅니다.\n" +
|
EditorGUILayout.HelpBox($"m_StopTime({clip.length:F3}s)이 실제 키프레임 길이({actualLength:F3}s)와 다릅니다.\n" +
|
||||||
"스피드 변경 시 실제 키프레임 길이를 기준으로 계산합니다.", MessageType.Warning);
|
"스피드 변경 시 실제 키프레임 길이를 기준으로 계산합니다.", MessageType.Warning);
|
||||||
}
|
}
|
||||||
|
else if (!hasBoneCurves)
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField(" (본 곡선 없음, m_StopTime 기준)", EditorStyles.miniLabel);
|
||||||
|
}
|
||||||
|
|
||||||
EditorGUILayout.LabelField("Total Frames ({fps}fps)", $"{Mathf.Max(1, Mathf.FloorToInt(actualLength * fps))}");
|
EditorGUILayout.LabelField("Total Frames ({fps}fps)", $"{Mathf.Max(1, Mathf.FloorToInt(effectiveLength * fps))}");
|
||||||
EditorGUI.indentLevel--;
|
EditorGUI.indentLevel--;
|
||||||
|
|
||||||
EditorGUILayout.Space();
|
EditorGUILayout.Space();
|
||||||
@@ -74,7 +84,7 @@ public class AnimationSectionSpeedEditor : EditorWindow
|
|||||||
fps = EditorGUILayout.IntField("FPS", fps);
|
fps = EditorGUILayout.IntField("FPS", fps);
|
||||||
fps = Mathf.Max(1, fps);
|
fps = Mathf.Max(1, fps);
|
||||||
|
|
||||||
int totalFrames = Mathf.Max(1, Mathf.FloorToInt(actualLength * fps));
|
int totalFrames = Mathf.Max(1, Mathf.FloorToInt(effectiveLength * fps));
|
||||||
|
|
||||||
// ── 프레임 범위 ──
|
// ── 프레임 범위 ──
|
||||||
EditorGUILayout.LabelField("Target Frame Range", EditorStyles.boldLabel);
|
EditorGUILayout.LabelField("Target Frame Range", EditorStyles.boldLabel);
|
||||||
@@ -108,7 +118,7 @@ public class AnimationSectionSpeedEditor : EditorWindow
|
|||||||
EditorGUILayout.Space();
|
EditorGUILayout.Space();
|
||||||
|
|
||||||
// ── 타임라인 시각화 ──
|
// ── 타임라인 시각화 ──
|
||||||
DrawTimeline(startTime, endTime, actualLength);
|
DrawTimeline(startTime, endTime, effectiveLength);
|
||||||
|
|
||||||
EditorGUILayout.Space();
|
EditorGUILayout.Space();
|
||||||
|
|
||||||
@@ -133,7 +143,7 @@ public class AnimationSectionSpeedEditor : EditorWindow
|
|||||||
// ── 미리보기 ──
|
// ── 미리보기 ──
|
||||||
float newDuration = duration / speedMultiplier;
|
float newDuration = duration / speedMultiplier;
|
||||||
float timeDelta = newDuration - duration;
|
float timeDelta = newDuration - duration;
|
||||||
float newLength = actualLength + timeDelta;
|
float newLength = effectiveLength + timeDelta;
|
||||||
|
|
||||||
EditorGUILayout.LabelField("Preview", EditorStyles.boldLabel);
|
EditorGUILayout.LabelField("Preview", EditorStyles.boldLabel);
|
||||||
EditorGUI.indentLevel++;
|
EditorGUI.indentLevel++;
|
||||||
@@ -301,6 +311,7 @@ public class AnimationSectionSpeedEditor : EditorWindow
|
|||||||
float newDuration = duration / speedMultiplier;
|
float newDuration = duration / speedMultiplier;
|
||||||
float timeDelta = newDuration - duration;
|
float timeDelta = newDuration - duration;
|
||||||
float actualLength = GetActualLastKeyframeTime(clip);
|
float actualLength = GetActualLastKeyframeTime(clip);
|
||||||
|
float effectiveLength = actualLength > 0f ? actualLength : clip.length;
|
||||||
|
|
||||||
Undo.RegisterCompleteObjectUndo(clip, $"Section Speed {speedMultiplier}x (frames {startFrame}~{endFrame})");
|
Undo.RegisterCompleteObjectUndo(clip, $"Section Speed {speedMultiplier}x (frames {startFrame}~{endFrame})");
|
||||||
|
|
||||||
@@ -392,7 +403,7 @@ public class AnimationSectionSpeedEditor : EditorWindow
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── 5. m_StopTime 갱신 (SerializedObject로) ──
|
// ── 5. m_StopTime 갱신 (SerializedObject로) ──
|
||||||
float newStopTime = actualLength + timeDelta;
|
float newStopTime = effectiveLength + timeDelta;
|
||||||
var serializedClip = new SerializedObject(clip);
|
var serializedClip = new SerializedObject(clip);
|
||||||
SerializedProperty stopTimeProp = serializedClip.FindProperty("m_StopTime");
|
SerializedProperty stopTimeProp = serializedClip.FindProperty("m_StopTime");
|
||||||
if (stopTimeProp != null)
|
if (stopTimeProp != null)
|
||||||
@@ -433,7 +444,7 @@ public class AnimationSectionSpeedEditor : EditorWindow
|
|||||||
|
|
||||||
Debug.Log($"[SectionSpeedEditor] {clip.name}: frames {startFrame}~{endFrame} → {speedMultiplier}x " +
|
Debug.Log($"[SectionSpeedEditor] {clip.name}: frames {startFrame}~{endFrame} → {speedMultiplier}x " +
|
||||||
$"({duration:F3}s → {newDuration:F3}s) " +
|
$"({duration:F3}s → {newDuration:F3}s) " +
|
||||||
$"| clip: {actualLength:F3}s → {newStopTime:F3}s " +
|
$"| clip: {effectiveLength:F3}s → {newStopTime:F3}s " +
|
||||||
$"| curves: {modifiedCurves}, keyframes: {modifiedKeyframes}, skipped: {skippedCurves}" +
|
$"| curves: {modifiedCurves}, keyframes: {modifiedKeyframes}, skipped: {skippedCurves}" +
|
||||||
$" | events: {(eventsModified ? "modified" : "none")}");
|
$" | events: {(eventsModified ? "modified" : "none")}");
|
||||||
}
|
}
|
||||||
|
|||||||
103
Assets/_Game/Scripts/Editor/BaseSkillClipAssigner.cs
Normal file
103
Assets/_Game/Scripts/Editor/BaseSkillClipAssigner.cs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.Animations;
|
||||||
|
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Colosseum.Editor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// AnimatorController의 Skill 상태에 base clip을 일괄 할당하는 에디터 도구입니다.
|
||||||
|
/// </summary>
|
||||||
|
public static class BaseSkillClipAssigner
|
||||||
|
{
|
||||||
|
private const string SkillStateName = "Skill";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 선택한 애니메이션 클립을 모든 AnimatorController의 Skill 상태에 할당합니다.
|
||||||
|
/// </summary>
|
||||||
|
[MenuItem("Tools/Animation/Assign Base Skill Clip to All Controllers")]
|
||||||
|
private static void AssignBaseClipToAllControllers()
|
||||||
|
{
|
||||||
|
AnimationClip selectedClip = Selection.activeObject as AnimationClip;
|
||||||
|
if (selectedClip == null)
|
||||||
|
{
|
||||||
|
EditorUtility.DisplayDialog(
|
||||||
|
"Base Skill Clip 할당",
|
||||||
|
"Animations 폴더에서 할당할 AnimationClip을 선택한 후 다시 실행하세요.",
|
||||||
|
"확인");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] controllerGuids = AssetDatabase.FindAssets("t:AnimatorController", new[] { "Assets/_Game" });
|
||||||
|
int assignedCount = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < controllerGuids.Length; i++)
|
||||||
|
{
|
||||||
|
string path = AssetDatabase.GUIDToAssetPath(controllerGuids[i]);
|
||||||
|
AnimatorController ac = AssetDatabase.LoadAssetAtPath<AnimatorController>(path);
|
||||||
|
if (ac == null) continue;
|
||||||
|
|
||||||
|
if (TryAssignClipToState(ac, SkillStateName, selectedClip))
|
||||||
|
assignedCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assignedCount > 0)
|
||||||
|
{
|
||||||
|
AssetDatabase.SaveAssets();
|
||||||
|
EditorUtility.DisplayDialog(
|
||||||
|
"Base Skill Clip 할당 완료",
|
||||||
|
$"{selectedClip.name}을(를) {assignedCount}개 컨트롤러의 Skill 상태에 할당했습니다.",
|
||||||
|
"확인");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EditorUtility.DisplayDialog(
|
||||||
|
"Base Skill Clip 할당",
|
||||||
|
"Skill 상태를 가진 컨트롤러를 찾지 못했습니다.",
|
||||||
|
"확인");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AnimatorController의 지정한 상태에 클립을 할당합니다.
|
||||||
|
/// </summary>
|
||||||
|
private static bool TryAssignClipToState(AnimatorController ac, string stateName, AnimationClip clip)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < ac.layers.Length; i++)
|
||||||
|
{
|
||||||
|
if (TryAssignClipInStateMachine(ac.layers[i].stateMachine, stateName, clip))
|
||||||
|
{
|
||||||
|
EditorUtility.SetDirty(ac);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// StateMachine을 재귀적으로 탐색하여 지정한 이름의 상태에 클립을 할당합니다.
|
||||||
|
/// </summary>
|
||||||
|
private static bool TryAssignClipInStateMachine(AnimatorStateMachine sm, string stateName, AnimationClip clip)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < sm.states.Length; i++)
|
||||||
|
{
|
||||||
|
if (sm.states[i].state.name == stateName)
|
||||||
|
{
|
||||||
|
sm.states[i].state.motion = clip;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < sm.stateMachines.Length; i++)
|
||||||
|
{
|
||||||
|
if (TryAssignClipInStateMachine(sm.stateMachines[i].stateMachine, stateName, clip))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 1a6c131eb50620a47ab0250c9d46d438
|
||||||
@@ -178,7 +178,6 @@ namespace Colosseum.Enemy
|
|||||||
var skillCtrl = GetComponent<Colosseum.Skills.SkillController>();
|
var skillCtrl = GetComponent<Colosseum.Skills.SkillController>();
|
||||||
bool needsYMotion = skillCtrl != null
|
bool needsYMotion = skillCtrl != null
|
||||||
&& skillCtrl.IsPlayingAnimation
|
&& skillCtrl.IsPlayingAnimation
|
||||||
&& !skillCtrl.IsInEndAnimation
|
|
||||||
&& skillCtrl.UsesRootMotion
|
&& skillCtrl.UsesRootMotion
|
||||||
&& !skillCtrl.IgnoreRootMotionY;
|
&& !skillCtrl.IgnoreRootMotionY;
|
||||||
|
|
||||||
|
|||||||
@@ -394,10 +394,18 @@ namespace Colosseum.Player
|
|||||||
|
|
||||||
private float GetSkillDuration(SkillData skill)
|
private float GetSkillDuration(SkillData skill)
|
||||||
{
|
{
|
||||||
if (skill == null || skill.SkillClip == null)
|
if (skill == null || skill.AnimationClips.Count == 0)
|
||||||
return settleDelay;
|
return settleDelay;
|
||||||
|
|
||||||
return Mathf.Max(settleDelay, skill.SkillClip.length / Mathf.Max(0.1f, skill.AnimationSpeed));
|
float totalLength = 0f;
|
||||||
|
var clips = skill.AnimationClips;
|
||||||
|
for (int i = 0; i < clips.Count; i++)
|
||||||
|
{
|
||||||
|
if (clips[i] != null)
|
||||||
|
totalLength += clips[i].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Mathf.Max(settleDelay, totalLength / Mathf.Max(0.1f, skill.AnimationSpeed));
|
||||||
}
|
}
|
||||||
|
|
||||||
private SkillData FindCancellableSkill()
|
private SkillData FindCancellableSkill()
|
||||||
|
|||||||
@@ -35,17 +35,18 @@ namespace Colosseum.Skills
|
|||||||
public class SkillController : NetworkBehaviour
|
public class SkillController : NetworkBehaviour
|
||||||
{
|
{
|
||||||
private const string SKILL_STATE_NAME = "Skill";
|
private const string SKILL_STATE_NAME = "Skill";
|
||||||
private const string END_STATE_NAME = "SkillEnd";
|
|
||||||
|
|
||||||
[Header("애니메이션")]
|
[Header("애니메이션")]
|
||||||
[SerializeField] private Animator animator;
|
[SerializeField] private Animator animator;
|
||||||
[Tooltip("기본 Animator Controller (스킬 종료 후 복원용)")]
|
[Tooltip("기본 Animator Controller (스킬 종료 후 복원용)")]
|
||||||
[SerializeField] private RuntimeAnimatorController baseController;
|
[SerializeField] private RuntimeAnimatorController baseController;
|
||||||
[Tooltip("Skill 상태에 연결된 기본 클립 (Override용)")]
|
[Tooltip("Skill 상태에 연결된 기본 클립 (Override용). baseController의 Skill state에서 자동 발견됩니다.")]
|
||||||
[SerializeField] private AnimationClip baseSkillClip;
|
[SerializeField] private AnimationClip baseSkillClip;
|
||||||
|
|
||||||
[Header("네트워크 동기화")]
|
[Header("네트워크 동기화")]
|
||||||
[Tooltip("\"_Player_\" 이름이 포함된 클립이 자동 등록됩니다. 서버→클라이언트 클립 동기화에 사용됩니다.")]
|
[Tooltip("이 이름이 포함된 클립이 자동 등록됩니다. 서버→클라이언트 클립 동기화에 사용됩니다.")]
|
||||||
|
[SerializeField] private string clipAutoRegisterFilter = "_Player_";
|
||||||
|
[Tooltip("자동 등록된 클립 목록 (서버→클라이언트 클립 인덱스 동기화용)")]
|
||||||
[SerializeField] private List<AnimationClip> registeredClips = new();
|
[SerializeField] private List<AnimationClip> registeredClips = new();
|
||||||
|
|
||||||
[Header("설정")]
|
[Header("설정")]
|
||||||
@@ -69,7 +70,7 @@ namespace Colosseum.Skills
|
|||||||
private readonly List<AbnormalityData> currentCastStartAbnormalities = new();
|
private readonly List<AbnormalityData> currentCastStartAbnormalities = new();
|
||||||
private readonly Dictionary<int, List<AbnormalityData>> currentTriggeredAbnormalities = new();
|
private readonly Dictionary<int, List<AbnormalityData>> currentTriggeredAbnormalities = new();
|
||||||
private readonly List<GameObject> currentTriggeredTargetsBuffer = new();
|
private readonly List<GameObject> currentTriggeredTargetsBuffer = new();
|
||||||
private bool waitingForEndAnimation; // EndAnimation 종료 대기 중
|
private int currentClipSequenceIndex; // 현재 재생 중인 클립 순서 (animationClips 내 인덱스)
|
||||||
private int currentRepeatCount = 1;
|
private int currentRepeatCount = 1;
|
||||||
private int currentIterationIndex = 0;
|
private int currentIterationIndex = 0;
|
||||||
private GameObject currentTargetOverride;
|
private GameObject currentTargetOverride;
|
||||||
@@ -80,7 +81,6 @@ namespace Colosseum.Skills
|
|||||||
|
|
||||||
public bool IsExecutingSkill => currentSkill != null;
|
public bool IsExecutingSkill => currentSkill != null;
|
||||||
public bool IsPlayingAnimation => currentSkill != null;
|
public bool IsPlayingAnimation => currentSkill != null;
|
||||||
public bool IsInEndAnimation => waitingForEndAnimation;
|
|
||||||
public bool UsesRootMotion => currentSkill != null && currentSkill.UseRootMotion;
|
public bool UsesRootMotion => currentSkill != null && currentSkill.UseRootMotion;
|
||||||
public bool IgnoreRootMotionY => currentSkill != null && currentSkill.IgnoreRootMotionY;
|
public bool IgnoreRootMotionY => currentSkill != null && currentSkill.IgnoreRootMotionY;
|
||||||
public SkillData CurrentSkill => currentSkill;
|
public SkillData CurrentSkill => currentSkill;
|
||||||
@@ -110,15 +110,73 @@ namespace Colosseum.Skills
|
|||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
private void OnValidate()
|
private void OnValidate()
|
||||||
{
|
{
|
||||||
AutoRegisterPlayerClips();
|
AutoDiscoverBaseSkillClip();
|
||||||
|
AutoRegisterClips();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "_Player_"가 포함된 모든 AnimationClip을 registeredClips에 자동 등록합니다.
|
/// baseController의 Skill 상태에 연결된 클립을 baseSkillClip으로 자동 발견합니다.
|
||||||
|
/// </summary>
|
||||||
|
private void AutoDiscoverBaseSkillClip()
|
||||||
|
{
|
||||||
|
if (baseSkillClip != null) return;
|
||||||
|
if (baseController == null) return;
|
||||||
|
|
||||||
|
var ac = baseController as UnityEditor.Animations.AnimatorController;
|
||||||
|
if (ac == null) return;
|
||||||
|
|
||||||
|
AnimationClip foundClip = FindClipInState(ac, SKILL_STATE_NAME);
|
||||||
|
if (foundClip != null)
|
||||||
|
{
|
||||||
|
baseSkillClip = foundClip;
|
||||||
|
UnityEditor.EditorUtility.SetDirty(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AnimatorController의 지정한 상태에 연결된 AnimationClip을 찾습니다.
|
||||||
|
/// </summary>
|
||||||
|
private static AnimationClip FindClipInState(UnityEditor.Animations.AnimatorController ac, string stateName)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < ac.layers.Length; i++)
|
||||||
|
{
|
||||||
|
AnimationClip clip = FindClipInStateMachine(ac.layers[i].stateMachine, stateName);
|
||||||
|
if (clip != null) return clip;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// StateMachine을 재귀적으로 탐색하여 지정한 이름의 상태에서 클립을 찾습니다.
|
||||||
|
/// </summary>
|
||||||
|
private static AnimationClip FindClipInStateMachine(UnityEditor.Animations.AnimatorStateMachine sm, string stateName)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < sm.states.Length; i++)
|
||||||
|
{
|
||||||
|
if (sm.states[i].state.name == stateName && sm.states[i].state.motion is AnimationClip clip)
|
||||||
|
return clip;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < sm.stateMachines.Length; i++)
|
||||||
|
{
|
||||||
|
AnimationClip clip = FindClipInStateMachine(sm.stateMachines[i].stateMachine, stateName);
|
||||||
|
if (clip != null) return clip;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// clipAutoRegisterFilter 이름이 포함된 모든 AnimationClip을 registeredClips에 자동 등록합니다.
|
||||||
/// 서버→클라이언트 클립 동기화 인덱스의 일관성을 위해 이름순으로 정렬합니다.
|
/// 서버→클라이언트 클립 동기화 인덱스의 일관성을 위해 이름순으로 정렬합니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void AutoRegisterPlayerClips()
|
private void AutoRegisterClips()
|
||||||
{
|
{
|
||||||
|
string trimmedFilter = clipAutoRegisterFilter.Trim('_');
|
||||||
|
if (string.IsNullOrEmpty(trimmedFilter))
|
||||||
|
return;
|
||||||
|
|
||||||
string[] guids = AssetDatabase.FindAssets("t:AnimationClip", new[] { "Assets/_Game/Animations" });
|
string[] guids = AssetDatabase.FindAssets("t:AnimationClip", new[] { "Assets/_Game/Animations" });
|
||||||
var clips = new List<AnimationClip>();
|
var clips = new List<AnimationClip>();
|
||||||
|
|
||||||
@@ -127,7 +185,7 @@ namespace Colosseum.Skills
|
|||||||
string path = AssetDatabase.GUIDToAssetPath(guid);
|
string path = AssetDatabase.GUIDToAssetPath(guid);
|
||||||
string clipName = Path.GetFileNameWithoutExtension(path);
|
string clipName = Path.GetFileNameWithoutExtension(path);
|
||||||
|
|
||||||
if (clipName.IndexOf("_Player_", StringComparison.OrdinalIgnoreCase) >= 0)
|
if (clipName.IndexOf(trimmedFilter, StringComparison.OrdinalIgnoreCase) >= 0)
|
||||||
{
|
{
|
||||||
AnimationClip clip = AssetDatabase.LoadAssetAtPath<AnimationClip>(path);
|
AnimationClip clip = AssetDatabase.LoadAssetAtPath<AnimationClip>(path);
|
||||||
if (clip != null)
|
if (clip != null)
|
||||||
@@ -155,7 +213,7 @@ namespace Colosseum.Skills
|
|||||||
{
|
{
|
||||||
registeredClips.Clear();
|
registeredClips.Clear();
|
||||||
registeredClips.AddRange(clips);
|
registeredClips.AddRange(clips);
|
||||||
Debug.Log($"[SkillController] 자동 등록: {clips.Count}개 Player 클립", this);
|
Debug.Log($"[SkillController] 자동 등록: {clips.Count}개 클립 (필터: {clipAutoRegisterFilter})", this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -167,38 +225,21 @@ namespace Colosseum.Skills
|
|||||||
|
|
||||||
var stateInfo = animator.GetCurrentAnimatorStateInfo(0);
|
var stateInfo = animator.GetCurrentAnimatorStateInfo(0);
|
||||||
|
|
||||||
// EndAnimation 종료 감지
|
// 애니메이션 종료 시 처리
|
||||||
if (waitingForEndAnimation)
|
|
||||||
{
|
|
||||||
if (stateInfo.normalizedTime >= 1f)
|
|
||||||
{
|
|
||||||
if (debugMode) Debug.Log($"[Skill] EndAnimation complete: {currentSkill.SkillName}");
|
|
||||||
RestoreBaseController();
|
|
||||||
ClearCurrentSkillState();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 애니메이션 종료 시 처리 (OnSkillEnd 여부와 관계없이 애니메이션 끝까지 재생)
|
|
||||||
if (stateInfo.normalizedTime >= 1f)
|
if (stateInfo.normalizedTime >= 1f)
|
||||||
{
|
{
|
||||||
|
// 같은 반복 차수 내에서 다음 클립이 있으면 재생
|
||||||
|
if (TryPlayNextClipInSequence())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 다음 반복 차수가 있으면 시작
|
||||||
if (TryStartNextIteration())
|
if (TryStartNextIteration())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (currentSkill.EndClip != null)
|
// 모든 클립과 반복이 끝나면 종료
|
||||||
{
|
if (debugMode) Debug.Log($"[Skill] Animation complete: {currentSkill.SkillName}");
|
||||||
// EndAnimation 재생 후 종료 대기
|
RestoreBaseController();
|
||||||
if (debugMode) Debug.Log($"[Skill] SkillAnimation done, playing EndAnimation: {currentSkill.SkillName}");
|
ClearCurrentSkillState();
|
||||||
PlayEndClip(currentSkill.EndClip);
|
|
||||||
waitingForEndAnimation = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// EndAnimation 없으면 바로 종료
|
|
||||||
if (debugMode) Debug.Log($"[Skill] Animation complete: {currentSkill.SkillName}");
|
|
||||||
RestoreBaseController();
|
|
||||||
ClearCurrentSkillState();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +304,6 @@ namespace Colosseum.Skills
|
|||||||
|
|
||||||
currentLoadoutEntry = loadoutEntry != null ? loadoutEntry.CreateCopy() : SkillLoadoutEntry.CreateTemporary(skill);
|
currentLoadoutEntry = loadoutEntry != null ? loadoutEntry.CreateCopy() : SkillLoadoutEntry.CreateTemporary(skill);
|
||||||
currentSkill = skill;
|
currentSkill = skill;
|
||||||
waitingForEndAnimation = false;
|
|
||||||
lastCancelReason = SkillCancelReason.None;
|
lastCancelReason = SkillCancelReason.None;
|
||||||
BuildResolvedEffects(currentLoadoutEntry);
|
BuildResolvedEffects(currentLoadoutEntry);
|
||||||
currentRepeatCount = currentLoadoutEntry.GetResolvedRepeatCount();
|
currentRepeatCount = currentLoadoutEntry.GetResolvedRepeatCount();
|
||||||
@@ -379,7 +419,7 @@ namespace Colosseum.Skills
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
currentIterationIndex++;
|
currentIterationIndex++;
|
||||||
waitingForEndAnimation = false;
|
currentClipSequenceIndex = 0;
|
||||||
|
|
||||||
if (debugMode && currentRepeatCount > 1)
|
if (debugMode && currentRepeatCount > 1)
|
||||||
{
|
{
|
||||||
@@ -388,18 +428,41 @@ namespace Colosseum.Skills
|
|||||||
|
|
||||||
TriggerCastStartEffects();
|
TriggerCastStartEffects();
|
||||||
|
|
||||||
if (currentSkill.SkillClip != null && animator != null)
|
if (currentSkill.AnimationClips.Count > 0 && animator != null)
|
||||||
{
|
{
|
||||||
float resolvedAnimationSpeed = currentLoadoutEntry != null
|
float resolvedAnimationSpeed = currentLoadoutEntry != null
|
||||||
? currentLoadoutEntry.GetResolvedAnimationSpeed()
|
? currentLoadoutEntry.GetResolvedAnimationSpeed()
|
||||||
: currentSkill.AnimationSpeed;
|
: currentSkill.AnimationSpeed;
|
||||||
animator.speed = resolvedAnimationSpeed;
|
animator.speed = resolvedAnimationSpeed;
|
||||||
PlaySkillClip(currentSkill.SkillClip);
|
PlaySkillClip(currentSkill.AnimationClips[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
TriggerImmediateSelfEffectsIfNeeded();
|
TriggerImmediateSelfEffectsIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 시퀀스 내 다음 클립이 있으면 재생합니다.
|
||||||
|
/// </summary>
|
||||||
|
private bool TryPlayNextClipInSequence()
|
||||||
|
{
|
||||||
|
if (currentSkill == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int nextIndex = currentClipSequenceIndex + 1;
|
||||||
|
if (nextIndex >= currentSkill.AnimationClips.Count)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
currentClipSequenceIndex = nextIndex;
|
||||||
|
PlaySkillClip(currentSkill.AnimationClips[currentClipSequenceIndex]);
|
||||||
|
|
||||||
|
if (debugMode)
|
||||||
|
{
|
||||||
|
Debug.Log($"[Skill] Playing clip {currentClipSequenceIndex + 1}/{currentSkill.AnimationClips.Count}: {currentSkill.AnimationClips[currentClipSequenceIndex].name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 반복 시전이 남아 있으면 다음 차수를 시작합니다.
|
/// 반복 시전이 남아 있으면 다음 차수를 시작합니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -445,31 +508,6 @@ namespace Colosseum.Skills
|
|||||||
PlaySkillClipClientRpc(registeredClips.IndexOf(clip));
|
PlaySkillClipClientRpc(registeredClips.IndexOf(clip));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 종료 클립 재생
|
|
||||||
/// </summary>
|
|
||||||
private void PlayEndClip(AnimationClip clip)
|
|
||||||
{
|
|
||||||
if (baseSkillClip == null)
|
|
||||||
{
|
|
||||||
Debug.LogError("[SkillController] Base Skill Clip is not assigned!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var overrideController = new AnimatorOverrideController(baseController);
|
|
||||||
overrideController[baseSkillClip] = clip;
|
|
||||||
animator.runtimeAnimatorController = overrideController;
|
|
||||||
|
|
||||||
// 애니메이터 완전 리셋 후 재생
|
|
||||||
animator.Rebind();
|
|
||||||
animator.Update(0f);
|
|
||||||
animator.Play(SKILL_STATE_NAME, 0, 0f);
|
|
||||||
|
|
||||||
// 클라이언트에 클립 동기화
|
|
||||||
if (IsServer && IsSpawned)
|
|
||||||
PlaySkillClipClientRpc(registeredClips.IndexOf(clip));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 기본 컨트롤러로 복원
|
/// 기본 컨트롤러로 복원
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -646,7 +684,7 @@ namespace Colosseum.Skills
|
|||||||
currentTriggeredAbnormalities.Clear();
|
currentTriggeredAbnormalities.Clear();
|
||||||
currentTriggeredTargetsBuffer.Clear();
|
currentTriggeredTargetsBuffer.Clear();
|
||||||
currentTargetOverride = null;
|
currentTargetOverride = null;
|
||||||
waitingForEndAnimation = false;
|
currentClipSequenceIndex = 0;
|
||||||
currentRepeatCount = 1;
|
currentRepeatCount = 1;
|
||||||
currentIterationIndex = 0;
|
currentIterationIndex = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,120 @@ namespace Colosseum.Skills
|
|||||||
[CreateAssetMenu(fileName = "NewSkill", menuName = "Colosseum/Skill")]
|
[CreateAssetMenu(fileName = "NewSkill", menuName = "Colosseum/Skill")]
|
||||||
public class SkillData : ScriptableObject
|
public class SkillData : ScriptableObject
|
||||||
{
|
{
|
||||||
|
private const string SkillAssetPrefix = "Data_Skill_";
|
||||||
|
private const string ClipAssetPrefix = "Anim_";
|
||||||
|
private const string AnimationsSearchPath = "Assets/_Game/Animations";
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
/// <summary>
|
||||||
|
/// 레거시 마이그레이션 및 애니메이션 클립 자동 매칭.
|
||||||
|
/// 에디터에서 애셋 로드/수정 시 자동 실행됩니다.
|
||||||
|
/// </summary>
|
||||||
|
private void OnValidate()
|
||||||
|
{
|
||||||
|
MigrateLegacyClips();
|
||||||
|
RefreshAnimationClips();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 기존 skillClip/endClip 데이터를 animationClips 리스트로 이관합니다.
|
||||||
|
/// </summary>
|
||||||
|
private void MigrateLegacyClips()
|
||||||
|
{
|
||||||
|
if (legacySkillClip == null && legacyEndClip == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (animationClips.Count > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (legacySkillClip != null)
|
||||||
|
{
|
||||||
|
animationClips.Add(legacySkillClip);
|
||||||
|
legacySkillClip = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (legacyEndClip != null)
|
||||||
|
{
|
||||||
|
animationClips.Add(legacyEndClip);
|
||||||
|
legacyEndClip = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnityEditor.EditorUtility.SetDirty(this);
|
||||||
|
Debug.Log($"[SkillData] 레거시 클립을 animationClips로 이관 완료: {SkillName ?? name}", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 애셋 이름 기반으로 매칭되는 애니메이션 클립을 자동 수집합니다.
|
||||||
|
/// SkillData 이름이 'Data_Skill_'으로 시작하면 'Anim_{key}_{순서}' 클립을 찾아 animationClips에 채웁니다.
|
||||||
|
/// </summary>
|
||||||
|
public void RefreshAnimationClips()
|
||||||
|
{
|
||||||
|
if (!name.StartsWith(SkillAssetPrefix))
|
||||||
|
return;
|
||||||
|
|
||||||
|
string key = name.Substring(SkillAssetPrefix.Length);
|
||||||
|
if (string.IsNullOrEmpty(key))
|
||||||
|
return;
|
||||||
|
|
||||||
|
string[] guids = UnityEditor.AssetDatabase.FindAssets("t:AnimationClip", new[] { AnimationsSearchPath });
|
||||||
|
var matchedClips = new List<(AnimationClip clip, int order)>();
|
||||||
|
|
||||||
|
for (int i = 0; i < guids.Length; i++)
|
||||||
|
{
|
||||||
|
string path = UnityEditor.AssetDatabase.GUIDToAssetPath(guids[i]);
|
||||||
|
AnimationClip clip = UnityEditor.AssetDatabase.LoadAssetAtPath<AnimationClip>(path);
|
||||||
|
if (clip == null) continue;
|
||||||
|
|
||||||
|
string clipName = clip.name;
|
||||||
|
if (!clipName.StartsWith(ClipAssetPrefix))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
string remaining = clipName.Substring(ClipAssetPrefix.Length);
|
||||||
|
|
||||||
|
if (remaining == key)
|
||||||
|
{
|
||||||
|
// 정확 매칭 (순서 번호 없음) → 최우선
|
||||||
|
matchedClips.Add((clip, -1));
|
||||||
|
}
|
||||||
|
else if (remaining.StartsWith(key + "_"))
|
||||||
|
{
|
||||||
|
string suffix = remaining.Substring(key.Length + 1);
|
||||||
|
if (int.TryParse(suffix, out int order))
|
||||||
|
{
|
||||||
|
matchedClips.Add((clip, order));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchedClips.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 정렬: 순서 번호 없음(-1) → 순서 번호 오름차순
|
||||||
|
matchedClips.Sort((a, b) => a.order.CompareTo(b.order));
|
||||||
|
|
||||||
|
// 변경이 있는 경우만 갱신 (무한 루프 방지)
|
||||||
|
bool changed = matchedClips.Count != animationClips.Count;
|
||||||
|
if (!changed)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < matchedClips.Count; i++)
|
||||||
|
{
|
||||||
|
if (matchedClips[i].clip != animationClips[i])
|
||||||
|
{
|
||||||
|
changed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!changed) return;
|
||||||
|
|
||||||
|
animationClips.Clear();
|
||||||
|
for (int i = 0; i < matchedClips.Count; i++)
|
||||||
|
animationClips.Add(matchedClips[i].clip);
|
||||||
|
|
||||||
|
UnityEditor.EditorUtility.SetDirty(this);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
[Header("기본 정보")]
|
[Header("기본 정보")]
|
||||||
[SerializeField] private string skillName;
|
[SerializeField] private string skillName;
|
||||||
[TextArea(2, 4)]
|
[TextArea(2, 4)]
|
||||||
@@ -73,13 +187,15 @@ namespace Colosseum.Skills
|
|||||||
[SerializeField] private SkillBaseType baseTypes = SkillBaseType.None;
|
[SerializeField] private SkillBaseType baseTypes = SkillBaseType.None;
|
||||||
|
|
||||||
[Header("애니메이션")]
|
[Header("애니메이션")]
|
||||||
[Tooltip("기본 Animator Controller의 'Skill' 상태에 덮어씌워질 클립")]
|
[Tooltip("순차 재생할 클립 목록. 애셋 이름이 Data_Skill_ 접두사면 Anim_{이름}_{순서} 클립을 자동 수집합니다.")]
|
||||||
[SerializeField] private AnimationClip skillClip;
|
[SerializeField] private List<AnimationClip> animationClips = new();
|
||||||
[Tooltip("종료 애니메이션 (선택)")]
|
|
||||||
[SerializeField] private AnimationClip endClip;
|
|
||||||
[Tooltip("애니메이션 재생 속도 (1 = 기본, 2 = 2배속)")]
|
[Tooltip("애니메이션 재생 속도 (1 = 기본, 2 = 2배속)")]
|
||||||
[Min(0.1f)] [SerializeField] private float animationSpeed = 1f;
|
[Min(0.1f)] [SerializeField] private float animationSpeed = 1f;
|
||||||
|
|
||||||
|
// 레거시 마이그레이션 (기존 skillClip/endClip 데이터 보존)
|
||||||
|
[SerializeField, HideInInspector] private AnimationClip legacySkillClip;
|
||||||
|
[SerializeField, HideInInspector] private AnimationClip legacyEndClip;
|
||||||
|
|
||||||
[Header("루트 모션")]
|
[Header("루트 모션")]
|
||||||
[Tooltip("애니메이션의 이동/회전 데이터를 캐릭터에 적용")]
|
[Tooltip("애니메이션의 이동/회전 데이터를 캐릭터에 적용")]
|
||||||
[SerializeField] private bool useRootMotion = false;
|
[SerializeField] private bool useRootMotion = false;
|
||||||
@@ -123,8 +239,15 @@ namespace Colosseum.Skills
|
|||||||
public SkillRoleType SkillRole => skillRole;
|
public SkillRoleType SkillRole => skillRole;
|
||||||
public SkillActivationType ActivationType => activationType;
|
public SkillActivationType ActivationType => activationType;
|
||||||
public SkillBaseType BaseTypes => baseTypes;
|
public SkillBaseType BaseTypes => baseTypes;
|
||||||
public AnimationClip SkillClip => skillClip;
|
/// <summary>
|
||||||
public AnimationClip EndClip => endClip;
|
/// 순차 재생할 클립 목록입니다.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<AnimationClip> AnimationClips => animationClips;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 첫 번째 클립 (레거시 호환성). 기존 코드에서 SkillClip을 참조하는 곳에서 사용됩니다.
|
||||||
|
/// </summary>
|
||||||
|
public AnimationClip SkillClip => animationClips.Count > 0 ? animationClips[0] : null;
|
||||||
public float AnimationSpeed => animationSpeed;
|
public float AnimationSpeed => animationSpeed;
|
||||||
public float Cooldown => cooldown;
|
public float Cooldown => cooldown;
|
||||||
public float ManaCost => manaCost;
|
public float ManaCost => manaCost;
|
||||||
|
|||||||
Reference in New Issue
Block a user