399 lines
28 KiB
C#
399 lines
28 KiB
C#
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<ChargeStage> 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>(); // 조건이 맞으면 가져올거에요 -> 부모의 Stats 컴포넌트를 playerStats에
|
|
|
|
// 기본 발사체 프리팹 설정
|
|
if (_currentProjectilePrefab == null) // 조건이 맞으면 실행할거에요 -> 현재 설정된 프리팹이 없다면
|
|
_currentProjectilePrefab = defaultProjectilePrefab; // 값을 넣을거에요 -> 기본 프리팹을
|
|
|
|
if (chargeStages == null || chargeStages.Count == 0) // 조건이 맞으면 실행할거에요 -> 차징 단계 리스트가 비어있다면
|
|
{
|
|
chargeStages = new List<ChargeStage> // 리스트를 생성할거에요 -> 기본 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 대체)
|
|
// ============================================
|
|
|
|
/// <summary>
|
|
/// ArrowPickup에서 습득 시 호출합니다.
|
|
/// 파티클 프리팹 + 속성 정보를 저장합니다.
|
|
/// </summary>
|
|
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"); // 로그를 출력할거에요 -> 장착된 화살 정보를
|
|
}
|
|
|
|
/// <summary>
|
|
/// 기본 화살로 초기화
|
|
/// </summary>
|
|
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] 화살 생성 — 파티클 프리팹 발사
|
|
// ============================================
|
|
|
|
/// <summary>
|
|
/// 애니메이션 이벤트에서 호출됩니다.
|
|
/// 파티클 프리팹을 Instantiate하고 PlayerArrow 컴포넌트로 이동/충돌을 처리합니다.
|
|
/// </summary>
|
|
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>(); // 가져올거에요 -> PlayerArrow 스크립트를
|
|
if (arrowScript == null) // 조건이 맞으면 실행할거에요 -> 스크립트가 없다면
|
|
{
|
|
arrowScript = projectile.AddComponent<PlayerArrow>(); // 추가할거에요 -> 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); // 그림을 그릴거에요 -> 타겟 위치 표시를
|
|
}
|
|
}
|
|
}
|
|
} |