114 lines
4.8 KiB
C#
114 lines
4.8 KiB
C#
using UnityEngine;
|
|
using WL.Combat;
|
|
|
|
namespace WL.Player
|
|
{
|
|
/// <summary>
|
|
/// 플레이어 전투 파라미터 + 적 탐색.
|
|
/// 시야/공격 범위는 프리팹에서 바로 보이고 고칠 수 있도록 [SerializeField] 로 두고,
|
|
/// OnDrawGizmosSelected 로 씬 뷰에 노랑(시야)·빨강(공격) 원을 그린다.
|
|
///
|
|
/// 피해량은 CombatSettings(ScriptableObject)의 3~5 랜덤을 쓴다(C45).
|
|
/// 에셋이 비어 있을 때만 아래 fallbackAttackDamage 를 쓴다.
|
|
/// </summary>
|
|
[DisallowMultipleComponent]
|
|
public class PlayerCombat : MonoBehaviour
|
|
{
|
|
[Header("수치 설정 (ScriptableObject)")]
|
|
[SerializeField] private CombatSettings combatSettings;
|
|
|
|
[Header("■ PD 조정값 — 범위 (단위 m · 현재 임시값)")]
|
|
[Tooltip("PD 조정값 · 이 반경 안의 적을 탐색한다 (임시 8m)")]
|
|
[SerializeField] private float sightRange = 8f;
|
|
[Tooltip("PD 조정값 · 이 반경 안이면 공격한다. 사격 캐릭터 기준 임시 6m")]
|
|
[SerializeField] private float attackRange = 6f;
|
|
|
|
[Header("■ PD 조정값 — 공격")]
|
|
[Tooltip("PD 조정값 · 공격 간격(초) · 임시 1.2")]
|
|
[SerializeField] private float attackCooldown = 1.2f;
|
|
[Tooltip("CombatSettings 가 비어 있을 때만 쓰는 대체 피해량")]
|
|
[SerializeField] private float fallbackAttackDamage = 4f;
|
|
[Tooltip("PD 조정값 · 공격 모션 시작 후 피해가 들어가는 시간(초) · 임시 0.25")]
|
|
[SerializeField] private float attackHitDelay = 0.25f;
|
|
[Tooltip("PD 조정값 · 공격 모션 길이를 Animator 에서 못 읽을 때 쓰는 대체 시간(초)")]
|
|
[SerializeField] private float attackFallbackDuration = 0.8f;
|
|
|
|
[Header("■ PD 조정값 — 기즈모 표시")]
|
|
[Tooltip("선택했을 때 시야(노랑)·공격(빨강) 범위를 씬 뷰에 그린다")]
|
|
[SerializeField] private bool drawRangeGizmos = true;
|
|
[Tooltip("기즈모 원을 그리는 높이(발밑 기준 m)")]
|
|
[SerializeField] private float gizmoHeight = 0.05f;
|
|
|
|
private float _cooldownTimer;
|
|
|
|
public float SightRange { get { return sightRange; } }
|
|
public float AttackRange { get { return attackRange; } }
|
|
public float AttackCooldown { get { return attackCooldown; } }
|
|
public CombatSettings Settings { get { return combatSettings; } }
|
|
/// <summary>이번 공격의 피해량. CombatSettings 가 있으면 3~5 정수 랜덤.</summary>
|
|
public float RollAttackDamage()
|
|
{
|
|
return combatSettings != null ? combatSettings.RollPlayerDamage() : fallbackAttackDamage;
|
|
}
|
|
public float AttackHitDelay { get { return attackHitDelay; } }
|
|
public float AttackFallbackDuration { get { return attackFallbackDuration; } }
|
|
public bool CanAttack { get { return _cooldownTimer <= 0f; } }
|
|
|
|
private void Update()
|
|
{
|
|
if (_cooldownTimer > 0f) _cooldownTimer -= Time.deltaTime;
|
|
}
|
|
|
|
/// <summary>시야 범위 안의 가장 가까운 생존 적. 없으면 null.</summary>
|
|
public Enemy FindTarget()
|
|
{
|
|
return Enemy.FindNearestAlive(transform.position, sightRange);
|
|
}
|
|
|
|
public bool IsInAttackRange(Enemy e)
|
|
{
|
|
if (e == null) return false;
|
|
Vector3 a = transform.position; a.y = 0f;
|
|
Vector3 b = e.transform.position; b.y = 0f;
|
|
return (b - a).sqrMagnitude <= attackRange * attackRange;
|
|
}
|
|
|
|
public void ConsumeAttack()
|
|
{
|
|
_cooldownTimer = attackCooldown;
|
|
}
|
|
|
|
/// <summary>실제 피해 적용. 사거리를 다시 확인한 뒤 넣는다.</summary>
|
|
public void ApplyDamage(Enemy e)
|
|
{
|
|
if (e == null || !e.IsAlive) return;
|
|
if (!IsInAttackRange(e)) return;
|
|
e.TakeDamage(RollAttackDamage());
|
|
}
|
|
|
|
private void OnDrawGizmosSelected()
|
|
{
|
|
if (!drawRangeGizmos) return;
|
|
Vector3 c = transform.position + Vector3.up * gizmoHeight;
|
|
|
|
Gizmos.color = new Color(1f, 0.92f, 0.16f, 1f); // 노랑 = 시야
|
|
DrawCircle(c, sightRange);
|
|
Gizmos.color = new Color(1f, 0.20f, 0.20f, 1f); // 빨강 = 공격
|
|
DrawCircle(c, attackRange);
|
|
}
|
|
|
|
private static void DrawCircle(Vector3 center, float radius)
|
|
{
|
|
const int seg = 64;
|
|
Vector3 prev = center + new Vector3(radius, 0f, 0f);
|
|
for (int i = 1; i <= seg; i++)
|
|
{
|
|
float a = (i / (float)seg) * Mathf.PI * 2f;
|
|
Vector3 cur = center + new Vector3(Mathf.Cos(a) * radius, 0f, Mathf.Sin(a) * radius);
|
|
Gizmos.DrawLine(prev, cur);
|
|
prev = cur;
|
|
}
|
|
}
|
|
}
|
|
}
|