using UnityEngine; // 유니티 엔진의 기본 기능을 불러올거에요 -> UnityEngine을 using System.Collections; // 코루틴 기능을 사용할거에요 -> System.Collections를 using System.Collections.Generic; // 리스트 기능을 사용할거에요 -> System.Collections.Generic을 public class PlayerAttack : MonoBehaviour // 클래스를 선언할거에요 -> MonoBehaviour를 상속받는 PlayerAttack을 { [Header("--- 활 설정 ---")] // 인스펙터 창에 제목을 표시할거에요 -> --- 활 설정 --- 을 [SerializeField] private Transform firePoint; // 변수를 선언할거에요 -> 화살 발사 위치인 firePoint를 [SerializeField] private PlayerAnimator pAnim; // 변수를 선언할거에요 -> 플레이어 애니메이터 스크립트인 pAnim을 [SerializeField] private PlayerSoundFX soundFX; // [NEW] 변수를 선언할거에요 -> 사운드 스크립트인 soundFX를 [Header("--- 스탯 참조 ---")] // 인스펙터 창에 제목을 표시할거에요 -> --- 스탯 참조 --- 를 [SerializeField] private Stats playerStats; // 변수를 선언할거에요 -> 플레이어 스탯 스크립트인 playerStats를 [Header("--- 일반 공격 (좌클릭) ---")] // 인스펙터 창에 제목을 표시할거에요 -> --- 일반 공격 (좌클릭) --- 을 [SerializeField] private float normalRange = 15f; // 변수를 선언할거에요 -> 일반 공격 사거리인 normalRange를 [SerializeField] private float normalSpeed = 20f; // 변수를 선언할거에요 -> 일반 화살 속도인 normalSpeed를 [SerializeField] private float attackCooldown = 0.5f; // 변수를 선언할거에요 -> 공격 쿨타임인 attackCooldown을 [Header("--- 차징 공격 (우클릭) ---")] // 인스펙터 창에 제목을 표시할거에요 -> --- 차징 공격 (우클릭) --- 을 [SerializeField] private float maxChargeTime = 2.0f; // 변수를 선언할거에요 -> 최대 차징 시간인 maxChargeTime을 [System.Serializable] // 직렬화할거에요 -> 인스펙터에서 수정할 수 있게 구조체를 public struct ChargeStage // 구조체를 정의할거에요 -> 차징 단계를 정의하는 ChargeStage를 { public float chargeTime; // 변수를 선언할거에요 -> 도달 시간인 chargeTime을 public float damageMult; // 변수를 선언할거에요 -> 데미지 배율인 damageMult를 public float rangeMult; // 변수를 선언할거에요 -> 사거리/속도 배율인 rangeMult를 } [SerializeField] private List chargeStages; // 리스트를 선언할거에요 -> 차징 단계 목록인 chargeStages를 [Header("--- 에임 보정 설정 ---")] // 인스펙터 창에 제목을 표시할거에요 -> --- 에임 보정 설정 --- 을 [SerializeField] private bool enableAutoAim = true; // 변수를 선언할거에요 -> 자동 조준 사용 여부인 enableAutoAim을 [Tooltip("마우스 방향 계산용 평면 높이 (캐릭터 발 기준 오프셋, 보통 허리~가슴 높이인 0.8~1.2 권장)")] [SerializeField] private float aimPlaneHeight = 1f; // 변수를 선언할거에요 -> 마우스 방향 계산 평면 높이를 (고정값으로 애니메이션 영향 차단) [SerializeField] private float autoAimRange = 15f; // 변수를 선언할거에요 -> 자동 조준 감지 거리인 autoAimRange를 [SerializeField] private float autoAimAngle = 45f; // 변수를 선언할거에요 -> 자동 조준 감지 각도인 autoAimAngle을 [Range(0f, 1f)] // 슬라이더를 만들거에요 -> 0부터 1 사이의 값으로 [SerializeField] private float aimAssistStrength = 0.4f; // 변수를 선언할거에요 -> 보정 강도인 aimAssistStrength를 [SerializeField] private LayerMask enemyLayer; // 변수를 선언할거에요 -> 적 레이어인 enemyLayer를 // onlyMaxCharge 제거 — 차징/일반 공격 모두 동일하게 마우스 방향 + 사거리 내 에임보정 적용 // ============================================ // [NEW] 파티클 기반 화살 시스템 // ============================================ [Header("--- 파티클 화살 시스템 ---")] // 인스펙터 창에 제목을 표시할거에요 -> --- 파티클 화살 시스템 --- 을 [SerializeField] private GameObject defaultProjectilePrefab; // 변수를 선언할거에요 -> 기본 발사체 프리팹인 defaultProjectilePrefab을 private GameObject _currentProjectilePrefab; // 변수를 선언할거에요 -> 현재 사용할 발사체 프리팹인 _currentProjectilePrefab을 private ArrowElementType _currentElementType = ArrowElementType.None; // 변수를 초기화할거에요 -> 현재 화살 속성인 _currentElementType을 없음으로 private float _currentElementDamage = 0f; // 변수를 초기화할거에요 -> 현재 속성 데미지인 _currentElementDamage를 0으로 private float _currentElementDuration = 0f; // 변수를 초기화할거에요 -> 현재 속성 지속 시간인 _currentElementDuration을 0으로 private float _lastAttackTime; // 변수를 선언할거에요 -> 마지막 공격 시간인 _lastAttackTime을 private float _chargeTimer; // 변수를 선언할거에요 -> 차징 진행 시간인 _chargeTimer를 private bool _isCharging = false; // 변수를 초기화할거에요 -> 차징 중 여부인 _isCharging을 거짓으로 private bool _isAttacking = false; // 변수를 초기화할거에요 -> 공격 동작 중 여부인 _isAttacking을 거짓으로 private bool _waitForRelease = false; // 변수를 초기화할거에요 -> 발사 대기 상태 여부인 _waitForRelease를 거짓으로 private float _pendingDamage; // 변수를 선언할거에요 -> 발사될 화살의 데미지인 _pendingDamage를 private float _pendingSpeed; // 변수를 선언할거에요 -> 발사될 화살의 속도인 _pendingSpeed를 private float _pendingRange; // 변수를 선언할거에요 -> 발사될 화살의 사거리인 _pendingRange를 private Vector3 _pendingShootDirection; // 변수를 선언할거에요 -> 발사될 방향인 _pendingShootDirection을 public bool IsCharging => _isCharging; // 프로퍼티를 선언할거에요 -> 차징 중인지 여부를 반환하는 IsCharging을 public bool IsAttacking { get => _isAttacking; set => _isAttacking = value; } // 프로퍼티를 선언할거에요 -> 공격 중인지 여부를 읽고 쓰는 IsAttacking을 public float ChargeProgress => Mathf.Clamp01(_chargeTimer / maxChargeTime); // 프로퍼티를 선언할거에요 -> 차징 진행률(0~1)을 반환하는 ChargeProgress를 // [NEW] 외부에서 현재 속성 확인용 public ArrowElementType CurrentElement => _currentElementType; // 프로퍼티를 선언할거에요 -> 현재 화살 속성을 반환하는 CurrentElement를 public GameObject CurrentProjectilePrefab => _currentProjectilePrefab; // 프로퍼티를 선언할거에요 -> 현재 발사체 프리팹을 반환하는 CurrentProjectilePrefab을 private void Start() // 함수를 실행할거에요 -> 스크립트 시작 시 Start를 { if (playerStats == null) playerStats = GetComponentInParent(); // 조건이 맞으면 가져올거에요 -> 부모의 Stats 컴포넌트를 playerStats에 // 기본 발사체 프리팹 설정 if (_currentProjectilePrefab == null) // 조건이 맞으면 실행할거에요 -> 현재 설정된 프리팹이 없다면 _currentProjectilePrefab = defaultProjectilePrefab; // 값을 넣을거에요 -> 기본 프리팹을 if (chargeStages == null || chargeStages.Count == 0) // 조건이 맞으면 실행할거에요 -> 차징 단계 리스트가 비어있다면 { chargeStages = new List // 리스트를 생성할거에요 -> 기본 3단계 차징 설정을 담은 리스트를 { new ChargeStage { chargeTime = 0f, damageMult = 1f, rangeMult = 1f }, // 값을 넣을거에요 -> 0단계 설정을 new ChargeStage { chargeTime = 1f, damageMult = 1.5f, rangeMult = 1.2f }, // 값을 넣을거에요 -> 1단계 설정을 new ChargeStage { chargeTime = 2f, damageMult = 2.5f, rangeMult = 1.5f } // 값을 넣을거에요 -> 2단계 설정을 }; } float calculatedMaxTime = 0f; // 변수를 초기화할거에요 -> 계산된 최대 시간을 0으로 foreach (var stage in chargeStages) // 반복할거에요 -> 모든 차징 단계에 대해 { if (stage.chargeTime > calculatedMaxTime) calculatedMaxTime = stage.chargeTime; // 조건이 맞으면 갱신할거에요 -> 더 긴 시간이 있다면 그 값으로 } maxChargeTime = Mathf.Max(calculatedMaxTime, 0.1f); // 값을 설정할거에요 -> 계산된 최대 시간으로 (최소 0.1 보장) } private void Update() // 함수를 실행할거에요 -> 매 프레임마다 Update를 { if (_isCharging) _chargeTimer += Time.deltaTime; // 조건이 맞으면 더할거에요 -> 차징 중이라면 타이머에 시간을 } // ============================================ // [NEW] 화살 장착 (SwapArrow 대체) // ============================================ /// /// ArrowPickup에서 습득 시 호출합니다. /// 파티클 프리팹 + 속성 정보를 저장합니다. /// public void SetCurrentArrow(ArrowData data) // 함수를 선언할거에요 -> 새로운 화살 데이터를 적용하는 SetCurrentArrow를 { if (data.projectilePrefab != null) // 조건이 맞으면 실행할거에요 -> 데이터에 프리팹이 있다면 _currentProjectilePrefab = data.projectilePrefab; // 값을 바꿀거에요 -> 현재 발사체 프리팹을 새 것으로 else // 조건이 틀리면 실행할거에요 -> 데이터에 프리팹이 없다면 _currentProjectilePrefab = defaultProjectilePrefab; // 값을 바꿀거에요 -> 기본 프리팹으로 _currentElementType = data.elementType; // 값을 저장할거에요 -> 화살 속성 타입을 _currentElementDamage = data.elementDamage; // 값을 저장할거에요 -> 속성 데미지를 _currentElementDuration = data.elementDuration; // 값을 저장할거에요 -> 속성 지속 시간을 Debug.Log($"화살 장착: [{data.arrowName}] 속성={data.elementType}, " + $"속성데미지={data.elementDamage}, 지속시간={data.elementDuration}s"); // 로그를 출력할거에요 -> 장착된 화살 정보를 } /// /// 기본 화살로 초기화 /// public void ResetArrow() // 함수를 선언할거에요 -> 화살을 기본 상태로 되돌리는 ResetArrow를 { _currentProjectilePrefab = defaultProjectilePrefab; // 값을 바꿀거에요 -> 발사체를 기본 프리팹으로 _currentElementType = ArrowElementType.None; // 값을 바꿀거에요 -> 속성을 없음(None)으로 _currentElementDamage = 0f; // 값을 초기화할거에요 -> 속성 데미지를 0으로 _currentElementDuration = 0f; // 값을 초기화할거에요 -> 속성 시간을 0으로 Debug.Log("화살 초기화: 기본 화살"); // 로그를 출력할거에요 -> 초기화 완료 메시지를 } // ============================================ // 일반 공격 (좌클릭) — 기존 로직 100% 유지 // ============================================ public void PerformNormalAttack() // 함수를 선언할거에요 -> 일반 공격을 수행하는 PerformNormalAttack을 { if (Time.time < _lastAttackTime + attackCooldown || _isAttacking) return; // 조건이 맞으면 중단할거에요 -> 쿨타임 중이거나 이미 공격 중이라면 _pendingDamage = (playerStats != null) ? playerStats.TotalAttackDamage : 10f; // 값을 가져올거에요 -> 스탯이 있으면 총 공격력을, 없으면 10을 _pendingSpeed = normalSpeed; // 값을 저장할거에요 -> 기본 속도를 _pendingRange = normalRange; // 값을 저장할거에요 -> 기본 사거리를 _pendingShootDirection = GetMouseDirection(); // 값을 계산할거에요 -> 마우스 방향을 구해서 발사 방향으로 _lastAttackTime = Time.time; // 값을 갱신할거에요 -> 마지막 공격 시간을 현재로 if (pAnim != null) pAnim.TriggerThrow(); // 실행할거에요 -> 던지는 애니메이션 트리거를 StartCoroutine(AttackRoutine()); // 코루틴을 시작할거에요 -> 공격 상태 관리를 위한 AttackRoutine을 } // ============================================ // 차징 공격 (우클릭) — 기존 로직 100% 유지 // ============================================ public void StartCharging() // 함수를 선언할거에요 -> 차징을 시작하는 StartCharging을 { if (_waitForRelease) return; // 조건이 맞으면 중단할거에요 -> 이미 발사 대기 중이라면 _isCharging = true; // 상태를 바꿀거에요 -> 차징 중 상태를 참(true)으로 _chargeTimer = 0f; // 값을 초기화할거에요 -> 차징 타이머를 0으로 if (pAnim != null) pAnim.SetCharging(true); // 실행할거에요 -> 애니메이터에 차징 시작을 알리기를 if (CinemachineShake.Instance != null) CinemachineShake.Instance.SetZoom(true); // 조건이 맞으면 실행할거에요 -> 카메라 줌 효과를 켜기를 if (soundFX != null) soundFX.PlayBowDraw(); // [NEW] 실행할거에요 -> 활 시위 당기기 + 차징 루프 소리를 } private void ResetChargingEffects() // 함수를 선언할거에요 -> 차징 효과를 초기화하는 ResetChargingEffects를 { _isCharging = false; // 상태를 바꿀거에요 -> 차징 상태를 거짓(false)으로 _chargeTimer = 0f; // 값을 초기화할거에요 -> 차징 타이머를 if (pAnim != null) pAnim.SetCharging(false); // 실행할거에요 -> 애니메이터 차징 상태 끄기를 if (CinemachineShake.Instance != null) CinemachineShake.Instance.SetZoom(false); // 조건이 맞으면 실행할거에요 -> 카메라 줌 효과를 끄기를 if (soundFX != null) soundFX.StopChargeLoop(); // [NEW] 실행할거에요 -> 차징 루프 소리 정지를 } public void CancelCharging() // 함수를 선언할거에요 -> 차징을 취소하는 CancelCharging을 { ResetChargingEffects(); // 함수를 실행할거에요 -> 차징 효과 초기화를 _waitForRelease = false; // 상태를 바꿀거에요 -> 발사 대기 상태를 거짓으로 } public void ReleaseAttack() // 함수를 선언할거에요 -> 차징된 공격을 발사하는 ReleaseAttack을 { if (!_isCharging) return; // 조건이 맞으면 중단할거에요 -> 차징 중이 아니라면 ChargeStage currentStage = chargeStages[0]; // 변수를 초기화할거에요 -> 기본 0단계를 시작값으로 foreach (var stage in chargeStages) // 반복할거에요 -> 모든 단계를 돌면서 { if (_chargeTimer >= stage.chargeTime) currentStage = stage; // 조건이 맞으면 갱신할거에요 -> 타이머가 단계 시간보다 길다면 해당 단계로 } float baseDmg = (playerStats != null) ? playerStats.TotalAttackDamage : 10f; // 값을 가져올거에요 -> 기본 공격력을 _pendingDamage = baseDmg * 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()); // 코루틴을 시작할거에요 -> 공격 루틴을 } // ============================================ // 에임 보정 시스템 — 기존 로직 100% 유지 // ============================================ private Vector3 GetMouseDirection() // 함수를 선언할거에요 -> 마우스가 가리키는 방향을 구하는 GetMouseDirection을 { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); // 레이를 쏠거에요 -> 카메라에서 마우스 위치로 // ⭐ firePoint 높이가 아닌 플레이어 발 기준 고정 높이로 평면 생성 // firePoint Y가 애니메이션에 따라 흔들리면 마우스 월드 좌표 계산이 틀어짐 // → transform.position.y (발 높이) + 고정 오프셋으로 안정적인 평면 사용 Vector3 stablePlaneOrigin = new Vector3(transform.position.x, transform.position.y + aimPlaneHeight, transform.position.z); // 계산할거에요 -> 안정적인 평면 기준점을 Plane firePlane = new Plane(Vector3.up, stablePlaneOrigin); // 만들거에요 -> 고정 높이 수평면을 if (firePlane.Raycast(ray, out float distance)) // 조건이 맞으면 실행할거에요 -> 레이가 평면에 닿았다면 { Vector3 worldMousePos = ray.GetPoint(distance); // 구할거에요 -> 평면상 마우스 월드 위치를 Vector3 direction = (worldMousePos - stablePlaneOrigin).normalized; // 구할거에요 -> 기준점에서 마우스까지의 방향을 direction.y = 0f; // 무시할거에요 -> 수직 성분을 if (direction.sqrMagnitude < 0.001f) return transform.forward; // 방어할거에요 -> 0벡터 방지 return direction; // 반환할거에요 -> 계산된 방향을 } return transform.forward; // 반환할거에요 -> 실패 시 정면 방향을 } // ⭐ GetShootDirection — 차징/일반 공격 모두 동일한 로직 // // 1. 기본: 마우스 커서 방향으로 발사 // 2. autoAimRange 내에 적이 있고 마우스 방향 autoAimAngle 이내라면 에임보정 적용 // (풀차징 여부 관계없이 항상 동일하게 동작) private Vector3 GetShootDirection(bool isMaxCharge = false) // 함수를 선언할거에요 -> 최종 발사 방향을 구하는 GetShootDirection을 (isMaxCharge는 더 이상 사용 안 함) { Vector3 mouseDir = GetMouseDirection(); // 가져올거에요 -> 현재 마우스 방향을 if (!enableAutoAim) 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 = 0f; // 무시할거에요 -> 수직 성분을 Vector3 assistedDir = Vector3.Lerp(mouseDir, targetDir, aimAssistStrength); // 보간할거에요 -> 마우스 방향과 타겟 방향 사이를 강도만큼 assistedDir.Normalize(); // 정규화할거에요 -> 벡터 길이를 1로 return assistedDir; // 반환할거에요 -> 에임보정된 방향을 } return mouseDir; // 반환할거에요 -> 적 없으면 마우스 방향 그대로 } private Transform FindBestTarget(Vector3 mouseDirection) // 함수를 선언할거에요 -> 조준 보정 대상을 찾는 FindBestTarget을 { Collider[] enemies = Physics.OverlapSphere(transform.position, autoAimRange, enemyLayer); // 배열에 담을거에요 -> 사거리 내의 모든 적을 if (enemies.Length == 0) return null; // 조건이 맞으면 반환할거에요 -> 적이 없으면 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; // 반환할거에요 -> 가장 점수가 좋은 타겟을 } // ============================================ // [MODIFIED] 화살 생성 — 파티클 프리팹 발사 // ============================================ /// /// 애니메이션 이벤트에서 호출됩니다. /// 파티클 프리팹을 Instantiate하고 PlayerArrow 컴포넌트로 이동/충돌을 처리합니다. /// public void OnShootArrow() // 함수를 선언할거에요 -> 애니메이션 이벤트로 호출될 발사 함수 OnShootArrow를 { if (_currentProjectilePrefab == null || firePoint == null) // 조건이 맞으면 중단할거에요 -> 프리팹이나 발사 위치가 없다면 { Debug.LogWarning("발사체 프리팹 또는 firePoint가 없습니다!"); // 경고 로그를 띄울거에요 -> 설정 확인 필요 메시지를 return; } if (soundFX != null) soundFX.PlayBowRelease(); // 실행할거에요 -> 화살 발사 소리를 // ⭐ 발사 직전 방향 재계산 // _waitForRelease = true → 차징 발사 → 마우스 방향 + 에임보정 // _waitForRelease = false → 일반 공격 → 마우스 방향만 (에임보정 없음) Vector3 shootDir = _waitForRelease ? GetShootDirection() // 차징: 에임보정 포함 : GetMouseDirection(); // 일반: 마우스 방향만 Quaternion rotation = Quaternion.LookRotation(shootDir); // 계산할거에요 -> 발사 방향으로의 회전을 GameObject projectile = Instantiate(_currentProjectilePrefab, firePoint.position, rotation); // 생성할거에요 -> 화살 오브젝트를 PlayerArrow arrowScript = projectile.GetComponent(); // 가져올거에요 -> PlayerArrow 스크립트를 if (arrowScript == null) // 조건이 맞으면 실행할거에요 -> 스크립트가 없다면 { arrowScript = projectile.AddComponent(); // 추가할거에요 -> PlayerArrow 컴포넌트를 즉석에서 } arrowScript.Initialize( // 초기화할거에요 -> 화살 스크립트에 모든 정보를 전달해서 _pendingDamage, // 클릭 시점에 확정된 데미지 (스탯 기반이라 그대로 사용) _pendingSpeed, // 클릭 시점에 확정된 속도 _pendingRange, // 클릭 시점에 확정된 사거리 shootDir, // ⭐ 발사 직전 재계산된 방향 _currentElementType, _currentElementDamage, _currentElementDuration ); _waitForRelease = false; // 초기화할거에요 -> 발사 대기 상태를 } // ============================================ // 유틸리티 — 기존 로직 100% 유지 // ============================================ public void OnAttackEnd() // 함수를 선언할거에요 -> 공격 애니메이션 종료 시 호출될 OnAttackEnd를 { _isAttacking = false; // 상태를 바꿀거에요 -> 공격 상태를 거짓으로 ResetChargingEffects(); // 함수를 실행할거에요 -> 차징 효과 초기화를 } private IEnumerator AttackRoutine() // 코루틴 함수를 선언할거에요 -> 공격 상태 안전장치인 AttackRoutine을 { _isAttacking = true; // 상태를 바꿀거에요 -> 공격 중 상태를 참으로 yield return new WaitForSeconds(0.6f); // 기다릴거에요 -> 0.6초(모션 시간)만큼 if (_isAttacking) // 조건이 맞으면 실행할거에요 -> 아직도 공격 중 상태라면 (강제 종료) { _isAttacking = false; // 상태를 바꿀거에요 -> 공격 상태를 거짓으로 ResetChargingEffects(); // 함수를 실행할거에요 -> 효과 초기화를 } } // [DEPRECATED] 하위 호환성을 위해 남겨둠 [System.Obsolete("SetCurrentArrow(ArrowData)를 사용하세요.")] // 경고를 띄울거에요 -> 이 함수는 더 이상 쓰지 말라고 public void SwapArrow(GameObject newArrow) // 함수를 선언할거에요 -> 구버전 화살 교체 함수 SwapArrow를 { if (newArrow == null) return; // 조건이 맞으면 중단할거에요 -> 비어있다면 Debug.LogWarning("SwapArrow()는 더 이상 사용되지 않습니다. SetCurrentArrow()를 사용하세요."); // 경고 로그를 출력할거에요 -> 새 함수 사용 권장을 } public void StartWeaponCollision() { } // 함수를 비워둘거에요 -> 근접 무기 충돌 시작(활에는 필요 없음) public void StopWeaponCollision() { } // 함수를 비워둘거에요 -> 근접 무기 충돌 종료(활에는 필요 없음) // ============================================ // Gizmo 디버그 — 기존 로직 100% 유지 // ============================================ private void OnDrawGizmosSelected() // 함수를 실행할거에요 -> 에디터 선택 시 디버그 그림을 그리는 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); // 그림을 그릴거에요 -> 타겟 위치 표시를 } } } }