fix: 드로그 패턴 애니메이션 재생 끊김 수정
- BT 재평가 중에도 패턴 실행 상태를 보존하도록 보스 패턴 액션과 런타임 상태를 조정했다. - 스킬 컨트롤러에서 동일 프레임 종료 판정을 막아 패턴 내 다음 스킬이 즉시 잘리는 문제를 수정했다. - 드로그 BT, 패턴/스킬 데이터, 애니메이션 클립과 컨트롤러를 현재 검증된 재생 구성으로 정리했다. - 자연 발동 기준으로 콤보-기본기2 재생 시간을 재검증해 클립 길이와 실제 재생 간격이 맞는 것을 확인했다.
This commit is contained in:
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
|
||||
|
||||
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:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4ff85a68bb491e143a001f3af82639ed
|
||||
guid: 2ca3044b7e7a3ff4eb8d3d6bf75f5e92
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 7400000
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8cfd11750543a4484bae82462b7c0351
|
||||
guid: 6b11f2b53d826bba3ab9f8086a42de14
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 7400000
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b8206d6d914a86cd9a169763c82f273e
|
||||
guid: 156c90879bba0d7649e8b29224daa390
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 7400000
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 16c44854334d767d3af2fd774b89a809
|
||||
guid: 6439cde8bc726bd1caad7d6e18a31416
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 7400000
|
||||
|
||||
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:
|
||||
118642
Assets/_Game/Animations/Anim_Drog_콤보-기본기3_2_1.anim
Normal file
118642
Assets/_Game/Animations/Anim_Drog_콤보-기본기3_2_1.anim
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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()
|
||||
{
|
||||
selectedPattern = null;
|
||||
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