using UnityEngine; using System.Collections; using System.Collections.Generic; public class PlayerAttack : MonoBehaviour { [Header("--- 활 설정 ---")] [SerializeField] private GameObject arrowPrefab; [SerializeField] private Transform firePoint; [SerializeField] private PlayerAnimator pAnim; [Header("--- 일반 공격 (좌클릭) ---")] [SerializeField] private float normalDamage = 10f; [SerializeField] private float normalRange = 15f; [SerializeField] private float normalSpeed = 20f; [SerializeField] private float attackCooldown = 0.5f; [Header("--- 차징 공격 (우클릭) ---")] [SerializeField] private float maxChargeTime = 2.0f; [System.Serializable] public struct ChargeStage { public float chargeTime; public float damageMult; public float rangeMult; } [SerializeField] private List chargeStages; [Header("--- 🎯 에임 보정 설정 ---")] [SerializeField] private bool enableAutoAim = true; // 자동 조준 켜기/끄기 [SerializeField] private float autoAimRange = 15f; // 자동 조준 감지 범위 [SerializeField] private float autoAimAngle = 45f; // 마우스 커서 기준 각도 (넓을수록 감지 잘됨) [Range(0f, 1f)] [Tooltip("에임 보정 강도 (0: 보정 없음, 1: 완전 자동 조준)")] [SerializeField] private float aimAssistStrength = 0.4f; // 40% 보정 (에임핵 느낌) [SerializeField] private LayerMask enemyLayer; // 적 레이어 [Tooltip("차징 최대 단계에서만 자동 조준 활성화")] [SerializeField] private bool onlyMaxCharge = true; // 최대 차징에서만 작동 private float _lastAttackTime; private float _chargeTimer; private bool _isCharging = false; private bool _isAttacking = false; private bool _waitForRelease = false; private float _pendingDamage; private float _pendingSpeed; private float _pendingRange; // 🎯 발사 방향 저장 private Vector3 _pendingShootDirection; public bool IsCharging => _isCharging; public bool IsAttacking { get => _isAttacking; set => _isAttacking = value; } public float ChargeProgress => Mathf.Clamp01(_chargeTimer / maxChargeTime); private void Start() { if (chargeStages == null || chargeStages.Count == 0) { chargeStages = new List { new ChargeStage { chargeTime = 0f, damageMult = 1f, rangeMult = 1f }, new ChargeStage { chargeTime = 1f, damageMult = 1.5f, rangeMult = 1.2f }, new ChargeStage { chargeTime = 2f, damageMult = 2.5f, rangeMult = 1.5f } }; } float calculatedMaxTime = 0f; foreach (var stage in chargeStages) { if (stage.chargeTime > calculatedMaxTime) calculatedMaxTime = stage.chargeTime; } maxChargeTime = Mathf.Max(calculatedMaxTime, 0.1f); } private void Update() { if (_isCharging) { _chargeTimer += Time.deltaTime; } } // --- [1] 일반 공격 --- public void PerformNormalAttack() { if (Time.time < _lastAttackTime + attackCooldown) return; if (_isAttacking) return; _pendingDamage = normalDamage; _pendingSpeed = normalSpeed; _pendingRange = normalRange; // 🎯 마우스 방향 계산 (일반 공격은 자동 조준 없음) _pendingShootDirection = GetMouseDirection(); _lastAttackTime = Time.time; if (pAnim != null) pAnim.TriggerThrow(); StartCoroutine(AttackRoutine()); } // --- [2] 차징 시작 --- public void StartCharging() { if (_waitForRelease) return; _isCharging = true; _chargeTimer = 0f; if (pAnim != null) pAnim.SetCharging(true); if (CinemachineShake.Instance != null) CinemachineShake.Instance.SetZoom(true); } private void ResetChargingEffects() { _isCharging = false; _chargeTimer = 0f; if (pAnim != null) pAnim.SetCharging(false); if (CinemachineShake.Instance != null) CinemachineShake.Instance.SetZoom(false); } // --- [3] 차징 취소 --- public void CancelCharging() { ResetChargingEffects(); _waitForRelease = false; } // --- [4] 차징 발사 --- public void ReleaseAttack() { if (!_isCharging) return; ChargeStage currentStage = chargeStages[0]; foreach (var stage in chargeStages) { if (_chargeTimer >= stage.chargeTime) currentStage = stage; } _pendingDamage = normalDamage * currentStage.damageMult; _pendingSpeed = normalSpeed * currentStage.rangeMult; _pendingRange = normalRange * currentStage.rangeMult; // 🎯 발사 방향 계산 (차징 최대 시 자동 조준) bool isMaxCharge = _chargeTimer >= maxChargeTime * 0.95f; // 95% 이상이면 최대 차징 _pendingShootDirection = GetShootDirection(isMaxCharge); if (pAnim != null) pAnim.TriggerThrow(); _waitForRelease = true; _lastAttackTime = Time.time; StartCoroutine(AttackRoutine()); } // --- [5] 🎯 마우스 방향 계산 --- private Vector3 GetMouseDirection() { // 마우스 위치 → 월드 좌표 Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); Plane groundPlane = new Plane(Vector3.up, transform.position); if (groundPlane.Raycast(ray, out float distance)) { Vector3 worldMousePos = ray.GetPoint(distance); Vector3 direction = (worldMousePos - firePoint.position).normalized; direction.y = 0; // 수평 발사 return direction; } // 실패 시 플레이어 정면 return transform.forward; } // --- [6] 🎯 발사 방향 계산 (에임 어시스트 포함) --- private Vector3 GetShootDirection(bool isMaxCharge) { Vector3 mouseDir = GetMouseDirection(); // 자동 조준 비활성화 또는 최대 차징 아님 if (!enableAutoAim || (onlyMaxCharge && !isMaxCharge)) { return mouseDir; } // 🎯 적 탐색 Transform bestTarget = FindBestTarget(mouseDir); if (bestTarget != null) { // 적 방향 계산 (중심부 조준) Vector3 targetPos = bestTarget.position + Vector3.up * 1.2f; Vector3 targetDir = (targetPos - firePoint.position).normalized; targetDir.y = 0; // 수평 발사 // ✨ 에임 어시스트: 마우스 방향과 적 방향을 섞음! // aimAssistStrength = 0.4 → 마우스 60% + 적 40% Vector3 assistedDir = Vector3.Lerp(mouseDir, targetDir, aimAssistStrength); assistedDir.y = 0; assistedDir.Normalize(); Debug.Log($"🎯 에임 어시스트 활성화! 타겟: {bestTarget.name}, 보정 강도: {aimAssistStrength * 100}%"); return assistedDir; } // 적 없으면 마우스 방향 return mouseDir; } // --- [7] 🎯 최적 타겟 찾기 --- private Transform FindBestTarget(Vector3 mouseDirection) { Collider[] enemies = Physics.OverlapSphere(transform.position, autoAimRange, enemyLayer); if (enemies.Length == 0) return null; Transform bestTarget = null; float bestScore = float.MaxValue; foreach (var enemy in enemies) { Vector3 dirToEnemy = (enemy.transform.position - transform.position).normalized; // 마우스 방향과의 각도 체크 float angle = Vector3.Angle(mouseDirection, dirToEnemy); if (angle > autoAimAngle) continue; // 각도 범위 밖 // 거리 체크 float distance = Vector3.Distance(transform.position, enemy.transform.position); // 점수 계산: 각도 + 거리 (낮을수록 좋음) float score = angle * 0.5f + distance * 0.5f; if (score < bestScore) { bestScore = score; bestTarget = enemy.transform; } } return bestTarget; } // --- [8] 이벤트: 화살 생성 --- public void OnShootArrow() { if (arrowPrefab == null || firePoint == null) return; // 🎯 저장된 방향으로 회전 계산 Quaternion shootRotation = Quaternion.LookRotation(_pendingShootDirection); // 화살 생성 (계산된 방향으로) GameObject arrow = Instantiate(arrowPrefab, firePoint.position, shootRotation); // ArrowItem 초기화 ArrowItem arrowItem = arrow.GetComponent(); if (arrowItem != null) { arrowItem.Initialize(_pendingDamage, _pendingSpeed, _pendingRange); return; } // 하위 호환 PlayerArrow arrowScript = arrow.GetComponent(); if (arrowScript != null) { arrowScript.Initialize(_pendingDamage, _pendingSpeed, _pendingRange); } } // --- [9] 이벤트: 공격 끝 --- public void OnAttackEnd() { _isAttacking = false; ResetChargingEffects(); } // --- [10] 안전장치 코루틴 --- private IEnumerator AttackRoutine() { _isAttacking = true; yield return new WaitForSeconds(0.6f); if (_isAttacking) { _isAttacking = false; ResetChargingEffects(); } } // --- [11] 화살 교체 --- public void SwapArrow(GameObject newArrow) { if (newArrow == null) return; arrowPrefab = newArrow; Debug.Log($"화살이 {newArrow.name}(으)로 교체되었습니다!"); } public void StartWeaponCollision() { } public void StopWeaponCollision() { } // --- [12] 🎯 디버그: 감지 범위 시각화 --- private void OnDrawGizmosSelected() { if (!enableAutoAim) return; // 자동 조준 범위 (노란색 원) Gizmos.color = Color.yellow; Gizmos.DrawWireSphere(transform.position, autoAimRange); // 마우스 방향 (파란색 선) if (Application.isPlaying && firePoint != null) { Vector3 mouseDir = GetMouseDirection(); Gizmos.color = Color.blue; Gizmos.DrawRay(firePoint.position, mouseDir * 5f); // 🎯 에임 어시스트 방향 (빨간색 선) Transform target = FindBestTarget(mouseDir); if (target != null) { Vector3 targetPos = target.position + Vector3.up * 1.2f; Vector3 targetDir = (targetPos - firePoint.position).normalized; targetDir.y = 0; // 보정된 최종 방향 Vector3 assistedDir = Vector3.Lerp(mouseDir, targetDir, aimAssistStrength); assistedDir.Normalize(); Gizmos.color = Color.red; Gizmos.DrawRay(firePoint.position, assistedDir * 7f); // 타겟 위치 표시 Gizmos.color = Color.green; Gizmos.DrawWireSphere(targetPos, 0.3f); } // 감지 각도 (연한 초록색 선) Gizmos.color = new Color(0, 1, 0, 0.3f); Vector3 leftBound = Quaternion.Euler(0, -autoAimAngle, 0) * mouseDir; Vector3 rightBound = Quaternion.Euler(0, autoAimAngle, 0) * mouseDir; Gizmos.DrawRay(firePoint.position, leftBound * autoAimRange); Gizmos.DrawRay(firePoint.position, rightBound * autoAimRange); } } }