feat: 회피 상태와 스킬 시작 판정 분리
This commit is contained in:
@@ -19,6 +19,11 @@ MonoBehaviour:
|
|||||||
endClip: {fileID: 0}
|
endClip: {fileID: 0}
|
||||||
useRootMotion: 1
|
useRootMotion: 1
|
||||||
ignoreRootMotionY: 1
|
ignoreRootMotionY: 1
|
||||||
|
isEvadeSkill: 1
|
||||||
|
blockMovementWhileCasting: 1
|
||||||
|
blockJumpWhileCasting: 1
|
||||||
|
blockOtherSkillsWhileCasting: 1
|
||||||
|
blockEvadeWhileCasting: 1
|
||||||
cooldown: 10
|
cooldown: 10
|
||||||
manaCost: 0
|
manaCost: 0
|
||||||
effects: []
|
effects: []
|
||||||
|
|||||||
@@ -453,6 +453,8 @@ MonoBehaviour:
|
|||||||
abnormalityManager: {fileID: 0}
|
abnormalityManager: {fileID: 0}
|
||||||
actionState: {fileID: 0}
|
actionState: {fileID: 0}
|
||||||
networkController: {fileID: 0}
|
networkController: {fileID: 0}
|
||||||
|
skillInput: {fileID: 0}
|
||||||
|
skillController: {fileID: 0}
|
||||||
stunData: {fileID: 0}
|
stunData: {fileID: 0}
|
||||||
silenceData: {fileID: 0}
|
silenceData: {fileID: 0}
|
||||||
runOnStartInEditor: 0
|
runOnStartInEditor: 0
|
||||||
|
|||||||
@@ -91,6 +91,10 @@ namespace Colosseum.Player
|
|||||||
GUILayout.BeginVertical();
|
GUILayout.BeginVertical();
|
||||||
|
|
||||||
GUILayout.Label($"사망 상태: {(networkController != null && networkController.IsDead ? "Dead" : "Alive")}");
|
GUILayout.Label($"사망 상태: {(networkController != null && networkController.IsDead ? "Dead" : "Alive")}");
|
||||||
|
if (TryGetComponent<PlayerActionState>(out var actionState))
|
||||||
|
{
|
||||||
|
GUILayout.Label($"회피 상태: {(actionState.IsEvading ? "Evading" : "Idle")} / 이동:{actionState.CanMove} / 스킬:{actionState.CanUseSkills}");
|
||||||
|
}
|
||||||
GUILayout.Label("입력 예시: 기절 / Data_Abnormality_Player_Stun / 0");
|
GUILayout.Label("입력 예시: 기절 / Data_Abnormality_Player_Stun / 0");
|
||||||
|
|
||||||
GUI.SetNextControlName("AbnormalityInputField");
|
GUI.SetNextControlName("AbnormalityInputField");
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@@ -5,6 +6,7 @@ using UnityEngine;
|
|||||||
using Unity.Netcode;
|
using Unity.Netcode;
|
||||||
|
|
||||||
using Colosseum.Abnormalities;
|
using Colosseum.Abnormalities;
|
||||||
|
using Colosseum.Skills;
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
@@ -23,6 +25,8 @@ namespace Colosseum.Player
|
|||||||
[SerializeField] private AbnormalityManager abnormalityManager;
|
[SerializeField] private AbnormalityManager abnormalityManager;
|
||||||
[SerializeField] private PlayerActionState actionState;
|
[SerializeField] private PlayerActionState actionState;
|
||||||
[SerializeField] private PlayerNetworkController networkController;
|
[SerializeField] private PlayerNetworkController networkController;
|
||||||
|
[SerializeField] private PlayerSkillInput skillInput;
|
||||||
|
[SerializeField] private SkillController skillController;
|
||||||
|
|
||||||
[Header("Test Data")]
|
[Header("Test Data")]
|
||||||
[SerializeField] private AbnormalityData stunData;
|
[SerializeField] private AbnormalityData stunData;
|
||||||
@@ -100,6 +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);
|
||||||
|
|
||||||
|
yield return RunEvadeVerification();
|
||||||
|
|
||||||
abnormalityManager.ApplyAbnormality(stunData, gameObject);
|
abnormalityManager.ApplyAbnormality(stunData, gameObject);
|
||||||
yield return new WaitForSeconds(settleDelay);
|
yield return new WaitForSeconds(settleDelay);
|
||||||
@@ -176,6 +183,10 @@ namespace Colosseum.Player
|
|||||||
actionState = GetComponent<PlayerActionState>();
|
actionState = GetComponent<PlayerActionState>();
|
||||||
if (networkController == null)
|
if (networkController == null)
|
||||||
networkController = GetComponent<PlayerNetworkController>();
|
networkController = GetComponent<PlayerNetworkController>();
|
||||||
|
if (skillInput == null)
|
||||||
|
skillInput = GetComponent<PlayerSkillInput>();
|
||||||
|
if (skillController == null)
|
||||||
|
skillController = GetComponent<SkillController>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadDefaultAssetsIfNeeded()
|
private void LoadDefaultAssetsIfNeeded()
|
||||||
@@ -222,5 +233,70 @@ namespace Colosseum.Player
|
|||||||
return Debug.isDebugBuild;
|
return Debug.isDebugBuild;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IEnumerator RunEvadeVerification()
|
||||||
|
{
|
||||||
|
SkillData evadeSkill = skillInput != null ? skillInput.GetSkill(6) : null;
|
||||||
|
if (evadeSkill == null)
|
||||||
|
{
|
||||||
|
AppendLine("[SKIP] 회피 검증: 회피 슬롯 스킬이 없습니다.");
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skillController == null || !skillController.ExecuteSkill(evadeSkill))
|
||||||
|
{
|
||||||
|
Verify("회피 검증: 스킬 실행 성공", false);
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return new WaitForSeconds(settleDelay);
|
||||||
|
|
||||||
|
Verify("회피 적용: IsEvading", actionState.IsEvading);
|
||||||
|
Verify("회피 적용: 이동 불가", !actionState.CanMove);
|
||||||
|
Verify("회피 적용: 점프 불가", !actionState.CanJump);
|
||||||
|
Verify("회피 적용: 일반 스킬 사용 불가", !actionState.CanUseSkills);
|
||||||
|
Verify("회피 적용: 회피 스킬 연속 사용 불가", !actionState.CanStartSkill(evadeSkill));
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
abnormalityManager.RemoveAbnormality(silenceData);
|
||||||
|
yield return new WaitForSeconds(settleDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return WaitForConditionOrTimeout(() => !actionState.IsEvading, GetSkillDuration(evadeSkill) + 1.5f);
|
||||||
|
|
||||||
|
Verify("회피 해제: IsEvading false", !actionState.IsEvading);
|
||||||
|
Verify("회피 해제: 이동 가능 복구", actionState.CanMove);
|
||||||
|
Verify("회피 해제: 스킬 사용 가능 복구", actionState.CanUseSkills);
|
||||||
|
Verify("회피 해제: 회피 스킬 재사용 가능 복구", actionState.CanStartSkill(evadeSkill));
|
||||||
|
}
|
||||||
|
|
||||||
|
private float GetSkillDuration(SkillData skill)
|
||||||
|
{
|
||||||
|
if (skill == null || skill.SkillClip == null)
|
||||||
|
return settleDelay;
|
||||||
|
|
||||||
|
return Mathf.Max(settleDelay, skill.SkillClip.length / Mathf.Max(0.1f, skill.AnimationSpeed));
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator WaitForConditionOrTimeout(Func<bool> predicate, float timeout)
|
||||||
|
{
|
||||||
|
float elapsed = 0f;
|
||||||
|
while (elapsed < timeout)
|
||||||
|
{
|
||||||
|
if (predicate())
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
elapsed += Time.deltaTime;
|
||||||
|
yield return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,12 @@ namespace Colosseum.Player
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public SkillData CurrentSkill => skillController != null ? skillController.CurrentSkill : null;
|
public SkillData CurrentSkill => skillController != null ? skillController.CurrentSkill : null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 현재 시전 중인 스킬이 회피 스킬일 때의 회피 상태 여부.
|
||||||
|
/// 침묵은 회피 상태 자체를 끊지 않으며, 회피 스킬의 신규 사용만 막습니다.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsEvading => IsCastingSkill && CurrentSkill != null && CurrentSkill.IsEvadeSkill;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 입력을 받아도 되는지 여부
|
/// 입력을 받아도 되는지 여부
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -71,14 +77,27 @@ namespace Colosseum.Player
|
|||||||
public bool CanJump => CanReceiveInput && !IsStunned && !BlocksJumpForCurrentSkill();
|
public bool CanJump => CanReceiveInput && !IsStunned && !BlocksJumpForCurrentSkill();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 스킬 사용 가능 여부
|
/// 일반 스킬 시작 가능 여부
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool CanUseSkills => CanReceiveInput && !IsStunned && !IsSilenced && !BlocksSkillUseForCurrentSkill();
|
public bool CanUseSkills => CanReceiveInput && !IsStunned && !IsSilenced && !IsEvading && !BlocksSkillUseForCurrentSkill();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 회피 사용 가능 여부
|
/// 특정 스킬의 시작 가능 여부.
|
||||||
|
/// 회피 스킬도 일반 스킬과 같은 시작 판정을 사용하되, 현재 시전 중인 스킬의 회피 차단 정책을 따릅니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool CanEvade => CanReceiveInput && !IsStunned && !IsSilenced && !BlocksEvadeForCurrentSkill();
|
public bool CanStartSkill(SkillData skill)
|
||||||
|
{
|
||||||
|
if (skill == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!CanReceiveInput || IsStunned || IsSilenced || IsEvading)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (skill.IsEvadeSkill)
|
||||||
|
return !BlocksEvadeForCurrentSkill();
|
||||||
|
|
||||||
|
return !BlocksSkillUseForCurrentSkill();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 현재 이동 속도 배율
|
/// 현재 이동 속도 배율
|
||||||
|
|||||||
@@ -123,8 +123,6 @@ namespace Colosseum.Player
|
|||||||
if (slotIndex < 0 || slotIndex >= skillSlots.Length)
|
if (slotIndex < 0 || slotIndex >= skillSlots.Length)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bool isEvadeSlot = slotIndex == skillSlots.Length - 1;
|
|
||||||
|
|
||||||
SkillData skill = skillSlots[slotIndex];
|
SkillData skill = skillSlots[slotIndex];
|
||||||
if (skill == null)
|
if (skill == null)
|
||||||
{
|
{
|
||||||
@@ -133,15 +131,9 @@ namespace Colosseum.Player
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 사망 상태 체크
|
// 사망 상태 체크
|
||||||
if (actionState != null)
|
if (actionState != null && !actionState.CanStartSkill(skill))
|
||||||
{
|
|
||||||
if (isEvadeSlot && !actionState.CanEvade)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!isEvadeSlot && !actionState.CanUseSkills)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 로컬 체크 (빠른 피드백용)
|
// 로컬 체크 (빠른 피드백용)
|
||||||
if (skillController.IsExecutingSkill)
|
if (skillController.IsExecutingSkill)
|
||||||
{
|
{
|
||||||
@@ -176,22 +168,13 @@ namespace Colosseum.Player
|
|||||||
if (slotIndex < 0 || slotIndex >= skillSlots.Length)
|
if (slotIndex < 0 || slotIndex >= skillSlots.Length)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bool isEvadeSlot = slotIndex == skillSlots.Length - 1;
|
|
||||||
|
|
||||||
SkillData skill = skillSlots[slotIndex];
|
SkillData skill = skillSlots[slotIndex];
|
||||||
if (skill == null) return;
|
if (skill == null) return;
|
||||||
|
|
||||||
// 서버에서 다시 검증
|
// 서버에서 다시 검증
|
||||||
// 사망 상태 체크
|
if (actionState != null && !actionState.CanStartSkill(skill))
|
||||||
if (actionState != null)
|
|
||||||
{
|
|
||||||
if (isEvadeSlot && !actionState.CanEvade)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!isEvadeSlot && !actionState.CanUseSkills)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (skillController.IsExecutingSkill || skillController.IsOnCooldown(skill))
|
if (skillController.IsExecutingSkill || skillController.IsOnCooldown(skill))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -279,7 +262,7 @@ namespace Colosseum.Player
|
|||||||
SkillData skill = GetSkill(slotIndex);
|
SkillData skill = GetSkill(slotIndex);
|
||||||
if (skill == null) return false;
|
if (skill == null) return false;
|
||||||
|
|
||||||
if (actionState != null && !actionState.CanUseSkills)
|
if (actionState != null && !actionState.CanStartSkill(skill))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return !skillController.IsOnCooldown(skill) && !skillController.IsExecutingSkill;
|
return !skillController.IsOnCooldown(skill) && !skillController.IsExecutingSkill;
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ 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("시전 중 점프 입력 차단 여부")]
|
||||||
@@ -61,6 +63,7 @@ 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;
|
||||||
|
|||||||
Reference in New Issue
Block a user