fix: 드로그 패턴 애니메이션 재생 끊김 수정
- BT 재평가 중에도 패턴 실행 상태를 보존하도록 보스 패턴 액션과 런타임 상태를 조정했다. - 스킬 컨트롤러에서 동일 프레임 종료 판정을 막아 패턴 내 다음 스킬이 즉시 잘리는 문제를 수정했다. - 드로그 BT, 패턴/스킬 데이터, 애니메이션 클립과 컨트롤러를 현재 검증된 재생 구성으로 정리했다. - 자연 발동 기준으로 콤보-기본기2 재생 시간을 재검증해 클립 길이와 실제 재생 간격이 맞는 것을 확인했다.
This commit is contained in:
+1143
-1143
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
@@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e35d6eb3ae2c5a146801c9dd399acd52
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 7400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -43628,7 +43628,7 @@ AnimationClip:
|
||||
m_KeepOriginalPositionY: 1
|
||||
m_KeepOriginalPositionXZ: 0
|
||||
m_HeightFromFeet: 0
|
||||
m_Mirror: 1
|
||||
m_Mirror: 0
|
||||
m_EditorCurves: []
|
||||
m_EulerEditorCurves: []
|
||||
m_HasGenericRootTransform: 0
|
||||
|
||||
+99004
-22396
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: 94da51b9da4bad4129ba5e33e671db62
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 7400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+24484
-24951
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4ff85a68bb491e143a001f3af82639ed
|
||||
guid: 2ca3044b7e7a3ff4eb8d3d6bf75f5e92
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 7400000
|
||||
+98053
-30696
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8cfd11750543a4484bae82462b7c0351
|
||||
guid: 6b11f2b53d826bba3ab9f8086a42de14
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 7400000
|
||||
|
||||
+23843
-31528
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b8206d6d914a86cd9a169763c82f273e
|
||||
guid: 156c90879bba0d7649e8b29224daa390
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 7400000
|
||||
|
||||
+24444
-36503
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 16c44854334d767d3af2fd774b89a809
|
||||
guid: 6439cde8bc726bd1caad7d6e18a31416
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 7400000
|
||||
|
||||
+208282
-76612
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 82cf3119cd7b56e3e9d579cac94fc09d
|
||||
guid: 606aec780e456217687074cbbf23a2c8
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 7400000
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 18b092b0aae73b9219db20623b0c3427
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 7400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f572193078ff9e229c1d39038620857
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 7400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0b70d6464b876144c84f2410c0359a4f
|
||||
guid: 16c44854334d767d3af2fd774b89a809
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 7400000
|
||||
@@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9678ad326a270e9aa9cb5ebf5fa00279
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 7400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -161,7 +161,7 @@ AnimatorStateMachine:
|
||||
m_ChildStates:
|
||||
- serializedVersion: 1
|
||||
m_State: {fileID: -4652545120162758660}
|
||||
m_Position: {x: 290, y: 180, z: 0}
|
||||
m_Position: {x: 480, y: 230, z: 0}
|
||||
- serializedVersion: 1
|
||||
m_State: {fileID: -2487449162152911812}
|
||||
m_Position: {x: -220, y: 350, z: 0}
|
||||
@@ -454,30 +454,6 @@ AnimatorController:
|
||||
m_IKPass: 0
|
||||
m_SyncedLayerAffectsTiming: 0
|
||||
m_Controller: {fileID: 9100000}
|
||||
- serializedVersion: 5
|
||||
m_Name: Override
|
||||
m_StateMachine: {fileID: -8724618498758723359}
|
||||
m_Mask: {fileID: 31900000, guid: d878cc26c0d6449d2ad2c0ed02c73eca, type: 2}
|
||||
m_Motions:
|
||||
- serializedVersion: 2
|
||||
m_State: {fileID: 0}
|
||||
m_Motion: {fileID: -1601098376034012026, guid: 86ec0c0d77d3fcd47b879e206e9b492c, type: 3}
|
||||
- serializedVersion: 2
|
||||
m_State: {fileID: -4652545120162758660}
|
||||
m_Motion: {fileID: 691025228808474260, guid: f24af721b97fadb4ba0f82937853e9f7, type: 3}
|
||||
- serializedVersion: 2
|
||||
m_State: {fileID: -2487449162152911812}
|
||||
m_Motion: {fileID: 8640942340808133859, guid: 235e9fb2014c3044ba826dd0e71987a4, type: 3}
|
||||
- serializedVersion: 2
|
||||
m_State: {fileID: 0}
|
||||
m_Motion: {fileID: 9143937929783346939, guid: 92dc2765bff93f540a4433ac13713d72, type: 3}
|
||||
m_Behaviours: []
|
||||
m_BlendingMode: 0
|
||||
m_SyncedLayerIndex: 0
|
||||
m_DefaultWeight: 0
|
||||
m_IKPass: 0
|
||||
m_SyncedLayerAffectsTiming: 0
|
||||
m_Controller: {fileID: 9100000}
|
||||
--- !u!1102 &1582555298472611487
|
||||
AnimatorState:
|
||||
serializedVersion: 6
|
||||
@@ -493,7 +469,7 @@ AnimatorState:
|
||||
- {fileID: 2459631991574308566}
|
||||
m_StateMachineBehaviours: []
|
||||
m_Position: {x: 50, y: 50, z: 0}
|
||||
m_IKOnFeet: 0
|
||||
m_IKOnFeet: 1
|
||||
m_WriteDefaultValues: 1
|
||||
m_Mirror: 0
|
||||
m_SpeedParameterActive: 0
|
||||
|
||||
@@ -22,16 +22,16 @@ MonoBehaviour:
|
||||
Skill: {fileID: 11400000, guid: ae7fc1b970b770680b95f69111f2b08a, type: 2}
|
||||
Duration: 0
|
||||
ChargeData:
|
||||
requiredDamageRatio: 0
|
||||
requiredDamageRatio: 0.1
|
||||
telegraphAbnormality: {fileID: 0}
|
||||
staggerDuration: 0
|
||||
staggerDuration: 2
|
||||
- Type: 0
|
||||
Skill: {fileID: 11400000, guid: e666c41a932cdd478a62552e12c64801, type: 2}
|
||||
Duration: 0
|
||||
ChargeData:
|
||||
requiredDamageRatio: 0
|
||||
requiredDamageRatio: 0.1
|
||||
telegraphAbnormality: {fileID: 0}
|
||||
staggerDuration: 0
|
||||
staggerDuration: 2
|
||||
- Type: 1
|
||||
Skill: {fileID: 0}
|
||||
Duration: 0.1
|
||||
|
||||
@@ -22,16 +22,16 @@ MonoBehaviour:
|
||||
Skill: {fileID: 11400000, guid: d8008b7d595f832798f900b884fb6ac2, type: 2}
|
||||
Duration: 0
|
||||
ChargeData:
|
||||
requiredDamageRatio: 0
|
||||
requiredDamageRatio: 0.1
|
||||
telegraphAbnormality: {fileID: 0}
|
||||
staggerDuration: 0
|
||||
staggerDuration: 2
|
||||
- Type: 0
|
||||
Skill: {fileID: 11400000, guid: a42c075b82a2b40b3b1c4540bea4bd03, type: 2}
|
||||
Duration: 0
|
||||
ChargeData:
|
||||
requiredDamageRatio: 0
|
||||
requiredDamageRatio: 0.1
|
||||
telegraphAbnormality: {fileID: 0}
|
||||
staggerDuration: 0
|
||||
staggerDuration: 2
|
||||
cooldown: 3
|
||||
minPhase: 1
|
||||
skipJumpStepOnNoTarget: 0
|
||||
|
||||
@@ -22,23 +22,23 @@ MonoBehaviour:
|
||||
Skill: {fileID: 11400000, guid: 78fa18c15c0ea5248bc6966b4b2c4e04, type: 2}
|
||||
Duration: 0
|
||||
ChargeData:
|
||||
requiredDamageRatio: 0
|
||||
requiredDamageRatio: 0.1
|
||||
telegraphAbnormality: {fileID: 0}
|
||||
staggerDuration: 0
|
||||
staggerDuration: 2
|
||||
- Type: 0
|
||||
Skill: {fileID: 11400000, guid: 03c4971dcffb2ea0eb36ac997ee2a1a0, type: 2}
|
||||
Duration: 0
|
||||
ChargeData:
|
||||
requiredDamageRatio: 0
|
||||
requiredDamageRatio: 0.1
|
||||
telegraphAbnormality: {fileID: 0}
|
||||
staggerDuration: 0
|
||||
staggerDuration: 2
|
||||
- Type: 0
|
||||
Skill: {fileID: 11400000, guid: fc435c80b8f1348f889910629f8eec51, type: 2}
|
||||
Duration: 0
|
||||
ChargeData:
|
||||
requiredDamageRatio: 0
|
||||
requiredDamageRatio: 0.1
|
||||
telegraphAbnormality: {fileID: 0}
|
||||
staggerDuration: 0
|
||||
staggerDuration: 2
|
||||
cooldown: 3.25
|
||||
minPhase: 1
|
||||
skipJumpStepOnNoTarget: 0
|
||||
|
||||
@@ -22,16 +22,16 @@ MonoBehaviour:
|
||||
Skill: {fileID: 11400000, guid: a3d01db588247bc93861ea39572489f5, type: 2}
|
||||
Duration: 0
|
||||
ChargeData:
|
||||
requiredDamageRatio: 0
|
||||
requiredDamageRatio: 0.1
|
||||
telegraphAbnormality: {fileID: 0}
|
||||
staggerDuration: 0
|
||||
staggerDuration: 2
|
||||
- Type: 0
|
||||
Skill: {fileID: 11400000, guid: 4d2a845524d535769b4e2583c6321ffe, type: 2}
|
||||
Duration: 0
|
||||
ChargeData:
|
||||
requiredDamageRatio: 0
|
||||
requiredDamageRatio: 0.1
|
||||
telegraphAbnormality: {fileID: 0}
|
||||
staggerDuration: 0
|
||||
staggerDuration: 2
|
||||
- Type: 1
|
||||
Skill: {fileID: 0}
|
||||
Duration: 0.1
|
||||
|
||||
@@ -20,7 +20,6 @@ MonoBehaviour:
|
||||
baseTypes: 1
|
||||
animationClips:
|
||||
- {fileID: 7400000, guid: 567a0c8cbb10eafa08807226645826e2, type: 2}
|
||||
- {fileID: 7400000, guid: 94da51b9da4bad4129ba5e33e671db62, type: 2}
|
||||
animationSpeed: 1
|
||||
useRootMotion: 1
|
||||
ignoreRootMotionY: 1
|
||||
|
||||
@@ -12,15 +12,15 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 94f0a76cebcac2f4fb5daf1b675fd79f, type: 3}
|
||||
m_Name: "Data_Skill_Drog_\uCF64\uBCF4-\uAE30\uBCF8\uAE301_3"
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Skills.SkillData
|
||||
skillName: "\uCF64\uBCF4-\uAE30\uBCF8\uAE301 2\uD0C0"
|
||||
skillName: "\uCF64\uBCF4-\uAE30\uBCF8\uAE301 3"
|
||||
description: "\uAE30\uBCF8\uAE30 \uCF64\uBCF41\uC758 \uD6C4\uC18D \uD0C0\uACA9\uC785\uB2C8\uB2E4."
|
||||
icon: {fileID: 0}
|
||||
skillRole: 1
|
||||
activationType: 1
|
||||
baseTypes: 1
|
||||
animationClips:
|
||||
- {fileID: 7400000, guid: 567a0c8cbb10eafa08807226645826e2, type: 2}
|
||||
- {fileID: 7400000, guid: 94da51b9da4bad4129ba5e33e671db62, type: 2}
|
||||
- {fileID: 7400000, guid: b948f6e859be42cf9ad570e16fd418f1, type: 2}
|
||||
- {fileID: 7400000, guid: 2ca3044b7e7a3ff4eb8d3d6bf75f5e92, type: 2}
|
||||
animationSpeed: 1
|
||||
useRootMotion: 1
|
||||
ignoreRootMotionY: 1
|
||||
|
||||
@@ -19,9 +19,9 @@ MonoBehaviour:
|
||||
activationType: 1
|
||||
baseTypes: 1
|
||||
animationClips:
|
||||
- {fileID: 7400000, guid: 8cfd11750543a4484bae82462b7c0351, type: 2}
|
||||
- {fileID: 7400000, guid: b8206d6d914a86cd9a169763c82f273e, type: 2}
|
||||
- {fileID: 7400000, guid: 16c44854334d767d3af2fd774b89a809, type: 2}
|
||||
- {fileID: 7400000, guid: 6b11f2b53d826bba3ab9f8086a42de14, type: 2}
|
||||
- {fileID: 7400000, guid: 156c90879bba0d7649e8b29224daa390, type: 2}
|
||||
- {fileID: 7400000, guid: 6439cde8bc726bd1caad7d6e18a31416, type: 2}
|
||||
animationSpeed: 1
|
||||
useRootMotion: 1
|
||||
ignoreRootMotionY: 1
|
||||
|
||||
@@ -19,7 +19,7 @@ MonoBehaviour:
|
||||
activationType: 1
|
||||
baseTypes: 1
|
||||
animationClips:
|
||||
- {fileID: 7400000, guid: 82cf3119cd7b56e3e9d579cac94fc09d, type: 2}
|
||||
- {fileID: 7400000, guid: 606aec780e456217687074cbbf23a2c8, type: 2}
|
||||
animationSpeed: 1
|
||||
useRootMotion: 1
|
||||
ignoreRootMotionY: 1
|
||||
|
||||
@@ -19,7 +19,7 @@ MonoBehaviour:
|
||||
activationType: 1
|
||||
baseTypes: 1
|
||||
animationClips:
|
||||
- {fileID: 7400000, guid: 8f572193078ff9e229c1d39038620857, type: 2}
|
||||
- {fileID: 7400000, guid: 16c44854334d767d3af2fd774b89a809, type: 2}
|
||||
animationSpeed: 1
|
||||
useRootMotion: 1
|
||||
ignoreRootMotionY: 1
|
||||
|
||||
@@ -43,6 +43,12 @@ public abstract partial class BossPatternActionBase : Action
|
||||
private ChargeStepData activeChargeData;
|
||||
private bool chargeTelegraphApplied;
|
||||
|
||||
/// <summary>
|
||||
/// 현재 액션 인스턴스가 진행 중인 패턴 실행 상태를 이미 보유하고 있는지 여부입니다.
|
||||
/// BT 재평가 중 재진입할 때 기존 실행을 이어가기 위한 가드로 사용합니다.
|
||||
/// </summary>
|
||||
protected bool HasActivePatternExecutionState => activePattern != null;
|
||||
|
||||
/// <summary>
|
||||
/// 액션 시작 시 실제로 실행할 패턴과 대상을 결정합니다.
|
||||
/// </summary>
|
||||
@@ -56,6 +62,10 @@ public abstract partial class BossPatternActionBase : Action
|
||||
protected override Status OnStart()
|
||||
{
|
||||
ResolveReferences();
|
||||
|
||||
if (ShouldPreserveExecutionState())
|
||||
return Status.Running;
|
||||
|
||||
ClearRuntimeState();
|
||||
|
||||
if (!IsReady())
|
||||
@@ -153,6 +163,9 @@ public abstract partial class BossPatternActionBase : Action
|
||||
|
||||
protected override void OnEnd()
|
||||
{
|
||||
if (ShouldPreserveExecutionState())
|
||||
return;
|
||||
|
||||
ClearRuntimeState();
|
||||
}
|
||||
|
||||
@@ -389,6 +402,20 @@ public abstract partial class BossPatternActionBase : Action
|
||||
waitEndTime = 0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// BT 관찰자 재평가로 노드가 다시 시작될 때 현재 패턴 실행 상태를 유지해야 하는지 판단합니다.
|
||||
/// </summary>
|
||||
private bool ShouldPreserveExecutionState()
|
||||
{
|
||||
if (!IsReady() || activePattern == null || runtimeState == null || !runtimeState.IsExecutingPattern)
|
||||
return false;
|
||||
|
||||
if (runtimeState.IsBehaviorSuppressed || bossEnemy.IsDead)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsFirstSkillStep(int stepIndex)
|
||||
{
|
||||
if (activePattern == null || activePattern.Steps == null)
|
||||
|
||||
@@ -34,6 +34,9 @@ namespace Colosseum.AI.BehaviorActions.Actions
|
||||
|
||||
protected override Status OnStart()
|
||||
{
|
||||
if (HasActivePatternExecutionState)
|
||||
return base.OnStart();
|
||||
|
||||
if (!TrySelectPattern(out selectedPattern))
|
||||
return Status.Failure;
|
||||
|
||||
@@ -44,7 +47,9 @@ namespace Colosseum.AI.BehaviorActions.Actions
|
||||
|
||||
protected override void OnEnd()
|
||||
{
|
||||
if (!HasActivePatternExecutionState)
|
||||
selectedPattern = null;
|
||||
|
||||
base.OnEnd();
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,8 @@ namespace Colosseum.Enemy
|
||||
[Header("Pattern Flow")]
|
||||
[Tooltip("패턴 하나가 끝난 뒤 다음 패턴을 시작하기까지의 공통 텀")]
|
||||
[Min(0f)] [SerializeField] protected float commonPatternInterval = 0.35f;
|
||||
[Tooltip("패턴 종료 후 Idle 자세가 잠깐 안착할 수 있도록 추가로 확보하는 시간")]
|
||||
[Min(0f)] [SerializeField] protected float postPatternIdleSettleDuration = 0.12f;
|
||||
|
||||
[Header("Phase State")]
|
||||
[Tooltip("BT가 관리하는 최대 페이즈 수")]
|
||||
@@ -62,6 +64,7 @@ namespace Colosseum.Enemy
|
||||
protected int currentPatternPhase = 1;
|
||||
protected float currentPhaseStartTime;
|
||||
protected float nextPatternReadyTime;
|
||||
protected float lastPatternCompletedTime = float.NegativeInfinity;
|
||||
protected BossPatternExecutionResult lastPatternExecutionResult;
|
||||
protected BossPatternData lastExecutedPattern;
|
||||
protected BossPatternData activePattern;
|
||||
@@ -100,6 +103,7 @@ namespace Colosseum.Enemy
|
||||
/// 패턴 종료 후 다음 패턴 시작까지 남은 공통 텀입니다.
|
||||
/// </summary>
|
||||
public float RemainingPatternInterval => Mathf.Max(0f, nextPatternReadyTime - Time.time);
|
||||
public float RemainingPatternIdleSettleTime => Mathf.Max(0f, (lastPatternCompletedTime + Mathf.Max(0f, postPatternIdleSettleDuration)) - Time.time);
|
||||
|
||||
/// <summary>
|
||||
/// 마지막 패턴 실행 결과
|
||||
@@ -239,6 +243,7 @@ namespace Colosseum.Enemy
|
||||
activePattern = null;
|
||||
currentPatternSkillStartsFromIdle = false;
|
||||
currentPatternSkillReturnsToIdle = false;
|
||||
lastPatternCompletedTime = Time.time;
|
||||
|
||||
if (pattern != null && IsTerminalPatternExecutionResult(result))
|
||||
StartCommonPatternInterval();
|
||||
@@ -338,6 +343,9 @@ namespace Colosseum.Enemy
|
||||
if (pattern == null || pattern.Steps == null || pattern.Steps.Count == 0)
|
||||
return false;
|
||||
|
||||
if (Time.time < lastPatternCompletedTime + Mathf.Max(0f, postPatternIdleSettleDuration))
|
||||
return false;
|
||||
|
||||
if (!IsCommonPatternIntervalReady())
|
||||
return false;
|
||||
|
||||
@@ -407,6 +415,7 @@ namespace Colosseum.Enemy
|
||||
currentPatternPhase = 1;
|
||||
currentPhaseStartTime = Time.time;
|
||||
nextPatternReadyTime = 0f;
|
||||
lastPatternCompletedTime = float.NegativeInfinity;
|
||||
lastPatternExecutionResult = BossPatternExecutionResult.None;
|
||||
lastExecutedPattern = null;
|
||||
lastReviveCaster = null;
|
||||
|
||||
@@ -127,6 +127,10 @@ namespace Colosseum.Skills
|
||||
private Dictionary<SkillData, float> cooldownTracker = new Dictionary<SkillData, float>();
|
||||
private AnimatorOverrideController runtimeOverrideController;
|
||||
private int cachedRecoveryStateHash;
|
||||
private float currentClipStartTime = -1f;
|
||||
private float currentClipExpectedDuration = 0f;
|
||||
private string currentClipDebugName = string.Empty;
|
||||
private int currentClipStartFrame = -1;
|
||||
|
||||
|
||||
public bool IsExecutingSkill => currentSkill != null;
|
||||
@@ -292,7 +296,13 @@ namespace Colosseum.Skills
|
||||
// 애니메이션 종료 시 처리
|
||||
if (stateInfo.normalizedTime >= 1f)
|
||||
{
|
||||
if (Time.frameCount <= currentClipStartFrame)
|
||||
return;
|
||||
|
||||
// 같은 반복 차수 내에서 다음 클립이 있으면 재생
|
||||
if (HasNextClipInSequence())
|
||||
LogCurrentClipTiming("Advance");
|
||||
|
||||
if (TryPlayNextClipInSequence())
|
||||
return;
|
||||
|
||||
@@ -308,6 +318,7 @@ namespace Colosseum.Skills
|
||||
}
|
||||
|
||||
// 모든 클립과 단계가 끝나면 종료
|
||||
LogCurrentClipTiming("Complete");
|
||||
if (debugMode) Debug.Log($"[Skill] Animation complete: {currentSkill.SkillName}");
|
||||
RestoreBaseControllerIfNeeded();
|
||||
CompleteCurrentSkillExecution(SkillExecutionResult.Completed);
|
||||
@@ -566,7 +577,8 @@ namespace Colosseum.Skills
|
||||
? currentLoadoutEntry.GetResolvedAnimationSpeed()
|
||||
: currentSkill.AnimationSpeed;
|
||||
animator.speed = resolvedAnimationSpeed;
|
||||
PlaySkillClip(currentPhaseAnimationClips[0], ShouldBlendIntoClip());
|
||||
float enterTransitionDuration = ResolveSkillEnterTransitionDuration();
|
||||
PlaySkillClip(currentPhaseAnimationClips[0], enterTransitionDuration > 0f, enterTransitionDuration);
|
||||
}
|
||||
|
||||
TriggerImmediateSelfEffectsIfNeeded();
|
||||
@@ -588,7 +600,7 @@ namespace Colosseum.Skills
|
||||
return false;
|
||||
|
||||
currentClipSequenceIndex = nextIndex;
|
||||
PlaySkillClip(currentPhaseAnimationClips[currentClipSequenceIndex], blendIn: false);
|
||||
PlaySkillClip(currentPhaseAnimationClips[currentClipSequenceIndex], blendIn: false, transitionDuration: 0f);
|
||||
|
||||
if (debugMode)
|
||||
{
|
||||
@@ -598,6 +610,17 @@ namespace Colosseum.Skills
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 시퀀스에 다음 클립이 남아 있는지 반환합니다.
|
||||
/// </summary>
|
||||
private bool HasNextClipInSequence()
|
||||
{
|
||||
if (currentSkill == null || currentPhaseAnimationClips == null)
|
||||
return false;
|
||||
|
||||
return currentClipSequenceIndex + 1 < currentPhaseAnimationClips.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 반복 시전이 남아 있으면 다음 차수를 시작합니다.
|
||||
/// </summary>
|
||||
@@ -616,7 +639,7 @@ namespace Colosseum.Skills
|
||||
/// <summary>
|
||||
/// 스킬 클립으로 Override Controller 생성 후 재생
|
||||
/// </summary>
|
||||
private void PlaySkillClip(AnimationClip clip, bool blendIn)
|
||||
private void PlaySkillClip(AnimationClip clip, bool blendIn, float transitionDuration)
|
||||
{
|
||||
if (baseSkillClip == null)
|
||||
{
|
||||
@@ -630,19 +653,21 @@ namespace Colosseum.Skills
|
||||
return;
|
||||
}
|
||||
|
||||
RecordCurrentClipTiming(clip);
|
||||
|
||||
if (debugMode)
|
||||
{
|
||||
Debug.Log($"[Skill] PlaySkillClip: {clip.name}, BaseClip: {baseSkillClip.name}");
|
||||
Debug.Log($"[Skill] PlaySkillClip: {clip.name}, BaseClip: {baseSkillClip.name}, Blend={blendIn}, Transition={transitionDuration:F2}");
|
||||
}
|
||||
|
||||
if (blendIn)
|
||||
animator.CrossFadeInFixedTime(GetSkillStateHash(), GetSkillEnterTransitionDuration(), 0, 0f);
|
||||
animator.CrossFadeInFixedTime(GetSkillStateHash(), transitionDuration, 0, 0f);
|
||||
else
|
||||
animator.Play(GetSkillStateHash(), 0, 0f);
|
||||
|
||||
// 클라이언트에 클립 동기화
|
||||
if (IsServer && IsSpawned)
|
||||
PlaySkillClipClientRpc(registeredClips.IndexOf(clip), blendIn);
|
||||
PlaySkillClipClientRpc(registeredClips.IndexOf(clip), blendIn, transitionDuration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -673,7 +698,7 @@ namespace Colosseum.Skills
|
||||
/// 클라이언트: override 컨트롤러 적용 + 스킬 상태 재생 (원자적 실행으로 타이밍 문제 해결)
|
||||
/// </summary>
|
||||
[Rpc(SendTo.NotServer)]
|
||||
private void PlaySkillClipClientRpc(int clipIndex, bool blendIn)
|
||||
private void PlaySkillClipClientRpc(int clipIndex, bool blendIn, float transitionDuration)
|
||||
{
|
||||
if (baseSkillClip == null || animator == null || baseController == null) return;
|
||||
if (clipIndex < 0 || clipIndex >= registeredClips.Count || registeredClips[clipIndex] == null)
|
||||
@@ -685,7 +710,7 @@ namespace Colosseum.Skills
|
||||
if (!ApplyOverrideClip(registeredClips[clipIndex]))
|
||||
return;
|
||||
if (blendIn)
|
||||
animator.CrossFadeInFixedTime(GetSkillStateHash(), GetSkillEnterTransitionDuration(), 0, 0f);
|
||||
animator.CrossFadeInFixedTime(GetSkillStateHash(), transitionDuration, 0, 0f);
|
||||
else
|
||||
animator.Play(GetSkillStateHash(), 0, 0f);
|
||||
}
|
||||
@@ -825,6 +850,7 @@ namespace Colosseum.Skills
|
||||
lastCancelledSkillName = currentSkill.SkillName;
|
||||
lastCancelReason = reason;
|
||||
|
||||
LogCurrentClipTiming("Cancelled");
|
||||
Debug.Log($"[Skill] Cancelled: {currentSkill.SkillName} / reason={reason}");
|
||||
|
||||
RestoreBaseController();
|
||||
@@ -1171,7 +1197,7 @@ namespace Colosseum.Skills
|
||||
? currentLoadoutEntry.GetResolvedAnimationSpeed()
|
||||
: currentSkill.AnimationSpeed;
|
||||
animator.speed = resolvedAnimationSpeed;
|
||||
PlaySkillClip(currentPhaseAnimationClips[0], blendIn: false);
|
||||
PlaySkillClip(currentPhaseAnimationClips[0], blendIn: false, transitionDuration: 0f);
|
||||
|
||||
if (debugMode)
|
||||
Debug.Log($"[Skill] 해제 단계 시작: {currentSkill.SkillName}");
|
||||
@@ -1265,6 +1291,10 @@ namespace Colosseum.Skills
|
||||
currentIterationIndex = 0;
|
||||
loopHoldRequested = false;
|
||||
cachedRecoveryStateHash = 0;
|
||||
currentClipStartTime = -1f;
|
||||
currentClipExpectedDuration = 0f;
|
||||
currentClipDebugName = string.Empty;
|
||||
currentClipStartFrame = -1;
|
||||
shouldBlendIntoCurrentSkill = true;
|
||||
shouldRestoreToIdleAfterCurrentSkill = true;
|
||||
}
|
||||
@@ -1288,6 +1318,38 @@ namespace Colosseum.Skills
|
||||
sustainController?.HandleSkillExecutionEnded();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 클립의 예상 재생 시간을 기록합니다.
|
||||
/// </summary>
|
||||
private void RecordCurrentClipTiming(AnimationClip clip)
|
||||
{
|
||||
if (clip == null)
|
||||
return;
|
||||
|
||||
currentClipStartTime = Time.time;
|
||||
currentClipExpectedDuration = clip.length / Mathf.Max(0.0001f, animator != null ? animator.speed : 1f);
|
||||
currentClipDebugName = clip.name;
|
||||
currentClipStartFrame = Time.frameCount;
|
||||
|
||||
if (!debugMode)
|
||||
return;
|
||||
|
||||
Debug.Log($"[SkillTiming] Start: skill={currentSkill?.SkillName ?? "<null>"}, clip={currentClipDebugName}, t={currentClipStartTime:F3}, expected={currentClipExpectedDuration:F3}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 클립의 실제 경과 시간을 로그로 남깁니다.
|
||||
/// </summary>
|
||||
private void LogCurrentClipTiming(string phase)
|
||||
{
|
||||
if (!debugMode || string.IsNullOrEmpty(currentClipDebugName) || currentClipStartTime < 0f)
|
||||
return;
|
||||
|
||||
float elapsed = Time.time - currentClipStartTime;
|
||||
float delta = elapsed - currentClipExpectedDuration;
|
||||
Debug.Log($"[SkillTiming] {phase}: skill={currentSkill?.SkillName ?? "<null>"}, clip={currentClipDebugName}, t={Time.time:F3}, elapsed={elapsed:F3}, expected={currentClipExpectedDuration:F3}, delta={delta:F3}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 적 스킬이 시전 중일 때 대상 추적 정책을 적용합니다.
|
||||
/// </summary>
|
||||
@@ -1460,29 +1522,46 @@ namespace Colosseum.Skills
|
||||
/// <summary>
|
||||
/// 현재 재생할 클립이 스킬 시작 블렌드 대상인지 반환합니다.
|
||||
/// </summary>
|
||||
private bool ShouldBlendIntoClip()
|
||||
private float ResolveSkillEnterTransitionDuration()
|
||||
{
|
||||
if (isBossPatternBoundarySkill)
|
||||
return false;
|
||||
|
||||
if (!shouldBlendIntoCurrentSkill)
|
||||
return false;
|
||||
return 0f;
|
||||
|
||||
if (isPlayingReleasePhase)
|
||||
return false;
|
||||
return 0f;
|
||||
|
||||
return currentClipSequenceIndex == 0 && currentIterationIndex == 1;
|
||||
if (currentClipSequenceIndex != 0 || currentIterationIndex != 1)
|
||||
return 0f;
|
||||
|
||||
if (!isBossPatternBoundarySkill)
|
||||
return skillEnterTransitionDuration;
|
||||
|
||||
bool shouldBlendBossEntry = ShouldBlendBossPatternEntryFromCurrentState();
|
||||
return shouldBlendBossEntry
|
||||
? Mathf.Max(skillEnterTransitionDuration, bossPatternEnterTransitionDuration)
|
||||
: 0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 스킬 진입에 사용할 전환 시간을 반환합니다.
|
||||
/// 보스 패턴 첫 스킬이 이동 중 진입인지 판단합니다.
|
||||
/// </summary>
|
||||
private float GetSkillEnterTransitionDuration()
|
||||
private bool ShouldBlendBossPatternEntryFromCurrentState()
|
||||
{
|
||||
if (isBossPatternBoundarySkill)
|
||||
return bossPatternEnterTransitionDuration;
|
||||
if (animator == null)
|
||||
return false;
|
||||
|
||||
return skillEnterTransitionDuration;
|
||||
if (animator.IsInTransition(0))
|
||||
return true;
|
||||
|
||||
AnimatorStateInfo currentState = animator.GetCurrentAnimatorStateInfo(0);
|
||||
int moveStateHash = Animator.StringToHash($"{BaseLayerName}.{MoveStateName}");
|
||||
if (currentState.fullPathHash == moveStateHash)
|
||||
return true;
|
||||
|
||||
UnityEngine.AI.NavMeshAgent agent = GetComponent<UnityEngine.AI.NavMeshAgent>();
|
||||
return agent != null
|
||||
&& agent.enabled
|
||||
&& agent.velocity.sqrMagnitude > 0.0025f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user