From 287ff4dc8399f109081fb8f5a310ad776c88e9bb Mon Sep 17 00:00:00 2001 From: dal4segno Date: Thu, 19 Mar 2026 19:16:32 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=AC=B4=EC=A0=81=20=EC=9D=B4=EC=83=81?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EA=B8=B0=EB=B0=98=20=EA=B5=AC=EB=A5=B4?= =?UTF-8?q?=EA=B8=B0=20=EC=8A=A4=ED=82=AC=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 무적 이상상태 데이터와 효과 에셋을 추가 - 구르기를 일반 스킬이 무적 상태를 부여하는 구조로 변경 - 대미지 처리와 플레이어 상태 판정이 무적 이상상태를 참조하도록 정리 --- .../Data_Abnormality_Player_Invincible.asset | 24 +++++++++++ ...a_Abnormality_Player_Invincible.asset.meta | 8 ++++ .../Skills/Data_Skill_Player_구르기.asset | 5 +-- .../Data_SkillEffect_Player_무적.asset | 26 ++++++++++++ .../Data_SkillEffect_Player_무적.asset.meta | 8 ++++ .../Player/Prefab_Player_Default.prefab | 1 + .../Scripts/Abnormalities/AbnormalityData.cs | 3 +- .../Abnormalities/AbnormalityManager.cs | 28 +++++++++++++ .../Player/PlayerAbnormalityDebugHUD.cs | 2 +- .../PlayerAbnormalityVerificationRunner.cs | 42 +++++++++---------- .../_Game/Scripts/Player/PlayerActionState.cs | 22 +++------- .../Scripts/Player/PlayerNetworkController.cs | 19 +++++++-- .../_Game/Scripts/Player/PlayerSkillInput.cs | 2 +- .../_Game/Scripts/Skills/SkillController.cs | 25 +++++++++++ Assets/_Game/Scripts/Skills/SkillData.cs | 6 --- 15 files changed, 166 insertions(+), 55 deletions(-) create mode 100644 Assets/_Game/Data/Abnormalities/Data_Abnormality_Player_Invincible.asset create mode 100644 Assets/_Game/Data/Abnormalities/Data_Abnormality_Player_Invincible.asset.meta create mode 100644 Assets/_Game/Data/Skills/Effects/Data_SkillEffect_Player_무적.asset create mode 100644 Assets/_Game/Data/Skills/Effects/Data_SkillEffect_Player_무적.asset.meta diff --git a/Assets/_Game/Data/Abnormalities/Data_Abnormality_Player_Invincible.asset b/Assets/_Game/Data/Abnormalities/Data_Abnormality_Player_Invincible.asset new file mode 100644 index 00000000..5dc1f2c8 --- /dev/null +++ b/Assets/_Game/Data/Abnormalities/Data_Abnormality_Player_Invincible.asset @@ -0,0 +1,24 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: b08cc671f858a3b409170a5356e960a0, type: 3} + m_Name: Data_Abnormality_Player_Invincible + m_EditorClassIdentifier: Colosseum.Game::Colosseum.Abnormalities.AbnormalityData + abnormalityName: "\uBB34\uC801" + icon: {fileID: 0} + duration: 1 + level: 1 + isDebuff: 0 + statModifiers: [] + periodicInterval: 0 + periodicValue: 0 + controlType: 4 + slowMultiplier: 0.5 diff --git a/Assets/_Game/Data/Abnormalities/Data_Abnormality_Player_Invincible.asset.meta b/Assets/_Game/Data/Abnormalities/Data_Abnormality_Player_Invincible.asset.meta new file mode 100644 index 00000000..455973fa --- /dev/null +++ b/Assets/_Game/Data/Abnormalities/Data_Abnormality_Player_Invincible.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7a1f2b3c4d5e6f708192a3b4c5d6e7f8 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Game/Data/Skills/Data_Skill_Player_구르기.asset b/Assets/_Game/Data/Skills/Data_Skill_Player_구르기.asset index 88434715..3b05143a 100644 --- a/Assets/_Game/Data/Skills/Data_Skill_Player_구르기.asset +++ b/Assets/_Game/Data/Skills/Data_Skill_Player_구르기.asset @@ -19,11 +19,10 @@ MonoBehaviour: endClip: {fileID: 0} useRootMotion: 1 ignoreRootMotionY: 1 - isEvadeSkill: 1 blockMovementWhileCasting: 1 blockJumpWhileCasting: 1 blockOtherSkillsWhileCasting: 1 - blockEvadeWhileCasting: 1 cooldown: 10 manaCost: 0 - effects: [] + effects: + - {fileID: 11400000, guid: 8b2c3d4e5f60718293a4b5c6d7e8f901, type: 2} diff --git a/Assets/_Game/Data/Skills/Effects/Data_SkillEffect_Player_무적.asset b/Assets/_Game/Data/Skills/Effects/Data_SkillEffect_Player_무적.asset new file mode 100644 index 00000000..bfc5588a --- /dev/null +++ b/Assets/_Game/Data/Skills/Effects/Data_SkillEffect_Player_무적.asset @@ -0,0 +1,26 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bf750718c64c4bd48af905d2927351de, type: 3} + m_Name: "Data_SkillEffect_Player_\uBB34\uC801" + m_EditorClassIdentifier: Colosseum.Game::Colosseum.Skills.Effects.AbnormalityEffect + targetType: 0 + targetTeam: 0 + areaCenter: 0 + areaShape: 0 + targetLayers: + serializedVersion: 2 + m_Bits: 0 + areaRadius: 3 + fanOriginDistance: 1 + fanRadius: 3 + fanHalfAngle: 45 + abnormalityData: {fileID: 11400000, guid: 7a1f2b3c4d5e6f708192a3b4c5d6e7f8, type: 2} diff --git a/Assets/_Game/Data/Skills/Effects/Data_SkillEffect_Player_무적.asset.meta b/Assets/_Game/Data/Skills/Effects/Data_SkillEffect_Player_무적.asset.meta new file mode 100644 index 00000000..2c02d66c --- /dev/null +++ b/Assets/_Game/Data/Skills/Effects/Data_SkillEffect_Player_무적.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8b2c3d4e5f60718293a4b5c6d7e8f901 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Game/Prefabs/Player/Prefab_Player_Default.prefab b/Assets/_Game/Prefabs/Player/Prefab_Player_Default.prefab index 6d63be85..432cb02b 100644 --- a/Assets/_Game/Prefabs/Player/Prefab_Player_Default.prefab +++ b/Assets/_Game/Prefabs/Player/Prefab_Player_Default.prefab @@ -203,6 +203,7 @@ MonoBehaviour: m_EditorClassIdentifier: Colosseum.Game::Colosseum.Player.PlayerNetworkController ShowTopMostFoldoutHeaderGroup: 1 characterStats: {fileID: -5132198055668300151} + abnormalityManager: {fileID: 0} --- !u!114 &8606252901290138286 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/Assets/_Game/Scripts/Abnormalities/AbnormalityData.cs b/Assets/_Game/Scripts/Abnormalities/AbnormalityData.cs index 54504f42..28382ccd 100644 --- a/Assets/_Game/Scripts/Abnormalities/AbnormalityData.cs +++ b/Assets/_Game/Scripts/Abnormalities/AbnormalityData.cs @@ -13,7 +13,8 @@ namespace Colosseum.Abnormalities None, // 제어 효과 없음 Stun, // 기절 (이동, 스킬 사용 불가) Silence, // 침묵 (스킬 사용 불가) - Slow // 둔화 (이동 속도 감소) + Slow, // 둔화 (이동 속도 감소) + Invincible // 무적 (대미지 무시) } /// diff --git a/Assets/_Game/Scripts/Abnormalities/AbnormalityManager.cs b/Assets/_Game/Scripts/Abnormalities/AbnormalityManager.cs index 53180f10..534cbcf0 100644 --- a/Assets/_Game/Scripts/Abnormalities/AbnormalityManager.cs +++ b/Assets/_Game/Scripts/Abnormalities/AbnormalityManager.cs @@ -26,11 +26,13 @@ namespace Colosseum.Abnormalities // 제어 효과 상태 private int stunCount; private int silenceCount; + private int invincibleCount; private float slowMultiplier = 1f; // 클라이언트 판정용 제어 효과 동기화 변수 private NetworkVariable syncedStunCount = new NetworkVariable(0); private NetworkVariable syncedSilenceCount = new NetworkVariable(0); + private NetworkVariable syncedInvincibleCount = new NetworkVariable(0); private NetworkVariable syncedSlowMultiplier = new NetworkVariable(1f); // 네트워크 동기화용 데이터 @@ -46,6 +48,11 @@ namespace Colosseum.Abnormalities /// public bool IsSilenced => GetCurrentSilenceCount() > 0; + /// + /// 무적 상태 여부 + /// + public bool IsInvincible => GetCurrentInvincibleCount() > 0; + /// /// 이동 속도 배율 (1.0 = 기본, 0.5 = 50% 감소) /// @@ -110,6 +117,7 @@ namespace Colosseum.Abnormalities syncedStunCount.OnValueChanged += HandleSyncedStunChanged; syncedSilenceCount.OnValueChanged += HandleSyncedSilenceChanged; + syncedInvincibleCount.OnValueChanged += HandleSyncedInvincibleChanged; syncedSlowMultiplier.OnValueChanged += HandleSyncedSlowChanged; if (networkController != null) @@ -128,6 +136,7 @@ namespace Colosseum.Abnormalities syncedAbnormalities.OnListChanged -= OnSyncedAbnormalitiesChanged; syncedStunCount.OnValueChanged -= HandleSyncedStunChanged; syncedSilenceCount.OnValueChanged -= HandleSyncedSilenceChanged; + syncedInvincibleCount.OnValueChanged -= HandleSyncedInvincibleChanged; syncedSlowMultiplier.OnValueChanged -= HandleSyncedSlowChanged; if (networkController != null) @@ -418,6 +427,10 @@ namespace Colosseum.Abnormalities case ControlType.Slow: slowMultiplier = Mathf.Min(slowMultiplier, data.slowMultiplier); break; + + case ControlType.Invincible: + invincibleCount++; + break; } SyncControlEffects(); @@ -438,6 +451,10 @@ namespace Colosseum.Abnormalities case ControlType.Slow: RecalculateSlowMultiplier(); break; + + case ControlType.Invincible: + invincibleCount = Mathf.Max(0, invincibleCount - 1); + break; } SyncControlEffects(); @@ -460,6 +477,8 @@ namespace Colosseum.Abnormalities private int GetCurrentSilenceCount() => IsServer ? silenceCount : syncedSilenceCount.Value; + private int GetCurrentInvincibleCount() => IsServer ? invincibleCount : syncedInvincibleCount.Value; + private float GetCurrentSlowMultiplier() => IsServer ? slowMultiplier : syncedSlowMultiplier.Value; private void SyncControlEffects() @@ -469,6 +488,7 @@ namespace Colosseum.Abnormalities syncedStunCount.Value = stunCount; syncedSilenceCount.Value = silenceCount; + syncedInvincibleCount.Value = invincibleCount; syncedSlowMultiplier.Value = slowMultiplier; } @@ -541,6 +561,14 @@ namespace Colosseum.Abnormalities OnAbnormalitiesChanged?.Invoke(); } + private void HandleSyncedInvincibleChanged(int oldValue, int newValue) + { + if (oldValue == newValue) + return; + + OnAbnormalitiesChanged?.Invoke(); + } + /// /// 사망 시 활성 이상 상태를 모두 제거합니다. /// diff --git a/Assets/_Game/Scripts/Player/PlayerAbnormalityDebugHUD.cs b/Assets/_Game/Scripts/Player/PlayerAbnormalityDebugHUD.cs index 929fe005..b32c0521 100644 --- a/Assets/_Game/Scripts/Player/PlayerAbnormalityDebugHUD.cs +++ b/Assets/_Game/Scripts/Player/PlayerAbnormalityDebugHUD.cs @@ -93,7 +93,7 @@ namespace Colosseum.Player GUILayout.Label($"사망 상태: {(networkController != null && networkController.IsDead ? "Dead" : "Alive")}"); if (TryGetComponent(out var actionState)) { - GUILayout.Label($"회피 상태: {(actionState.IsEvading ? "Evading" : "Idle")} / 이동:{actionState.CanMove} / 스킬:{actionState.CanUseSkills}"); + GUILayout.Label($"무적 상태: {(actionState.IsDamageImmune ? "Immune" : "Normal")} / 이동:{actionState.CanMove} / 스킬:{actionState.CanUseSkills}"); } GUILayout.Label("입력 예시: 기절 / Data_Abnormality_Player_Stun / 0"); diff --git a/Assets/_Game/Scripts/Player/PlayerAbnormalityVerificationRunner.cs b/Assets/_Game/Scripts/Player/PlayerAbnormalityVerificationRunner.cs index fe1070a0..2d05cd77 100644 --- a/Assets/_Game/Scripts/Player/PlayerAbnormalityVerificationRunner.cs +++ b/Assets/_Game/Scripts/Player/PlayerAbnormalityVerificationRunner.cs @@ -104,9 +104,9 @@ namespace Colosseum.Player Verify("초기 상태: 사망 아님", !networkController.IsDead); Verify("초기 상태: 이동 가능", actionState.CanMove); Verify("초기 상태: 스킬 사용 가능", actionState.CanUseSkills); - Verify("초기 상태: 회피 상태 아님", !actionState.IsEvading); + Verify("초기 상태: 무적 상태 아님", !actionState.IsDamageImmune); - yield return RunEvadeVerification(); + yield return RunInvincibilitySkillVerification(); abnormalityManager.ApplyAbnormality(stunData, gameObject); yield return new WaitForSeconds(settleDelay); @@ -234,48 +234,46 @@ namespace Colosseum.Player #endif } - private IEnumerator RunEvadeVerification() + private IEnumerator RunInvincibilitySkillVerification() { - SkillData evadeSkill = skillInput != null ? skillInput.GetSkill(6) : null; - if (evadeSkill == null) + SkillData invincibilitySkill = skillInput != null ? skillInput.GetSkill(6) : null; + if (invincibilitySkill == null) { - AppendLine("[SKIP] 회피 검증: 회피 슬롯 스킬이 없습니다."); + AppendLine("[SKIP] 무적 스킬 검증: 7번 슬롯 스킬이 없습니다."); yield break; } - if (skillController == null || !skillController.ExecuteSkill(evadeSkill)) + if (skillController == null || !skillController.ExecuteSkill(invincibilitySkill)) { - Verify("회피 검증: 스킬 실행 성공", false); + Verify("무적 스킬 검증: 스킬 실행 성공", false); yield break; } + yield return WaitForConditionOrTimeout(() => actionState.IsDamageImmune, GetSkillDuration(invincibilitySkill) + 0.5f); + + float healthBeforeDamage = networkController.Health; + + Verify("무적 적용: IsDamageImmune", actionState.IsDamageImmune); + networkController.TakeDamageRpc(15f); yield return new WaitForSeconds(settleDelay); - - Verify("회피 적용: IsEvading", actionState.IsEvading); - Verify("회피 적용: 이동 불가", !actionState.CanMove); - Verify("회피 적용: 점프 불가", !actionState.CanJump); - Verify("회피 적용: 일반 스킬 사용 불가", !actionState.CanUseSkills); - Verify("회피 적용: 회피 스킬 연속 사용 불가", !actionState.CanStartSkill(evadeSkill)); + Verify("무적 적용: 대미지 무시", Mathf.Approximately(networkController.Health, healthBeforeDamage)); if (silenceData != null) { abnormalityManager.ApplyAbnormality(silenceData, gameObject); yield return WaitForConditionOrTimeout(() => actionState.IsSilenced, settleDelay + 0.5f); - Verify("회피 중 침묵 적용: IsSilenced", actionState.IsSilenced); - Verify("회피 중 침묵 적용: 회피 상태 유지", actionState.IsEvading); - Verify("회피 중 침묵 적용: 회피 스킬 신규 사용 불가", !actionState.CanStartSkill(evadeSkill)); + Verify("무적 중 침묵 적용: IsSilenced", actionState.IsSilenced); + Verify("무적 중 침묵 적용: 무적 상태 유지", actionState.IsDamageImmune); + Verify("무적 중 침묵 적용: 스킬 신규 사용 불가", !actionState.CanStartSkill(invincibilitySkill)); abnormalityManager.RemoveAbnormality(silenceData); yield return new WaitForSeconds(settleDelay); } - yield return WaitForConditionOrTimeout(() => !actionState.IsEvading, GetSkillDuration(evadeSkill) + 1.5f); + yield return WaitForConditionOrTimeout(() => !actionState.IsDamageImmune, GetSkillDuration(invincibilitySkill) + 1.5f); - Verify("회피 해제: IsEvading false", !actionState.IsEvading); - Verify("회피 해제: 이동 가능 복구", actionState.CanMove); - Verify("회피 해제: 스킬 사용 가능 복구", actionState.CanUseSkills); - Verify("회피 해제: 회피 스킬 재사용 가능 복구", actionState.CanStartSkill(evadeSkill)); + Verify("무적 해제: IsDamageImmune false", !actionState.IsDamageImmune); } private float GetSkillDuration(SkillData skill) diff --git a/Assets/_Game/Scripts/Player/PlayerActionState.cs b/Assets/_Game/Scripts/Player/PlayerActionState.cs index a147a1db..c10a4dbb 100644 --- a/Assets/_Game/Scripts/Player/PlayerActionState.cs +++ b/Assets/_Game/Scripts/Player/PlayerActionState.cs @@ -56,10 +56,9 @@ namespace Colosseum.Player public SkillData CurrentSkill => skillController != null ? skillController.CurrentSkill : null; /// - /// 현재 시전 중인 스킬이 회피 스킬일 때의 회피 상태 여부. - /// 침묵은 회피 상태 자체를 끊지 않으며, 회피 스킬의 신규 사용만 막습니다. + /// 무적 이상상태 여부 /// - public bool IsEvading => IsCastingSkill && CurrentSkill != null && CurrentSkill.IsEvadeSkill; + public bool IsDamageImmune => abnormalityManager != null && abnormalityManager.IsInvincible; /// /// 입력을 받아도 되는지 여부 @@ -79,23 +78,20 @@ namespace Colosseum.Player /// /// 일반 스킬 시작 가능 여부 /// - public bool CanUseSkills => CanReceiveInput && !IsStunned && !IsSilenced && !IsEvading && !BlocksSkillUseForCurrentSkill(); + public bool CanUseSkills => CanReceiveInput && !IsStunned && !IsSilenced && !BlocksSkillUseForCurrentSkill(); /// /// 특정 스킬의 시작 가능 여부. - /// 회피 스킬도 일반 스킬과 같은 시작 판정을 사용하되, 현재 시전 중인 스킬의 회피 차단 정책을 따릅니다. + /// 스킬 이름과 무관하게 동일한 시작 규칙을 사용합니다. /// public bool CanStartSkill(SkillData skill) { if (skill == null) return false; - if (!CanReceiveInput || IsStunned || IsSilenced || IsEvading) + if (!CanReceiveInput || IsStunned || IsSilenced) return false; - if (skill.IsEvadeSkill) - return !BlocksEvadeForCurrentSkill(); - return !BlocksSkillUseForCurrentSkill(); } @@ -148,13 +144,5 @@ namespace Colosseum.Player return CurrentSkill == null || CurrentSkill.BlockOtherSkillsWhileCasting; } - - private bool BlocksEvadeForCurrentSkill() - { - if (!IsCastingSkill) - return false; - - return CurrentSkill == null || CurrentSkill.BlockEvadeWhileCasting; - } } } diff --git a/Assets/_Game/Scripts/Player/PlayerNetworkController.cs b/Assets/_Game/Scripts/Player/PlayerNetworkController.cs index 97355845..b43072f8 100644 --- a/Assets/_Game/Scripts/Player/PlayerNetworkController.cs +++ b/Assets/_Game/Scripts/Player/PlayerNetworkController.cs @@ -18,6 +18,9 @@ namespace Colosseum.Player [Tooltip("CharacterStats 컴포넌트 (없으면 자동 검색)")] [SerializeField] private CharacterStats characterStats; + [Tooltip("이상상태 관리자 (없으면 자동 검색)")] + [SerializeField] private AbnormalityManager abnormalityManager; + // 네트워크 동기화 변수 private NetworkVariable currentHealth = new NetworkVariable(100f); private NetworkVariable currentMana = new NetworkVariable(50f); @@ -50,6 +53,11 @@ namespace Colosseum.Player characterStats = GetComponent(); } + if (abnormalityManager == null) + { + abnormalityManager = GetComponent(); + } + // 네트워크 변수 변경 콜백 등록 currentHealth.OnValueChanged += HandleHealthChanged; currentMana.OnValueChanged += HandleManaChanged; @@ -93,7 +101,7 @@ namespace Colosseum.Player [Rpc(SendTo.Server)] public void TakeDamageRpc(float damage) { - if (isDead.Value) return; + if (isDead.Value || IsDamageImmune()) return; currentHealth.Value = Mathf.Max(0f, currentHealth.Value - damage); @@ -159,7 +167,6 @@ namespace Colosseum.Player isDead.Value = true; // 사망 시 활성 이상 상태를 정리해 리스폰 시 잔존하지 않게 합니다. - var abnormalityManager = GetComponent(); if (abnormalityManager != null) { abnormalityManager.RemoveAllAbnormalities(); @@ -202,7 +209,6 @@ namespace Colosseum.Player { if (!IsServer) return; - var abnormalityManager = GetComponent(); if (abnormalityManager != null) { abnormalityManager.RemoveAllAbnormalities(); @@ -244,7 +250,7 @@ namespace Colosseum.Player /// public float TakeDamage(float damage, object source = null) { - if (!IsServer || isDead.Value) return 0f; + if (!IsServer || isDead.Value || IsDamageImmune()) return 0f; float actualDamage = Mathf.Min(damage, currentHealth.Value); currentHealth.Value = Mathf.Max(0f, currentHealth.Value - damage); @@ -269,6 +275,11 @@ namespace Colosseum.Player return actualHeal; } + + private bool IsDamageImmune() + { + return abnormalityManager != null && abnormalityManager.IsInvincible; + } #endregion } } diff --git a/Assets/_Game/Scripts/Player/PlayerSkillInput.cs b/Assets/_Game/Scripts/Player/PlayerSkillInput.cs index 14caeb33..45cb17ef 100644 --- a/Assets/_Game/Scripts/Player/PlayerSkillInput.cs +++ b/Assets/_Game/Scripts/Player/PlayerSkillInput.cs @@ -15,7 +15,7 @@ namespace Colosseum.Player public class PlayerSkillInput : NetworkBehaviour { [Header("Skill Slots")] - [Tooltip("각 슬롯에 등록할 스킬 데이터 (6개 + 긴급회피)")] + [Tooltip("각 슬롯에 등록할 스킬 데이터 (6개 + 추가 슬롯)")] [SerializeField] private SkillData[] skillSlots = new SkillData[7]; [Header("References")] diff --git a/Assets/_Game/Scripts/Skills/SkillController.cs b/Assets/_Game/Scripts/Skills/SkillController.cs index 7acfbad7..1ef622c3 100644 --- a/Assets/_Game/Scripts/Skills/SkillController.cs +++ b/Assets/_Game/Scripts/Skills/SkillController.cs @@ -148,9 +148,34 @@ namespace Colosseum.Skills PlaySkillClip(skill.SkillClip); } + TriggerImmediateSelfEffectsIfNeeded(skill); + return true; } + /// + /// 애니메이션 이벤트가 없는 자기 자신 대상 효과는 시전 즉시 발동합니다. + /// 버프/무적 같은 자기 강화 스킬이 이벤트 누락으로 동작하지 않는 상황을 막기 위한 보정입니다. + /// + private void TriggerImmediateSelfEffectsIfNeeded(SkillData skill) + { + if (skill == null || skill.Effects == null || skill.Effects.Count == 0) + return; + + if (skill.SkillClip != null && skill.SkillClip.events != null && skill.SkillClip.events.Length > 0) + return; + + for (int i = 0; i < skill.Effects.Count; i++) + { + SkillEffect effect = skill.Effects[i]; + if (effect == null || effect.TargetType != TargetType.Self) + continue; + + if (debugMode) Debug.Log($"[Skill] Immediate self effect: {effect.name} (index {i})"); + effect.ExecuteOnCast(gameObject); + } + } + /// /// 스킬 클립으로 Override Controller 생성 후 재생 /// diff --git a/Assets/_Game/Scripts/Skills/SkillData.cs b/Assets/_Game/Scripts/Skills/SkillData.cs index 5d4531a7..3f12c162 100644 --- a/Assets/_Game/Scripts/Skills/SkillData.cs +++ b/Assets/_Game/Scripts/Skills/SkillData.cs @@ -32,16 +32,12 @@ namespace Colosseum.Skills [SerializeField] private bool jumpToTarget = false; [Header("행동 제한")] - [Tooltip("이 스킬을 회피 상태로 취급할지 여부")] - [SerializeField] private bool isEvadeSkill = false; [Tooltip("시전 중 이동 입력 차단 여부")] [SerializeField] private bool blockMovementWhileCasting = true; [Tooltip("시전 중 점프 입력 차단 여부")] [SerializeField] private bool blockJumpWhileCasting = true; [Tooltip("시전 중 다른 스킬 입력 차단 여부")] [SerializeField] private bool blockOtherSkillsWhileCasting = true; - [Tooltip("시전 중 회피 입력 차단 여부")] - [SerializeField] private bool blockEvadeWhileCasting = true; [Header("쿨타임 & 비용")] [Min(0f)] [SerializeField] private float cooldown = 1f; @@ -63,11 +59,9 @@ namespace Colosseum.Skills public bool UseRootMotion => useRootMotion; public bool IgnoreRootMotionY => ignoreRootMotionY; public bool JumpToTarget => jumpToTarget; - public bool IsEvadeSkill => isEvadeSkill; public bool BlockMovementWhileCasting => blockMovementWhileCasting; public bool BlockJumpWhileCasting => blockJumpWhileCasting; public bool BlockOtherSkillsWhileCasting => blockOtherSkillsWhileCasting; - public bool BlockEvadeWhileCasting => blockEvadeWhileCasting; public IReadOnlyList Effects => effects; } }