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

182 lines
5.2 KiB
C#
Raw Normal View History

2026-01-29 06:58:38 +00:00
using UnityEngine;
2026-01-30 07:45:11 +00:00
using System.Collections;
2026-02-06 04:20:12 +00:00
using System.Collections.Generic;
2026-01-29 06:58:38 +00:00
public class PlayerAttack : MonoBehaviour
{
2026-02-06 04:20:12 +00:00
[Header("--- 활 설정 ---")]
2026-02-06 05:32:48 +00:00
[SerializeField] private GameObject arrowPrefab;
[SerializeField] private Transform firePoint;
[SerializeField] private PlayerAnimator pAnim;
2026-02-06 04:20:12 +00:00
[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<ChargeStage> chargeStages;
2026-02-02 15:02:12 +00:00
2026-02-06 04:20:12 +00:00
private float _lastAttackTime;
private float _chargeTimer;
private bool _isCharging = false;
private bool _isAttacking = false;
2026-02-06 05:32:48 +00:00
// 재장전 잠금 장치 (한 발 쏘면 우클릭 뗄 때까지 발사 불가)
private bool _waitForRelease = false;
2026-02-06 04:20:12 +00:00
private float _pendingDamage;
private float _pendingSpeed;
private float _pendingRange;
public bool IsCharging => _isCharging;
2026-02-02 15:02:12 +00:00
public bool IsAttacking
{
get => _isAttacking;
set => _isAttacking = value;
}
2026-02-06 04:20:12 +00:00
public float ChargeProgress => Mathf.Clamp01(_chargeTimer / maxChargeTime);
private void Start()
{
if (chargeStages == null || chargeStages.Count == 0)
{
chargeStages = new List<ChargeStage>
{
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 }
};
}
}
2026-01-30 07:45:11 +00:00
2026-01-29 06:58:38 +00:00
private void Update()
{
if (_isCharging)
{
_chargeTimer += Time.deltaTime;
}
}
2026-02-06 05:32:48 +00:00
// --- [1] 일반 공격 ---
2026-01-29 06:58:38 +00:00
public void PerformNormalAttack()
{
if (Time.time < _lastAttackTime + attackCooldown) return;
2026-02-06 04:20:12 +00:00
if (_isAttacking) return;
2026-01-29 06:58:38 +00:00
2026-02-06 04:20:12 +00:00
_pendingDamage = normalDamage;
_pendingSpeed = normalSpeed;
_pendingRange = normalRange;
2026-01-30 07:45:11 +00:00
2026-01-29 06:58:38 +00:00
_lastAttackTime = Time.time;
2026-02-06 04:20:12 +00:00
if (pAnim != null) pAnim.TriggerThrow();
2026-01-30 07:45:11 +00:00
2026-02-06 04:20:12 +00:00
StartCoroutine(AttackRoutine());
2026-01-30 07:45:11 +00:00
}
2026-02-06 05:32:48 +00:00
// --- [2] 차징 시작 ---
2026-02-06 04:20:12 +00:00
public void StartCharging()
2026-01-30 07:45:11 +00:00
{
2026-02-06 05:32:48 +00:00
// 🔒 잠금 상태(방금 쏨)라면 차징 시작 안 함 (손 떼야 풀림)
if (_waitForRelease) return;
2026-02-06 04:20:12 +00:00
_isCharging = true;
_chargeTimer = 0f;
2026-01-30 06:30:27 +00:00
2026-02-06 04:20:12 +00:00
if (pAnim != null) pAnim.SetCharging(true);
if (CinemachineShake.Instance != null) CinemachineShake.Instance.SetZoom(true);
2026-01-29 06:58:38 +00:00
}
2026-02-06 05:32:48 +00:00
// 내부용: 효과 끄기
private void ResetChargingEffects()
2026-01-29 06:58:38 +00:00
{
2026-01-30 07:45:11 +00:00
_isCharging = false;
_chargeTimer = 0f;
2026-02-06 04:20:12 +00:00
2026-01-30 07:45:11 +00:00
if (pAnim != null) pAnim.SetCharging(false);
if (CinemachineShake.Instance != null) CinemachineShake.Instance.SetZoom(false);
2026-01-29 06:58:38 +00:00
}
2026-02-06 05:32:48 +00:00
// 외부 호출: 우클릭 뗐을 때 (여기서 잠금을 풉니다)
public void CancelCharging()
{
ResetChargingEffects();
_waitForRelease = false; // ✅ 잠금 해제! 다시 쏠 수 있음
}
// --- [3] 차징 발사 ---
2026-01-29 06:58:38 +00:00
public void ReleaseAttack()
{
2026-02-06 04:20:12 +00:00
if (!_isCharging) return;
2026-01-29 06:58:38 +00:00
2026-02-06 04:20:12 +00:00
ChargeStage currentStage = chargeStages[0];
foreach (var stage in chargeStages)
2026-01-31 13:07:35 +00:00
{
2026-02-06 04:20:12 +00:00
if (_chargeTimer >= stage.chargeTime) currentStage = stage;
2026-01-31 13:07:35 +00:00
}
2026-02-06 04:20:12 +00:00
_pendingDamage = normalDamage * currentStage.damageMult;
_pendingSpeed = normalSpeed * currentStage.rangeMult;
_pendingRange = normalRange * currentStage.rangeMult;
2026-01-31 13:07:35 +00:00
2026-02-06 04:20:12 +00:00
if (pAnim != null) pAnim.TriggerThrow();
2026-01-31 13:07:35 +00:00
2026-02-06 05:32:48 +00:00
// 🔒 발사했으므로 잠금! (우클릭 뗄 때까지 차징 불가)
_waitForRelease = true;
2026-02-06 04:20:12 +00:00
_lastAttackTime = Time.time;
StartCoroutine(AttackRoutine());
}
2026-02-06 05:32:48 +00:00
// --- [4] 이벤트: 화살 생성 ---
2026-02-06 04:20:12 +00:00
public void OnShootArrow()
{
if (arrowPrefab == null || firePoint == null) return;
2026-01-31 13:07:35 +00:00
2026-02-06 04:20:12 +00:00
GameObject arrow = Instantiate(arrowPrefab, firePoint.position, transform.rotation);
PlayerArrow arrowScript = arrow.GetComponent<PlayerArrow>();
2026-01-29 06:58:38 +00:00
2026-02-06 04:20:12 +00:00
if (arrowScript != null)
2026-01-29 06:58:38 +00:00
{
2026-02-06 04:20:12 +00:00
arrowScript.Initialize(_pendingDamage, _pendingSpeed, _pendingRange);
2026-01-29 06:58:38 +00:00
}
}
2026-02-06 05:32:48 +00:00
// --- [5] 이벤트: 공격 끝 (정상적인 경우) ---
public void OnAttackEnd()
{
_isAttacking = false;
ResetChargingEffects();
}
// --- [6] 안전장치 코루틴 ---
2026-02-06 04:20:12 +00:00
private IEnumerator AttackRoutine()
2026-01-29 06:58:38 +00:00
{
2026-02-06 04:20:12 +00:00
_isAttacking = true;
2026-02-06 05:32:48 +00:00
// 🚨 안전장치: 0.6초 뒤에는 무조건 공격 상태를 풉니다!
// (애니메이션 이벤트 OnAttackEnd가 씹혀도 멈추지 않게 함)
yield return new WaitForSeconds(0.6f);
if (_isAttacking)
{
_isAttacking = false;
ResetChargingEffects();
}
2026-01-29 06:58:38 +00:00
}
2026-02-06 04:20:12 +00:00
public void StartWeaponCollision() { }
public void StopWeaponCollision() { }
2026-01-29 06:58:38 +00:00
}