feat: 플레이어 다운 및 넉백 피격 반응 추가
- HitReactionController로 다운과 넉백 전용 로직을 분리 - 다운 시작, 루프, 회복 애니메이션과 DownEffect를 연결 - 행동 상태와 스킬 취소가 피격 반응과 연동되도록 정리 - 자동 검증 러너에 다운 및 넉백 검증을 추가
This commit is contained in:
@@ -27,6 +27,8 @@ namespace Colosseum.Player
|
||||
[SerializeField] private PlayerNetworkController networkController;
|
||||
[SerializeField] private PlayerSkillInput skillInput;
|
||||
[SerializeField] private SkillController skillController;
|
||||
[SerializeField] private PlayerMovement playerMovement;
|
||||
[SerializeField] private HitReactionController hitReactionController;
|
||||
|
||||
[Header("Test Data")]
|
||||
[SerializeField] private AbnormalityData stunData;
|
||||
@@ -84,7 +86,7 @@ namespace Colosseum.Player
|
||||
ResolveReferences();
|
||||
LoadDefaultAssetsIfNeeded();
|
||||
|
||||
if (abnormalityManager == null || actionState == null || networkController == null || stunData == null || silenceData == null)
|
||||
if (abnormalityManager == null || actionState == null || networkController == null || playerMovement == null || hitReactionController == null || stunData == null || silenceData == null)
|
||||
{
|
||||
Debug.LogWarning("[AbnormalityVerification] Missing references or test data.");
|
||||
yield break;
|
||||
@@ -105,9 +107,16 @@ namespace Colosseum.Player
|
||||
Verify("초기 상태: 이동 가능", actionState.CanMove);
|
||||
Verify("초기 상태: 스킬 사용 가능", actionState.CanUseSkills);
|
||||
Verify("초기 상태: 무적 상태 아님", !actionState.IsDamageImmune);
|
||||
Verify("초기 상태: 마지막 취소 이유 없음", skillController == null || skillController.LastCancelReason == SkillCancelReason.None);
|
||||
|
||||
yield return RunInvincibilitySkillVerification();
|
||||
|
||||
yield return RunStunCancellationVerification();
|
||||
|
||||
yield return RunDownVerification();
|
||||
|
||||
yield return RunKnockbackVerification();
|
||||
|
||||
abnormalityManager.ApplyAbnormality(stunData, gameObject);
|
||||
yield return new WaitForSeconds(settleDelay);
|
||||
|
||||
@@ -187,6 +196,10 @@ namespace Colosseum.Player
|
||||
skillInput = GetComponent<PlayerSkillInput>();
|
||||
if (skillController == null)
|
||||
skillController = GetComponent<SkillController>();
|
||||
if (playerMovement == null)
|
||||
playerMovement = GetComponent<PlayerMovement>();
|
||||
if (hitReactionController == null)
|
||||
hitReactionController = GetComponent<HitReactionController>();
|
||||
}
|
||||
|
||||
private void LoadDefaultAssetsIfNeeded()
|
||||
@@ -276,6 +289,109 @@ namespace Colosseum.Player
|
||||
Verify("무적 해제: IsDamageImmune false", !actionState.IsDamageImmune);
|
||||
}
|
||||
|
||||
private IEnumerator RunStunCancellationVerification()
|
||||
{
|
||||
SkillData cancellableSkill = FindCancellableSkill();
|
||||
if (cancellableSkill == null)
|
||||
{
|
||||
AppendLine("[SKIP] 기절 강제 취소 검증: 테스트용 스킬이 없습니다.");
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (skillController != null)
|
||||
{
|
||||
yield return WaitForConditionOrTimeout(() => !skillController.IsPlayingAnimation, 1.5f);
|
||||
}
|
||||
|
||||
if (skillController == null || !skillController.ExecuteSkill(cancellableSkill))
|
||||
{
|
||||
Verify("기절 강제 취소 검증: 스킬 실행 성공", false);
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(0.05f);
|
||||
|
||||
abnormalityManager.ApplyAbnormality(stunData, gameObject);
|
||||
yield return WaitForConditionOrTimeout(() => abnormalityManager.IsStunned, settleDelay + 0.5f);
|
||||
yield return new WaitForSeconds(0.05f);
|
||||
|
||||
Verify("기절 강제 취소: 스킬 애니메이션 중단", !skillController.IsPlayingAnimation);
|
||||
Verify("기절 강제 취소: 취소 이유 기록", skillController.LastCancelReason == SkillCancelReason.Stun);
|
||||
|
||||
yield return new WaitForSeconds(stunData.duration + settleDelay);
|
||||
}
|
||||
|
||||
private IEnumerator RunKnockbackVerification()
|
||||
{
|
||||
abnormalityManager.RemoveAllAbnormalities();
|
||||
hitReactionController.ClearHitReactionState();
|
||||
yield return new WaitForSeconds(settleDelay);
|
||||
|
||||
Vector3 startPosition = transform.position;
|
||||
Vector3 knockbackVelocity = Vector3.back * 6f;
|
||||
|
||||
RequestKnockbackRpc(knockbackVelocity, 0.2f);
|
||||
yield return new WaitForSeconds(0.3f);
|
||||
|
||||
float movedDistance = Vector3.Distance(startPosition, transform.position);
|
||||
Verify("넉백 적용: 위치 이동 발생", movedDistance > 0.2f);
|
||||
Verify("넉백 적용: 강제 이동 종료", !playerMovement.IsForcedMoving);
|
||||
}
|
||||
|
||||
private IEnumerator RunDownVerification()
|
||||
{
|
||||
SkillData cancellableSkill = FindCancellableSkill();
|
||||
if (cancellableSkill == null)
|
||||
{
|
||||
AppendLine("[SKIP] 다운 검증: 테스트용 스킬이 없습니다.");
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (skillController != null)
|
||||
{
|
||||
yield return WaitForConditionOrTimeout(() => !skillController.IsPlayingAnimation, 1.5f);
|
||||
}
|
||||
|
||||
if (skillController == null || !skillController.ExecuteSkill(cancellableSkill))
|
||||
{
|
||||
Verify("다운 강제 취소 검증: 스킬 실행 성공", false);
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(0.05f);
|
||||
|
||||
RequestDownRpc(0.6f);
|
||||
yield return WaitForConditionOrTimeout(() => hitReactionController.IsDowned, settleDelay + 0.5f);
|
||||
yield return new WaitForSeconds(0.05f);
|
||||
|
||||
Verify("다운 적용: IsDowned", hitReactionController.IsDowned);
|
||||
Verify("다운 적용: ActionState.IsDowned", actionState.IsDowned);
|
||||
Verify("다운 강제 취소: 스킬 애니메이션 중단", !skillController.IsPlayingAnimation);
|
||||
Verify("다운 강제 취소: 취소 이유 기록", skillController.LastCancelReason == SkillCancelReason.HitReaction);
|
||||
Verify("다운 적용: 이동 불가", !actionState.CanMove);
|
||||
Verify("다운 적용: 점프 불가", !actionState.CanJump);
|
||||
Verify("다운 적용: 스킬 사용 불가", !actionState.CanUseSkills);
|
||||
Verify("다운 적용: 이동속도 0", Mathf.Approximately(actionState.MoveSpeedMultiplier, 0f));
|
||||
|
||||
yield return WaitForConditionOrTimeout(() => !hitReactionController.IsDowned, 1.5f);
|
||||
|
||||
Verify("다운 해제: IsDowned false", !hitReactionController.IsDowned);
|
||||
Verify("다운 해제: 이동 가능 복구", actionState.CanMove);
|
||||
Verify("다운 해제: 스킬 사용 가능 복구", actionState.CanUseSkills);
|
||||
}
|
||||
|
||||
[Rpc(SendTo.Server)]
|
||||
private void RequestDownRpc(float duration)
|
||||
{
|
||||
hitReactionController?.ApplyDown(duration);
|
||||
}
|
||||
|
||||
[Rpc(SendTo.Server)]
|
||||
private void RequestKnockbackRpc(Vector3 velocity, float duration)
|
||||
{
|
||||
hitReactionController?.ApplyKnockback(velocity, duration, false);
|
||||
}
|
||||
|
||||
private float GetSkillDuration(SkillData skill)
|
||||
{
|
||||
if (skill == null || skill.SkillClip == null)
|
||||
@@ -284,6 +400,21 @@ namespace Colosseum.Player
|
||||
return Mathf.Max(settleDelay, skill.SkillClip.length / Mathf.Max(0.1f, skill.AnimationSpeed));
|
||||
}
|
||||
|
||||
private SkillData FindCancellableSkill()
|
||||
{
|
||||
if (skillInput == null)
|
||||
return null;
|
||||
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
SkillData skill = skillInput.GetSkill(i);
|
||||
if (skill != null)
|
||||
return skill;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private IEnumerator WaitForConditionOrTimeout(Func<bool> predicate, float timeout)
|
||||
{
|
||||
float elapsed = 0f;
|
||||
|
||||
Reference in New Issue
Block a user