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; private float _lastAttackTime; private float _chargeTimer; private bool _isCharging = false; private bool _isAttacking = false; // 🔥 추가: 애니메이션 이벤트 발생 시점에 사용할 데이터 저장용 변수 private float _pendingDamage; private float _pendingSpeed; private float _pendingRange; 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 } }; } } 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; _lastAttackTime = Time.time; // 애니메이션 트리거 실행 if (pAnim != null) pAnim.TriggerThrow(); StartCoroutine(AttackRoutine()); } public void StartCharging() { _isCharging = true; _chargeTimer = 0f; if (pAnim != null) pAnim.SetCharging(true); if (CinemachineShake.Instance != null) CinemachineShake.Instance.SetZoom(true); } public void CancelCharging() { _isCharging = false; _chargeTimer = 0f; if (pAnim != null) pAnim.SetCharging(false); if (CinemachineShake.Instance != null) CinemachineShake.Instance.SetZoom(false); } // 2. 우클릭 중 좌클릭: 차징 발사 시도 (수치 계산 후 애니메이션 재생) 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; // 애니메이션 트리거 실행 if (pAnim != null) pAnim.TriggerThrow(); CancelCharging(); _lastAttackTime = Time.time; StartCoroutine(AttackRoutine()); } // 3. 🎯 핵심: 애니메이션의 'OnShootArrow' 이벤트 타이밍에 맞춰 실제 화살 생성 public void OnShootArrow() { if (arrowPrefab == null || firePoint == null) return; GameObject arrow = Instantiate(arrowPrefab, firePoint.position, transform.rotation); PlayerArrow arrowScript = arrow.GetComponent(); if (arrowScript != null) { // 미리 저장해둔(_pending...) 수치를 사용해 화살 초기화 arrowScript.Initialize(_pendingDamage, _pendingSpeed, _pendingRange); } } private IEnumerator AttackRoutine() { _isAttacking = true; yield return new WaitForSeconds(0.2f); _isAttacking = false; } public void StartWeaponCollision() { } public void StopWeaponCollision() { } }