using UnityEngine; public class PlayerAttack : MonoBehaviour { [Header("--- 참조 ---")] [SerializeField] private PlayerInteraction interaction; [SerializeField] private Stats stats; [SerializeField] private PlayerAnimator pAnim; [SerializeField] private PlayerHealth playerHealth; [SerializeField] private WeaponHitBox weaponHitBox; [Header("--- 일반 공격 설정 ---")] [SerializeField] private float attackCooldown = 0.5f; [Header("--- 단계별 투척 정확도(Spread) 설정 ---")] [Tooltip("Lv1 (0~1초) 투척 시 오차 범위 (값이 클수록 빗나감)")] [SerializeField] private float level1Spread = 15f; [Tooltip("Lv2 (1~2초) 투척 시 오차 범위")] [SerializeField] private float level2Spread = 7f; [Tooltip("Lv3 (풀차징) 투척 시 오차 범위 (0이면 100% 명중)")] [SerializeField] private float level3Spread = 0f; [SerializeField] private float fullChargeTime = 2f; private float _lastAttackTime, _chargeTimer; private bool _isCharging; // UI에서 차징 상태와 진행도를 확인하기 위한 통로 public float ChargeProgress => Mathf.Clamp01(_chargeTimer / fullChargeTime); public bool IsCharging => _isCharging; private void Update() { if (_isCharging) { _chargeTimer += Time.deltaTime; // 차징 중 우클릭(1) 시 즉시 취소 if (Input.GetMouseButtonDown(1)) { CancelCharging(); Debug.Log("차징 취소됨: 아이들 상태로 복귀"); } } } public void PerformNormalAttack() { if (playerHealth != null && playerHealth.isHit) return; if (interaction.CurrentWeapon == null || pAnim == null) return; if (Time.time < _lastAttackTime + attackCooldown) return; pAnim.TriggerAttack(); _lastAttackTime = Time.time; } public void StartWeaponCollision() { if (interaction.CurrentWeapon == null || weaponHitBox == null) return; float damage = stats.BaseAttackDamage + interaction.CurrentWeapon.Config.BaseDamage; weaponHitBox.EnableHitBox(damage); } public void StopWeaponCollision() { if (weaponHitBox != null) weaponHitBox.DisableHitBox(); } public void ReleaseAttack() { if (!_isCharging || interaction.CurrentWeapon == null) return; pAnim.TriggerThrow(); pAnim.SetCharging(false); // ⭐ [업그레이드] 정밀 조준을 위한 레이캐스트 방식 Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); Plane groundPlane = new Plane(Vector3.up, transform.position); // 캐릭터 발밑 높이의 가상 평면 float rayDistance; Vector3 targetDirection = transform.forward; // 기본값은 캐릭터 정면 if (groundPlane.Raycast(ray, out rayDistance)) { Vector3 pointOnGround = ray.GetPoint(rayDistance); // 마우스가 가리키는 실제 땅 지점 targetDirection = (pointOnGround - transform.position).normalized; targetDirection.y = 0; // 수평 발사 보정 } // 마우스 방향으로 캐릭터 즉시 회전 if (targetDirection != Vector3.zero) transform.rotation = Quaternion.LookRotation(targetDirection); // ⭐ [수정] 인스펙터에서 설정한 단계별 정확도(Spread) 적용 int lv = _chargeTimer >= 2f ? 3 : (_chargeTimer >= 1f ? 2 : 1); float currentSpread = (lv == 3) ? level3Spread : (lv == 2 ? level2Spread : level1Spread); // 최종 투척 방향 계산 (Spread 적용) Vector3 finalThrowDir = Quaternion.Euler(0, Random.Range(-currentSpread, currentSpread), 0) * targetDirection; interaction.CurrentWeapon.OnThrown(finalThrowDir, interaction.CurrentWeapon.Config.GetForce(lv), lv, stats); interaction.ClearCurrentWeapon(); stats.ResetWeight(); _isCharging = false; _chargeTimer = 0f; } public void StartCharging() { if (playerHealth != null && playerHealth.isHit) return; if (interaction.CurrentWeapon == null) return; _isCharging = true; _chargeTimer = 0f; pAnim.SetCharging(true); } public void CancelCharging() { _isCharging = false; _chargeTimer = 0f; if (pAnim != null) pAnim.SetCharging(false); } }