feat: 무적 이상상태 기반 구르기 스킬 정리
This commit is contained in:
@@ -0,0 +1,24 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!114 &11400000
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: b08cc671f858a3b409170a5356e960a0, type: 3}
|
||||||
|
m_Name: Data_Abnormality_Player_Invincible
|
||||||
|
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Abnormalities.AbnormalityData
|
||||||
|
abnormalityName: "\uBB34\uC801"
|
||||||
|
icon: {fileID: 0}
|
||||||
|
duration: 1
|
||||||
|
level: 1
|
||||||
|
isDebuff: 0
|
||||||
|
statModifiers: []
|
||||||
|
periodicInterval: 0
|
||||||
|
periodicValue: 0
|
||||||
|
controlType: 4
|
||||||
|
slowMultiplier: 0.5
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7a1f2b3c4d5e6f708192a3b4c5d6e7f8
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 11400000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -19,11 +19,10 @@ MonoBehaviour:
|
|||||||
endClip: {fileID: 0}
|
endClip: {fileID: 0}
|
||||||
useRootMotion: 1
|
useRootMotion: 1
|
||||||
ignoreRootMotionY: 1
|
ignoreRootMotionY: 1
|
||||||
isEvadeSkill: 1
|
|
||||||
blockMovementWhileCasting: 1
|
blockMovementWhileCasting: 1
|
||||||
blockJumpWhileCasting: 1
|
blockJumpWhileCasting: 1
|
||||||
blockOtherSkillsWhileCasting: 1
|
blockOtherSkillsWhileCasting: 1
|
||||||
blockEvadeWhileCasting: 1
|
|
||||||
cooldown: 10
|
cooldown: 10
|
||||||
manaCost: 0
|
manaCost: 0
|
||||||
effects: []
|
effects:
|
||||||
|
- {fileID: 11400000, guid: 8b2c3d4e5f60718293a4b5c6d7e8f901, type: 2}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!114 &11400000
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: bf750718c64c4bd48af905d2927351de, type: 3}
|
||||||
|
m_Name: "Data_SkillEffect_Player_\uBB34\uC801"
|
||||||
|
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Skills.Effects.AbnormalityEffect
|
||||||
|
targetType: 0
|
||||||
|
targetTeam: 0
|
||||||
|
areaCenter: 0
|
||||||
|
areaShape: 0
|
||||||
|
targetLayers:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 0
|
||||||
|
areaRadius: 3
|
||||||
|
fanOriginDistance: 1
|
||||||
|
fanRadius: 3
|
||||||
|
fanHalfAngle: 45
|
||||||
|
abnormalityData: {fileID: 11400000, guid: 7a1f2b3c4d5e6f708192a3b4c5d6e7f8, type: 2}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8b2c3d4e5f60718293a4b5c6d7e8f901
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 11400000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -203,6 +203,7 @@ MonoBehaviour:
|
|||||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Player.PlayerNetworkController
|
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Player.PlayerNetworkController
|
||||||
ShowTopMostFoldoutHeaderGroup: 1
|
ShowTopMostFoldoutHeaderGroup: 1
|
||||||
characterStats: {fileID: -5132198055668300151}
|
characterStats: {fileID: -5132198055668300151}
|
||||||
|
abnormalityManager: {fileID: 0}
|
||||||
--- !u!114 &8606252901290138286
|
--- !u!114 &8606252901290138286
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ namespace Colosseum.Abnormalities
|
|||||||
None, // 제어 효과 없음
|
None, // 제어 효과 없음
|
||||||
Stun, // 기절 (이동, 스킬 사용 불가)
|
Stun, // 기절 (이동, 스킬 사용 불가)
|
||||||
Silence, // 침묵 (스킬 사용 불가)
|
Silence, // 침묵 (스킬 사용 불가)
|
||||||
Slow // 둔화 (이동 속도 감소)
|
Slow, // 둔화 (이동 속도 감소)
|
||||||
|
Invincible // 무적 (대미지 무시)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -26,11 +26,13 @@ namespace Colosseum.Abnormalities
|
|||||||
// 제어 효과 상태
|
// 제어 효과 상태
|
||||||
private int stunCount;
|
private int stunCount;
|
||||||
private int silenceCount;
|
private int silenceCount;
|
||||||
|
private int invincibleCount;
|
||||||
private float slowMultiplier = 1f;
|
private float slowMultiplier = 1f;
|
||||||
|
|
||||||
// 클라이언트 판정용 제어 효과 동기화 변수
|
// 클라이언트 판정용 제어 효과 동기화 변수
|
||||||
private NetworkVariable<int> syncedStunCount = new NetworkVariable<int>(0);
|
private NetworkVariable<int> syncedStunCount = new NetworkVariable<int>(0);
|
||||||
private NetworkVariable<int> syncedSilenceCount = new NetworkVariable<int>(0);
|
private NetworkVariable<int> syncedSilenceCount = new NetworkVariable<int>(0);
|
||||||
|
private NetworkVariable<int> syncedInvincibleCount = new NetworkVariable<int>(0);
|
||||||
private NetworkVariable<float> syncedSlowMultiplier = new NetworkVariable<float>(1f);
|
private NetworkVariable<float> syncedSlowMultiplier = new NetworkVariable<float>(1f);
|
||||||
|
|
||||||
// 네트워크 동기화용 데이터
|
// 네트워크 동기화용 데이터
|
||||||
@@ -46,6 +48,11 @@ namespace Colosseum.Abnormalities
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsSilenced => GetCurrentSilenceCount() > 0;
|
public bool IsSilenced => GetCurrentSilenceCount() > 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 무적 상태 여부
|
||||||
|
/// </summary>
|
||||||
|
public bool IsInvincible => GetCurrentInvincibleCount() > 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 이동 속도 배율 (1.0 = 기본, 0.5 = 50% 감소)
|
/// 이동 속도 배율 (1.0 = 기본, 0.5 = 50% 감소)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -110,6 +117,7 @@ namespace Colosseum.Abnormalities
|
|||||||
|
|
||||||
syncedStunCount.OnValueChanged += HandleSyncedStunChanged;
|
syncedStunCount.OnValueChanged += HandleSyncedStunChanged;
|
||||||
syncedSilenceCount.OnValueChanged += HandleSyncedSilenceChanged;
|
syncedSilenceCount.OnValueChanged += HandleSyncedSilenceChanged;
|
||||||
|
syncedInvincibleCount.OnValueChanged += HandleSyncedInvincibleChanged;
|
||||||
syncedSlowMultiplier.OnValueChanged += HandleSyncedSlowChanged;
|
syncedSlowMultiplier.OnValueChanged += HandleSyncedSlowChanged;
|
||||||
|
|
||||||
if (networkController != null)
|
if (networkController != null)
|
||||||
@@ -128,6 +136,7 @@ namespace Colosseum.Abnormalities
|
|||||||
syncedAbnormalities.OnListChanged -= OnSyncedAbnormalitiesChanged;
|
syncedAbnormalities.OnListChanged -= OnSyncedAbnormalitiesChanged;
|
||||||
syncedStunCount.OnValueChanged -= HandleSyncedStunChanged;
|
syncedStunCount.OnValueChanged -= HandleSyncedStunChanged;
|
||||||
syncedSilenceCount.OnValueChanged -= HandleSyncedSilenceChanged;
|
syncedSilenceCount.OnValueChanged -= HandleSyncedSilenceChanged;
|
||||||
|
syncedInvincibleCount.OnValueChanged -= HandleSyncedInvincibleChanged;
|
||||||
syncedSlowMultiplier.OnValueChanged -= HandleSyncedSlowChanged;
|
syncedSlowMultiplier.OnValueChanged -= HandleSyncedSlowChanged;
|
||||||
|
|
||||||
if (networkController != null)
|
if (networkController != null)
|
||||||
@@ -418,6 +427,10 @@ namespace Colosseum.Abnormalities
|
|||||||
case ControlType.Slow:
|
case ControlType.Slow:
|
||||||
slowMultiplier = Mathf.Min(slowMultiplier, data.slowMultiplier);
|
slowMultiplier = Mathf.Min(slowMultiplier, data.slowMultiplier);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ControlType.Invincible:
|
||||||
|
invincibleCount++;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
SyncControlEffects();
|
SyncControlEffects();
|
||||||
@@ -438,6 +451,10 @@ namespace Colosseum.Abnormalities
|
|||||||
case ControlType.Slow:
|
case ControlType.Slow:
|
||||||
RecalculateSlowMultiplier();
|
RecalculateSlowMultiplier();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ControlType.Invincible:
|
||||||
|
invincibleCount = Mathf.Max(0, invincibleCount - 1);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
SyncControlEffects();
|
SyncControlEffects();
|
||||||
@@ -460,6 +477,8 @@ namespace Colosseum.Abnormalities
|
|||||||
|
|
||||||
private int GetCurrentSilenceCount() => IsServer ? silenceCount : syncedSilenceCount.Value;
|
private int GetCurrentSilenceCount() => IsServer ? silenceCount : syncedSilenceCount.Value;
|
||||||
|
|
||||||
|
private int GetCurrentInvincibleCount() => IsServer ? invincibleCount : syncedInvincibleCount.Value;
|
||||||
|
|
||||||
private float GetCurrentSlowMultiplier() => IsServer ? slowMultiplier : syncedSlowMultiplier.Value;
|
private float GetCurrentSlowMultiplier() => IsServer ? slowMultiplier : syncedSlowMultiplier.Value;
|
||||||
|
|
||||||
private void SyncControlEffects()
|
private void SyncControlEffects()
|
||||||
@@ -469,6 +488,7 @@ namespace Colosseum.Abnormalities
|
|||||||
|
|
||||||
syncedStunCount.Value = stunCount;
|
syncedStunCount.Value = stunCount;
|
||||||
syncedSilenceCount.Value = silenceCount;
|
syncedSilenceCount.Value = silenceCount;
|
||||||
|
syncedInvincibleCount.Value = invincibleCount;
|
||||||
syncedSlowMultiplier.Value = slowMultiplier;
|
syncedSlowMultiplier.Value = slowMultiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,6 +561,14 @@ namespace Colosseum.Abnormalities
|
|||||||
OnAbnormalitiesChanged?.Invoke();
|
OnAbnormalitiesChanged?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HandleSyncedInvincibleChanged(int oldValue, int newValue)
|
||||||
|
{
|
||||||
|
if (oldValue == newValue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
OnAbnormalitiesChanged?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 사망 시 활성 이상 상태를 모두 제거합니다.
|
/// 사망 시 활성 이상 상태를 모두 제거합니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ namespace Colosseum.Player
|
|||||||
GUILayout.Label($"사망 상태: {(networkController != null && networkController.IsDead ? "Dead" : "Alive")}");
|
GUILayout.Label($"사망 상태: {(networkController != null && networkController.IsDead ? "Dead" : "Alive")}");
|
||||||
if (TryGetComponent<PlayerActionState>(out var actionState))
|
if (TryGetComponent<PlayerActionState>(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");
|
GUILayout.Label("입력 예시: 기절 / Data_Abnormality_Player_Stun / 0");
|
||||||
|
|
||||||
|
|||||||
@@ -104,9 +104,9 @@ namespace Colosseum.Player
|
|||||||
Verify("초기 상태: 사망 아님", !networkController.IsDead);
|
Verify("초기 상태: 사망 아님", !networkController.IsDead);
|
||||||
Verify("초기 상태: 이동 가능", actionState.CanMove);
|
Verify("초기 상태: 이동 가능", actionState.CanMove);
|
||||||
Verify("초기 상태: 스킬 사용 가능", actionState.CanUseSkills);
|
Verify("초기 상태: 스킬 사용 가능", actionState.CanUseSkills);
|
||||||
Verify("초기 상태: 회피 상태 아님", !actionState.IsEvading);
|
Verify("초기 상태: 무적 상태 아님", !actionState.IsDamageImmune);
|
||||||
|
|
||||||
yield return RunEvadeVerification();
|
yield return RunInvincibilitySkillVerification();
|
||||||
|
|
||||||
abnormalityManager.ApplyAbnormality(stunData, gameObject);
|
abnormalityManager.ApplyAbnormality(stunData, gameObject);
|
||||||
yield return new WaitForSeconds(settleDelay);
|
yield return new WaitForSeconds(settleDelay);
|
||||||
@@ -234,48 +234,46 @@ namespace Colosseum.Player
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerator RunEvadeVerification()
|
private IEnumerator RunInvincibilitySkillVerification()
|
||||||
{
|
{
|
||||||
SkillData evadeSkill = skillInput != null ? skillInput.GetSkill(6) : null;
|
SkillData invincibilitySkill = skillInput != null ? skillInput.GetSkill(6) : null;
|
||||||
if (evadeSkill == null)
|
if (invincibilitySkill == null)
|
||||||
{
|
{
|
||||||
AppendLine("[SKIP] 회피 검증: 회피 슬롯 스킬이 없습니다.");
|
AppendLine("[SKIP] 무적 스킬 검증: 7번 슬롯 스킬이 없습니다.");
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (skillController == null || !skillController.ExecuteSkill(evadeSkill))
|
if (skillController == null || !skillController.ExecuteSkill(invincibilitySkill))
|
||||||
{
|
{
|
||||||
Verify("회피 검증: 스킬 실행 성공", false);
|
Verify("무적 스킬 검증: 스킬 실행 성공", false);
|
||||||
yield break;
|
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);
|
yield return new WaitForSeconds(settleDelay);
|
||||||
|
Verify("무적 적용: 대미지 무시", Mathf.Approximately(networkController.Health, healthBeforeDamage));
|
||||||
Verify("회피 적용: IsEvading", actionState.IsEvading);
|
|
||||||
Verify("회피 적용: 이동 불가", !actionState.CanMove);
|
|
||||||
Verify("회피 적용: 점프 불가", !actionState.CanJump);
|
|
||||||
Verify("회피 적용: 일반 스킬 사용 불가", !actionState.CanUseSkills);
|
|
||||||
Verify("회피 적용: 회피 스킬 연속 사용 불가", !actionState.CanStartSkill(evadeSkill));
|
|
||||||
|
|
||||||
if (silenceData != null)
|
if (silenceData != null)
|
||||||
{
|
{
|
||||||
abnormalityManager.ApplyAbnormality(silenceData, gameObject);
|
abnormalityManager.ApplyAbnormality(silenceData, gameObject);
|
||||||
yield return WaitForConditionOrTimeout(() => actionState.IsSilenced, settleDelay + 0.5f);
|
yield return WaitForConditionOrTimeout(() => actionState.IsSilenced, settleDelay + 0.5f);
|
||||||
|
|
||||||
Verify("회피 중 침묵 적용: IsSilenced", actionState.IsSilenced);
|
Verify("무적 중 침묵 적용: IsSilenced", actionState.IsSilenced);
|
||||||
Verify("회피 중 침묵 적용: 회피 상태 유지", actionState.IsEvading);
|
Verify("무적 중 침묵 적용: 무적 상태 유지", actionState.IsDamageImmune);
|
||||||
Verify("회피 중 침묵 적용: 회피 스킬 신규 사용 불가", !actionState.CanStartSkill(evadeSkill));
|
Verify("무적 중 침묵 적용: 스킬 신규 사용 불가", !actionState.CanStartSkill(invincibilitySkill));
|
||||||
|
|
||||||
abnormalityManager.RemoveAbnormality(silenceData);
|
abnormalityManager.RemoveAbnormality(silenceData);
|
||||||
yield return new WaitForSeconds(settleDelay);
|
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("무적 해제: IsDamageImmune false", !actionState.IsDamageImmune);
|
||||||
Verify("회피 해제: 이동 가능 복구", actionState.CanMove);
|
|
||||||
Verify("회피 해제: 스킬 사용 가능 복구", actionState.CanUseSkills);
|
|
||||||
Verify("회피 해제: 회피 스킬 재사용 가능 복구", actionState.CanStartSkill(evadeSkill));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private float GetSkillDuration(SkillData skill)
|
private float GetSkillDuration(SkillData skill)
|
||||||
|
|||||||
@@ -56,10 +56,9 @@ namespace Colosseum.Player
|
|||||||
public SkillData CurrentSkill => skillController != null ? skillController.CurrentSkill : null;
|
public SkillData CurrentSkill => skillController != null ? skillController.CurrentSkill : null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 현재 시전 중인 스킬이 회피 스킬일 때의 회피 상태 여부.
|
/// 무적 이상상태 여부
|
||||||
/// 침묵은 회피 상태 자체를 끊지 않으며, 회피 스킬의 신규 사용만 막습니다.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsEvading => IsCastingSkill && CurrentSkill != null && CurrentSkill.IsEvadeSkill;
|
public bool IsDamageImmune => abnormalityManager != null && abnormalityManager.IsInvincible;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 입력을 받아도 되는지 여부
|
/// 입력을 받아도 되는지 여부
|
||||||
@@ -79,23 +78,20 @@ namespace Colosseum.Player
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 일반 스킬 시작 가능 여부
|
/// 일반 스킬 시작 가능 여부
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool CanUseSkills => CanReceiveInput && !IsStunned && !IsSilenced && !IsEvading && !BlocksSkillUseForCurrentSkill();
|
public bool CanUseSkills => CanReceiveInput && !IsStunned && !IsSilenced && !BlocksSkillUseForCurrentSkill();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 특정 스킬의 시작 가능 여부.
|
/// 특정 스킬의 시작 가능 여부.
|
||||||
/// 회피 스킬도 일반 스킬과 같은 시작 판정을 사용하되, 현재 시전 중인 스킬의 회피 차단 정책을 따릅니다.
|
/// 스킬 이름과 무관하게 동일한 시작 규칙을 사용합니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool CanStartSkill(SkillData skill)
|
public bool CanStartSkill(SkillData skill)
|
||||||
{
|
{
|
||||||
if (skill == null)
|
if (skill == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!CanReceiveInput || IsStunned || IsSilenced || IsEvading)
|
if (!CanReceiveInput || IsStunned || IsSilenced)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (skill.IsEvadeSkill)
|
|
||||||
return !BlocksEvadeForCurrentSkill();
|
|
||||||
|
|
||||||
return !BlocksSkillUseForCurrentSkill();
|
return !BlocksSkillUseForCurrentSkill();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,13 +144,5 @@ namespace Colosseum.Player
|
|||||||
|
|
||||||
return CurrentSkill == null || CurrentSkill.BlockOtherSkillsWhileCasting;
|
return CurrentSkill == null || CurrentSkill.BlockOtherSkillsWhileCasting;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool BlocksEvadeForCurrentSkill()
|
|
||||||
{
|
|
||||||
if (!IsCastingSkill)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return CurrentSkill == null || CurrentSkill.BlockEvadeWhileCasting;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ namespace Colosseum.Player
|
|||||||
[Tooltip("CharacterStats 컴포넌트 (없으면 자동 검색)")]
|
[Tooltip("CharacterStats 컴포넌트 (없으면 자동 검색)")]
|
||||||
[SerializeField] private CharacterStats characterStats;
|
[SerializeField] private CharacterStats characterStats;
|
||||||
|
|
||||||
|
[Tooltip("이상상태 관리자 (없으면 자동 검색)")]
|
||||||
|
[SerializeField] private AbnormalityManager abnormalityManager;
|
||||||
|
|
||||||
// 네트워크 동기화 변수
|
// 네트워크 동기화 변수
|
||||||
private NetworkVariable<float> currentHealth = new NetworkVariable<float>(100f);
|
private NetworkVariable<float> currentHealth = new NetworkVariable<float>(100f);
|
||||||
private NetworkVariable<float> currentMana = new NetworkVariable<float>(50f);
|
private NetworkVariable<float> currentMana = new NetworkVariable<float>(50f);
|
||||||
@@ -50,6 +53,11 @@ namespace Colosseum.Player
|
|||||||
characterStats = GetComponent<CharacterStats>();
|
characterStats = GetComponent<CharacterStats>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (abnormalityManager == null)
|
||||||
|
{
|
||||||
|
abnormalityManager = GetComponent<AbnormalityManager>();
|
||||||
|
}
|
||||||
|
|
||||||
// 네트워크 변수 변경 콜백 등록
|
// 네트워크 변수 변경 콜백 등록
|
||||||
currentHealth.OnValueChanged += HandleHealthChanged;
|
currentHealth.OnValueChanged += HandleHealthChanged;
|
||||||
currentMana.OnValueChanged += HandleManaChanged;
|
currentMana.OnValueChanged += HandleManaChanged;
|
||||||
@@ -93,7 +101,7 @@ namespace Colosseum.Player
|
|||||||
[Rpc(SendTo.Server)]
|
[Rpc(SendTo.Server)]
|
||||||
public void TakeDamageRpc(float damage)
|
public void TakeDamageRpc(float damage)
|
||||||
{
|
{
|
||||||
if (isDead.Value) return;
|
if (isDead.Value || IsDamageImmune()) return;
|
||||||
|
|
||||||
currentHealth.Value = Mathf.Max(0f, currentHealth.Value - damage);
|
currentHealth.Value = Mathf.Max(0f, currentHealth.Value - damage);
|
||||||
|
|
||||||
@@ -159,7 +167,6 @@ namespace Colosseum.Player
|
|||||||
isDead.Value = true;
|
isDead.Value = true;
|
||||||
|
|
||||||
// 사망 시 활성 이상 상태를 정리해 리스폰 시 잔존하지 않게 합니다.
|
// 사망 시 활성 이상 상태를 정리해 리스폰 시 잔존하지 않게 합니다.
|
||||||
var abnormalityManager = GetComponent<AbnormalityManager>();
|
|
||||||
if (abnormalityManager != null)
|
if (abnormalityManager != null)
|
||||||
{
|
{
|
||||||
abnormalityManager.RemoveAllAbnormalities();
|
abnormalityManager.RemoveAllAbnormalities();
|
||||||
@@ -202,7 +209,6 @@ namespace Colosseum.Player
|
|||||||
{
|
{
|
||||||
if (!IsServer) return;
|
if (!IsServer) return;
|
||||||
|
|
||||||
var abnormalityManager = GetComponent<AbnormalityManager>();
|
|
||||||
if (abnormalityManager != null)
|
if (abnormalityManager != null)
|
||||||
{
|
{
|
||||||
abnormalityManager.RemoveAllAbnormalities();
|
abnormalityManager.RemoveAllAbnormalities();
|
||||||
@@ -244,7 +250,7 @@ namespace Colosseum.Player
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public float TakeDamage(float damage, object source = null)
|
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);
|
float actualDamage = Mathf.Min(damage, currentHealth.Value);
|
||||||
currentHealth.Value = Mathf.Max(0f, currentHealth.Value - damage);
|
currentHealth.Value = Mathf.Max(0f, currentHealth.Value - damage);
|
||||||
@@ -269,6 +275,11 @@ namespace Colosseum.Player
|
|||||||
|
|
||||||
return actualHeal;
|
return actualHeal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsDamageImmune()
|
||||||
|
{
|
||||||
|
return abnormalityManager != null && abnormalityManager.IsInvincible;
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace Colosseum.Player
|
|||||||
public class PlayerSkillInput : NetworkBehaviour
|
public class PlayerSkillInput : NetworkBehaviour
|
||||||
{
|
{
|
||||||
[Header("Skill Slots")]
|
[Header("Skill Slots")]
|
||||||
[Tooltip("각 슬롯에 등록할 스킬 데이터 (6개 + 긴급회피)")]
|
[Tooltip("각 슬롯에 등록할 스킬 데이터 (6개 + 추가 슬롯)")]
|
||||||
[SerializeField] private SkillData[] skillSlots = new SkillData[7];
|
[SerializeField] private SkillData[] skillSlots = new SkillData[7];
|
||||||
|
|
||||||
[Header("References")]
|
[Header("References")]
|
||||||
|
|||||||
@@ -148,9 +148,34 @@ namespace Colosseum.Skills
|
|||||||
PlaySkillClip(skill.SkillClip);
|
PlaySkillClip(skill.SkillClip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TriggerImmediateSelfEffectsIfNeeded(skill);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 애니메이션 이벤트가 없는 자기 자신 대상 효과는 시전 즉시 발동합니다.
|
||||||
|
/// 버프/무적 같은 자기 강화 스킬이 이벤트 누락으로 동작하지 않는 상황을 막기 위한 보정입니다.
|
||||||
|
/// </summary>
|
||||||
|
private void TriggerImmediateSelfEffectsIfNeeded(SkillData skill)
|
||||||
|
{
|
||||||
|
if (skill == null || skill.Effects == null || skill.Effects.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (skill.SkillClip != null && skill.SkillClip.events != null && skill.SkillClip.events.Length > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (int i = 0; i < skill.Effects.Count; i++)
|
||||||
|
{
|
||||||
|
SkillEffect effect = skill.Effects[i];
|
||||||
|
if (effect == null || effect.TargetType != TargetType.Self)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (debugMode) Debug.Log($"[Skill] Immediate self effect: {effect.name} (index {i})");
|
||||||
|
effect.ExecuteOnCast(gameObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 스킬 클립으로 Override Controller 생성 후 재생
|
/// 스킬 클립으로 Override Controller 생성 후 재생
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -32,16 +32,12 @@ namespace Colosseum.Skills
|
|||||||
[SerializeField] private bool jumpToTarget = false;
|
[SerializeField] private bool jumpToTarget = false;
|
||||||
|
|
||||||
[Header("행동 제한")]
|
[Header("행동 제한")]
|
||||||
[Tooltip("이 스킬을 회피 상태로 취급할지 여부")]
|
|
||||||
[SerializeField] private bool isEvadeSkill = false;
|
|
||||||
[Tooltip("시전 중 이동 입력 차단 여부")]
|
[Tooltip("시전 중 이동 입력 차단 여부")]
|
||||||
[SerializeField] private bool blockMovementWhileCasting = true;
|
[SerializeField] private bool blockMovementWhileCasting = true;
|
||||||
[Tooltip("시전 중 점프 입력 차단 여부")]
|
[Tooltip("시전 중 점프 입력 차단 여부")]
|
||||||
[SerializeField] private bool blockJumpWhileCasting = true;
|
[SerializeField] private bool blockJumpWhileCasting = true;
|
||||||
[Tooltip("시전 중 다른 스킬 입력 차단 여부")]
|
[Tooltip("시전 중 다른 스킬 입력 차단 여부")]
|
||||||
[SerializeField] private bool blockOtherSkillsWhileCasting = true;
|
[SerializeField] private bool blockOtherSkillsWhileCasting = true;
|
||||||
[Tooltip("시전 중 회피 입력 차단 여부")]
|
|
||||||
[SerializeField] private bool blockEvadeWhileCasting = true;
|
|
||||||
|
|
||||||
[Header("쿨타임 & 비용")]
|
[Header("쿨타임 & 비용")]
|
||||||
[Min(0f)] [SerializeField] private float cooldown = 1f;
|
[Min(0f)] [SerializeField] private float cooldown = 1f;
|
||||||
@@ -63,11 +59,9 @@ namespace Colosseum.Skills
|
|||||||
public bool UseRootMotion => useRootMotion;
|
public bool UseRootMotion => useRootMotion;
|
||||||
public bool IgnoreRootMotionY => ignoreRootMotionY;
|
public bool IgnoreRootMotionY => ignoreRootMotionY;
|
||||||
public bool JumpToTarget => jumpToTarget;
|
public bool JumpToTarget => jumpToTarget;
|
||||||
public bool IsEvadeSkill => isEvadeSkill;
|
|
||||||
public bool BlockMovementWhileCasting => blockMovementWhileCasting;
|
public bool BlockMovementWhileCasting => blockMovementWhileCasting;
|
||||||
public bool BlockJumpWhileCasting => blockJumpWhileCasting;
|
public bool BlockJumpWhileCasting => blockJumpWhileCasting;
|
||||||
public bool BlockOtherSkillsWhileCasting => blockOtherSkillsWhileCasting;
|
public bool BlockOtherSkillsWhileCasting => blockOtherSkillsWhileCasting;
|
||||||
public bool BlockEvadeWhileCasting => blockEvadeWhileCasting;
|
|
||||||
public IReadOnlyList<SkillEffect> Effects => effects;
|
public IReadOnlyList<SkillEffect> Effects => effects;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user