2026-02-19 18:14:55 +00:00
|
|
|
using UnityEngine; // 유니티 기능을 불러올거에요 -> UnityEngine을
|
2026-02-04 14:06:25 +00:00
|
|
|
|
2026-02-13 09:11:54 +00:00
|
|
|
public class UniversalRangedMonster : MonsterClass // 클래스를 선언할거에요 -> 원거리 몬스터를
|
2026-02-04 14:06:25 +00:00
|
|
|
{
|
2026-02-13 09:11:54 +00:00
|
|
|
public enum AttackStyle { Straight, Lob } // 열거형을 정의할거에요 -> 공격 타입을
|
|
|
|
|
[Header("=== 원거리 설정 ===")] // 인스펙터 제목을 달거에요 -> 원거리 설정을
|
|
|
|
|
[SerializeField] private AttackStyle attackStyle = AttackStyle.Straight; // 변수를 선언할거에요 -> 스타일을
|
|
|
|
|
[SerializeField] private GameObject projectilePrefab; // 변수를 선언할거에요 -> 투사체를
|
|
|
|
|
[SerializeField] private Transform firePoint; // 변수를 선언할거에요 -> 발사 위치를
|
|
|
|
|
[SerializeField] private float attackRange = 10f; // 변수를 선언할거에요 -> 사거리를
|
|
|
|
|
[SerializeField] private float attackDelay = 2f; // 변수를 선언할거에요 -> 딜레이를
|
|
|
|
|
[SerializeField] private float projectileSpeed = 20f; // 변수를 선언할거에요 -> 속도를
|
2026-02-05 15:42:48 +00:00
|
|
|
|
2026-02-13 09:11:54 +00:00
|
|
|
[Header("곡사 설정")] // 인스펙터 제목을 달거에요 -> 곡사 설정을
|
|
|
|
|
[SerializeField] private float launchAngle = 45f; // 변수를 선언할거에요 -> 각도를
|
|
|
|
|
[SerializeField] private float aimHeightOffset = 1.2f; // 변수를 선언할거에요 -> 높이 보정을
|
2026-02-05 15:42:48 +00:00
|
|
|
|
2026-02-13 09:11:54 +00:00
|
|
|
[Header("애니메이션")] // 인스펙터 제목을 달거에요 -> 애니메이션을
|
|
|
|
|
[SerializeField] private string attackAnim = "Monster_Attack"; // 변수를 선언할거에요 -> 공격 애니를
|
|
|
|
|
[SerializeField] private string walkAnim = "Monster_Walk"; // 변수를 선언할거에요 -> 걷기 애니를
|
2026-02-04 14:06:25 +00:00
|
|
|
|
2026-02-13 09:11:54 +00:00
|
|
|
private float lastAttackTime; // 변수를 선언할거에요 -> 마지막 공격 시간을
|
2026-02-05 15:42:48 +00:00
|
|
|
|
2026-02-13 09:11:54 +00:00
|
|
|
protected override void Init() { if (agent != null) agent.stoppingDistance = attackRange * 0.8f; } // 초기화할거에요 -> 정지 거리를
|
2026-02-05 15:42:48 +00:00
|
|
|
|
2026-02-13 09:11:54 +00:00
|
|
|
protected override void ExecuteAILogic() // 함수를 실행할거에요 -> AI 로직을
|
2026-02-04 14:06:25 +00:00
|
|
|
{
|
2026-02-13 09:11:54 +00:00
|
|
|
if (isHit || isAttacking || isResting) return; // 중단할거에요 -> 행동 불가면
|
|
|
|
|
float dist = Vector3.Distance(transform.position, playerTransform.position); // 계산할거에요 -> 거리를
|
|
|
|
|
if (dist <= attackRange) TryAttack(); else ChasePlayer(); // 분기할거에요 -> 공격 또는 추격으로
|
2026-02-04 14:06:25 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-13 09:11:54 +00:00
|
|
|
private void ChasePlayer() // 함수를 선언할거에요 -> 추격 로직을
|
2026-02-05 15:42:48 +00:00
|
|
|
{
|
2026-02-13 09:11:54 +00:00
|
|
|
if (agent.isOnNavMesh) { agent.isStopped = false; agent.SetDestination(playerTransform.position); } // 이동할거에요 -> 플레이어에게
|
2026-02-19 18:14:55 +00:00
|
|
|
|
|
|
|
|
// ── [Bug 1 수정] 이미 같은 state면 재호출 금지 + 레이어 0 명시 ──
|
|
|
|
|
if (!animator.GetCurrentAnimatorStateInfo(0).IsName(walkAnim)) // 조건이 맞으면 실행할거에요 -> 현재 state가 walkAnim이 아니면
|
|
|
|
|
{
|
|
|
|
|
animator.Play(walkAnim, 0, 0f); // 재생할거에요 -> 걷기 애니를 (레이어 0 명시로 Invalid Layer Index -1 방지)
|
|
|
|
|
}
|
2026-02-05 15:42:48 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-13 09:11:54 +00:00
|
|
|
private void TryAttack() // 함수를 선언할거에요 -> 공격 시도를
|
2026-02-04 14:06:25 +00:00
|
|
|
{
|
2026-02-13 09:11:54 +00:00
|
|
|
if (Time.time < lastAttackTime + attackDelay) return; // 중단할거에요 -> 쿨타임 중이면
|
|
|
|
|
lastAttackTime = Time.time; isAttacking = true; // 갱신할거에요 -> 시간과 상태를
|
|
|
|
|
if (agent.isOnNavMesh) agent.isStopped = true; // 멈출거에요 -> 이동을
|
2026-02-04 14:06:25 +00:00
|
|
|
|
2026-02-13 09:11:54 +00:00
|
|
|
Vector3 dir = (playerTransform.position - transform.position).normalized; dir.y = 0; // 계산할거에요 -> 방향을
|
|
|
|
|
transform.rotation = Quaternion.LookRotation(dir); // 회전할거에요 -> 방향대로
|
2026-02-19 18:14:55 +00:00
|
|
|
animator.Play(attackAnim, 0, 0f); // 재생할거에요 -> 공격 애니를 (레이어 0 명시)
|
2026-02-04 14:06:25 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-13 09:11:54 +00:00
|
|
|
public override void OnAttackStart() // 함수를 실행할거에요 -> 공격 이벤트 발생 시
|
2026-02-04 14:06:25 +00:00
|
|
|
{
|
2026-02-13 09:11:54 +00:00
|
|
|
if (projectilePrefab == null || firePoint == null) return; // 중단할거에요 -> 설정 없으면
|
|
|
|
|
GameObject obj = Instantiate(projectilePrefab, firePoint.position, transform.rotation); // 생성할거에요 -> 투사체를
|
|
|
|
|
Projectile proj = obj.GetComponent<Projectile>(); // 가져올거에요 -> 스크립트를
|
2026-02-04 14:06:25 +00:00
|
|
|
|
2026-02-13 09:11:54 +00:00
|
|
|
if (attackStyle == AttackStyle.Straight) // 조건이 맞으면 실행할거에요 -> 직선형이면
|
2026-02-05 15:42:48 +00:00
|
|
|
{
|
2026-02-13 09:11:54 +00:00
|
|
|
if (proj != null) proj.Initialize(transform.forward, projectileSpeed, attackDamage); // 초기화할거에요 -> 직선 발사로
|
2026-02-05 15:42:48 +00:00
|
|
|
}
|
2026-02-13 09:11:54 +00:00
|
|
|
else // 조건이 틀리면 실행할거에요 -> 곡사형이면
|
2026-02-04 14:06:25 +00:00
|
|
|
{
|
2026-02-13 09:11:54 +00:00
|
|
|
Rigidbody rb = obj.GetComponent<Rigidbody>(); // 가져올거에요 -> 리지드바디를
|
|
|
|
|
if (rb != null)
|
2026-02-04 14:06:25 +00:00
|
|
|
{
|
2026-02-13 09:11:54 +00:00
|
|
|
if (proj != null) proj.Initialize(Vector3.zero, 0, attackDamage); // 초기화할거에요 -> 물리 이동으로
|
|
|
|
|
rb.useGravity = true; // 켤거에요 -> 중력을
|
|
|
|
|
Vector3 targetPos = playerTransform.position + Vector3.up * aimHeightOffset; // 계산할거에요 -> 타겟 위치를
|
|
|
|
|
Vector3 velocity = ProjectileMath.CalculateLobVelocity(firePoint.position, targetPos, launchAngle); // 계산할거에요 -> 헬퍼로 속도를
|
2026-02-05 15:42:48 +00:00
|
|
|
|
2026-02-13 09:11:54 +00:00
|
|
|
if (velocity != Vector3.zero) rb.velocity = velocity; // 적용할거에요 -> 속도를
|
|
|
|
|
else rb.AddForce(transform.forward * 15f + Vector3.up * 5f, ForceMode.Impulse); // 실패 시 기본 힘을
|
2026-02-04 14:06:25 +00:00
|
|
|
}
|
2026-02-05 15:42:48 +00:00
|
|
|
}
|
2026-02-04 14:06:25 +00:00
|
|
|
}
|
2026-02-19 18:14:55 +00:00
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
|
// 디버그 기즈모 오버라이드 — 공격(사격) 범위 추가 표시
|
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
|
protected override void OnDrawGizmosSelected() // 함수를 선언할거에요 -> 기즈모 오버라이드를
|
|
|
|
|
{
|
|
|
|
|
base.OnDrawGizmosSelected(); // 실행할거에요 -> 부모(감지 범위)를 먼저 그리기
|
|
|
|
|
|
|
|
|
|
if (!showDebugGizmos) return; // 중단할거에요 -> 기즈모가 꺼져 있으면
|
|
|
|
|
|
|
|
|
|
// 공격(사격) 범위 — 하늘색 반투명
|
|
|
|
|
Gizmos.color = new Color(0f, 0.7f, 1f, 0.08f); // 설정할거에요 -> 하늘색 반투명 채우기를
|
|
|
|
|
Gizmos.DrawSphere(transform.position, attackRange); // 그릴거에요 -> 사격 범위를
|
|
|
|
|
Gizmos.color = new Color(0f, 0.7f, 1f, 1f); // 설정할거에요 -> 하늘색 외곽선을
|
|
|
|
|
Gizmos.DrawWireSphere(transform.position, attackRange); // 그릴거에요 -> 사격 범위 외곽선을
|
|
|
|
|
|
|
|
|
|
// 발사 지점 → 전방 시선 표시
|
|
|
|
|
if (firePoint != null) // 조건이 맞으면 실행할거에요 -> 발사 위치가 있으면
|
|
|
|
|
{
|
|
|
|
|
Gizmos.color = Color.cyan; // 설정할거에요 -> 시안색을
|
|
|
|
|
Gizmos.DrawLine(firePoint.position, firePoint.position + firePoint.forward * 3f); // 그릴거에요 -> 발사 방향 선을
|
|
|
|
|
Gizmos.DrawSphere(firePoint.position, 0.1f); // 그릴거에요 -> 발사 위치 점을
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|