Projext/Assets/Scripts/Player/Combat/Attack.cs

374 lines
26 KiB
C#
Raw Normal View History

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