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을 [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을 [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를 [SerializeField] private bool onlyMaxCharge = true; // 변수를 선언할거에요 -> 풀차징 때만 유도할지 여부인 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); // 조건이 맞으면 실행할거에요 -> 카메라 줌 효과를 켜기를 } private void ResetChargingEffects() // 함수를 선언할거에요 -> 차징 효과를 초기화하는 ResetChargingEffects를 { _isCharging = false; // 상태를 바꿀거에요 -> 차징 상태를 거짓(false)으로 _chargeTimer = 0f; // 값을 초기화할거에요 -> 차징 타이머를 if (pAnim != null) pAnim.SetCharging(false); // 실행할거에요 -> 애니메이터 차징 상태 끄기를 if (CinemachineShake.Instance != null) CinemachineShake.Instance.SetZoom(false); // 조건이 맞으면 실행할거에요 -> 카메라 줌 효과를 끄기를 } 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); // 레이를 쏠거에요 -> 카메라에서 마우스 위치로 Plane firePlane = new Plane(Vector3.up, firePoint.position); // 평면을 만들거에요 -> 발사 위치 높이의 수평면을 if (firePlane.Raycast(ray, out float distance)) // 조건이 맞으면 실행할거에요 -> 레이가 평면에 닿았다면 { Vector3 worldMousePos = ray.GetPoint(distance); // 위치를 구할거에요 -> 평면상 마우스 위치를 Vector3 direction = (worldMousePos - firePoint.position).normalized; // 벡터를 구할거에요 -> 발사 위치에서 마우스까지의 방향을 direction.y = 0; // 값을 바꿀거에요 -> 높이 차이는 무시하게 y를 0으로 return direction; // 반환할거에요 -> 계산된 방향을 } return transform.forward; // 반환할거에요 -> 실패 시 정면 방향을 } private Vector3 GetShootDirection(bool isMaxCharge) // 함수를 선언할거에요 -> 최종 발사 방향(보정 포함)을 구하는 GetShootDirection을 { Vector3 mouseDir = GetMouseDirection(); // 값을 가져올거에요 -> 마우스 방향을 if (!enableAutoAim || (onlyMaxCharge && !isMaxCharge)) return mouseDir; // 조건이 맞으면 반환할거에요 -> 보정 안 씀 설정이거나 풀차징이 아니면 마우스 방향 그대로 Transform bestTarget = FindBestTarget(mouseDir); // 함수를 실행할거에요 -> 가장 적합한 타겟을 찾는 FindBestTarget을 if (bestTarget != null) // 조건이 맞으면 실행할거에요 -> 타겟이 있다면 { Vector3 targetPos = bestTarget.position + Vector3.up * 1.2f; // 위치를 계산할거에요 -> 타겟의 중심부(높이 보정)를 Vector3 targetDir = (targetPos - firePoint.position).normalized; // 방향을 계산할거에요 -> 발사 위치에서 타겟까지 targetDir.y = 0; // 값을 바꿀거에요 -> 수평 방향만 고려하게 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; } // 파티클 프리팹을 발사 위치에 생성 Quaternion rotation = Quaternion.LookRotation(_pendingShootDirection); // 회전을 계산할거에요 -> 발사 방향을 바라보게 GameObject projectile = Instantiate(_currentProjectilePrefab, firePoint.position, rotation); // 생성할거에요 -> 화살 오브젝트를 // PlayerArrow 컴포넌트 확인/추가 후 초기화 PlayerArrow arrowScript = projectile.GetComponent(); // 컴포넌트를 가져올거에요 -> PlayerArrow 스크립트를 if (arrowScript == null) // 조건이 맞으면 실행할거에요 -> 스크립트가 없다면 { arrowScript = projectile.AddComponent(); // 추가할거에요 -> PlayerArrow 컴포넌트를 즉석에서 } // 발사 정보 + 속성 정보 전달 arrowScript.Initialize( // 초기화할거에요 -> 화살 스크립트에 모든 정보를 전달해서 _pendingDamage, _pendingSpeed, _pendingRange, _pendingShootDirection, _currentElementType, _currentElementDamage, _currentElementDuration ); } // ============================================ // 유틸리티 — 기존 로직 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); // 그림을 그릴거에요 -> 타겟 위치 표시를 } } } }