feat: 플레이어 다운/넉백 피격 반응 추가
This commit is contained in:
211
Assets/_Game/Scripts/Player/HitReactionController.cs
Normal file
211
Assets/_Game/Scripts/Player/HitReactionController.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
using UnityEngine;
|
||||
|
||||
using Unity.Netcode;
|
||||
|
||||
using Colosseum.Skills;
|
||||
|
||||
namespace Colosseum.Player
|
||||
{
|
||||
/// <summary>
|
||||
/// 플레이어의 피격 제어 상태를 관리합니다.
|
||||
/// 넉백 강제 이동과 다운 상태, 피격 애니메이션 재생을 담당합니다.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
[RequireComponent(typeof(PlayerMovement))]
|
||||
public class HitReactionController : NetworkBehaviour
|
||||
{
|
||||
[Header("References")]
|
||||
[Tooltip("플레이어 이동 컴포넌트")]
|
||||
[SerializeField] private PlayerMovement playerMovement;
|
||||
|
||||
[Tooltip("스킬 실행 관리자")]
|
||||
[SerializeField] private SkillController skillController;
|
||||
|
||||
[Tooltip("플레이어 네트워크 상태")]
|
||||
[SerializeField] private PlayerNetworkController networkController;
|
||||
|
||||
[Tooltip("피격 애니메이션을 재생할 Animator")]
|
||||
[SerializeField] private Animator animator;
|
||||
|
||||
[Header("Animation")]
|
||||
[Tooltip("일반 피격 트리거 이름")]
|
||||
[SerializeField] private string hitTriggerParam = "Hit";
|
||||
|
||||
[Tooltip("다운 시작 트리거 이름")]
|
||||
[SerializeField] private string downTriggerParam = "Down";
|
||||
|
||||
[Tooltip("기상 트리거 이름")]
|
||||
[SerializeField] private string recoverTriggerParam = "Recover";
|
||||
|
||||
[Header("Settings")]
|
||||
[Tooltip("애니메이션 파라미터가 없을 때 경고 로그 출력")]
|
||||
[SerializeField] private bool logMissingAnimationParams = false;
|
||||
|
||||
private readonly NetworkVariable<bool> isDowned = new NetworkVariable<bool>(false);
|
||||
private float downRemainingTime;
|
||||
|
||||
/// <summary>
|
||||
/// 다운 상태 여부
|
||||
/// </summary>
|
||||
public bool IsDowned => isDowned.Value;
|
||||
|
||||
/// <summary>
|
||||
/// 넉백 강제 이동 진행 여부
|
||||
/// </summary>
|
||||
public bool IsKnockbackActive => playerMovement != null && playerMovement.IsForcedMoving;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
ResolveReferences();
|
||||
}
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
ResolveReferences();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!IsServer || !isDowned.Value)
|
||||
return;
|
||||
|
||||
downRemainingTime -= Time.deltaTime;
|
||||
if (downRemainingTime <= 0f)
|
||||
{
|
||||
RecoverFromDown();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 넉백을 적용합니다.
|
||||
/// </summary>
|
||||
public void ApplyKnockback(Vector3 worldVelocity, float duration, bool playHitAnimation = true)
|
||||
{
|
||||
if (!IsServer)
|
||||
return;
|
||||
|
||||
ResolveReferences();
|
||||
|
||||
if (networkController != null && networkController.IsDead)
|
||||
return;
|
||||
|
||||
playerMovement?.ApplyForcedMovement(worldVelocity, duration);
|
||||
|
||||
if (playHitAnimation)
|
||||
{
|
||||
TriggerAnimationRpc(hitTriggerParam);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 다운 상태를 적용합니다.
|
||||
/// </summary>
|
||||
public void ApplyDown(float duration)
|
||||
{
|
||||
if (!IsServer)
|
||||
return;
|
||||
|
||||
ResolveReferences();
|
||||
|
||||
if (networkController != null && networkController.IsDead)
|
||||
return;
|
||||
|
||||
downRemainingTime = Mathf.Max(downRemainingTime, duration);
|
||||
|
||||
if (isDowned.Value)
|
||||
return;
|
||||
|
||||
isDowned.Value = true;
|
||||
skillController?.CancelSkill(SkillCancelReason.HitReaction);
|
||||
playerMovement?.ClearForcedMovement();
|
||||
TriggerAnimationRpc(downTriggerParam);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 다운 상태를 해제합니다.
|
||||
/// </summary>
|
||||
public void RecoverFromDown()
|
||||
{
|
||||
if (!IsServer || !isDowned.Value)
|
||||
return;
|
||||
|
||||
isDowned.Value = false;
|
||||
downRemainingTime = 0f;
|
||||
TriggerAnimationRpc(recoverTriggerParam);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 피격 상태를 즉시 초기화합니다.
|
||||
/// </summary>
|
||||
public void ClearHitReactionState(bool playRecoverAnimation = false)
|
||||
{
|
||||
if (!IsServer)
|
||||
return;
|
||||
|
||||
ResolveReferences();
|
||||
|
||||
playerMovement?.ClearForcedMovement();
|
||||
|
||||
if (!isDowned.Value)
|
||||
return;
|
||||
|
||||
isDowned.Value = false;
|
||||
downRemainingTime = 0f;
|
||||
|
||||
if (playRecoverAnimation)
|
||||
{
|
||||
TriggerAnimationRpc(recoverTriggerParam);
|
||||
}
|
||||
}
|
||||
|
||||
[Rpc(SendTo.Everyone)]
|
||||
private void TriggerAnimationRpc(string triggerName)
|
||||
{
|
||||
ResolveReferences();
|
||||
|
||||
if (animator == null || string.IsNullOrWhiteSpace(triggerName))
|
||||
return;
|
||||
|
||||
if (!HasTrigger(triggerName))
|
||||
{
|
||||
if (logMissingAnimationParams)
|
||||
{
|
||||
Debug.LogWarning($"[HitReaction] Animator trigger not found: {triggerName}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
animator.SetTrigger(triggerName);
|
||||
}
|
||||
|
||||
private bool HasTrigger(string triggerName)
|
||||
{
|
||||
if (animator == null || string.IsNullOrWhiteSpace(triggerName))
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < animator.parameterCount; i++)
|
||||
{
|
||||
AnimatorControllerParameter parameter = animator.GetParameter(i);
|
||||
if (parameter.type == AnimatorControllerParameterType.Trigger && parameter.name == triggerName)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ResolveReferences()
|
||||
{
|
||||
if (playerMovement == null)
|
||||
playerMovement = GetComponent<PlayerMovement>();
|
||||
|
||||
if (skillController == null)
|
||||
skillController = GetComponent<SkillController>();
|
||||
|
||||
if (networkController == null)
|
||||
networkController = GetComponent<PlayerNetworkController>();
|
||||
|
||||
if (animator == null)
|
||||
animator = GetComponentInChildren<Animator>();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user