using UnityEngine; // 유니티 기능을 불러올거에요 -> UnityEngine을 public class UniversalRangedMonster : MonsterClass // 클래스를 선언할거에요 -> 원거리 몬스터를 { 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; // 변수를 선언할거에요 -> 속도를 [Header("곡사 설정")] // 인스펙터 제목을 달거에요 -> 곡사 설정을 [SerializeField] private float launchAngle = 45f; // 변수를 선언할거에요 -> 각도를 [SerializeField] private float aimHeightOffset = 1.2f; // 변수를 선언할거에요 -> 높이 보정을 [Header("애니메이션")] // 인스펙터 제목을 달거에요 -> 애니메이션을 [SerializeField] private string attackAnim = "Monster_Attack"; // 변수를 선언할거에요 -> 공격 애니를 [SerializeField] private string walkAnim = "Monster_Walk"; // 변수를 선언할거에요 -> 걷기 애니를 private float lastAttackTime; // 변수를 선언할거에요 -> 마지막 공격 시간을 protected override void Init() { if (agent != null) agent.stoppingDistance = attackRange * 0.8f; } // 초기화할거에요 -> 정지 거리를 protected override void ExecuteAILogic() // 함수를 실행할거에요 -> AI 로직을 { if (isHit || isAttacking || isResting) return; // 중단할거에요 -> 행동 불가면 float dist = Vector3.Distance(transform.position, playerTransform.position); // 계산할거에요 -> 거리를 if (dist <= attackRange) TryAttack(); else ChasePlayer(); // 분기할거에요 -> 공격 또는 추격으로 } private void ChasePlayer() // 함수를 선언할거에요 -> 추격 로직을 { if (agent.isOnNavMesh) { agent.isStopped = false; agent.SetDestination(playerTransform.position); } // 이동할거에요 -> 플레이어에게 // ── [Bug 1 수정] 이미 같은 state면 재호출 금지 + 레이어 0 명시 ── if (!animator.GetCurrentAnimatorStateInfo(0).IsName(walkAnim)) // 조건이 맞으면 실행할거에요 -> 현재 state가 walkAnim이 아니면 { animator.Play(walkAnim, 0, 0f); // 재생할거에요 -> 걷기 애니를 (레이어 0 명시로 Invalid Layer Index -1 방지) } } { Vector3 dir = (playerTransform.position - transform.position).normalized; dir.y = 0; // 계산할거에요 -> 방향을 transform.rotation = Quaternion.LookRotation(dir); // 회전할거에요 -> 방향대로 animator.Play(attackAnim, 0, 0f); // 재생할거에요 -> 공격 애니를 (레이어 0 명시) } public override void OnAttackStart() // 함수를 실행할거에요 -> 공격 이벤트 발생 시 { if (projectilePrefab == null || firePoint == null) return; // 중단할거에요 -> 설정 없으면 GameObject obj = Instantiate(projectilePrefab, firePoint.position, transform.rotation); // 생성할거에요 -> 투사체를 Projectile proj = obj.GetComponent(); // 가져올거에요 -> 스크립트를 if (attackStyle == AttackStyle.Straight) // 조건이 맞으면 실행할거에요 -> 직선형이면 { if (proj != null) proj.Initialize(transform.forward, projectileSpeed, attackDamage); // 초기화할거에요 -> 직선 발사로 } else // 조건이 틀리면 실행할거에요 -> 곡사형이면 { Rigidbody rb = obj.GetComponent(); // 가져올거에요 -> 리지드바디를 if (rb != null) { 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); // 계산할거에요 -> 헬퍼로 속도를 if (velocity != Vector3.zero) rb.velocity = velocity; // 적용할거에요 -> 속도를 else rb.AddForce(transform.forward * 15f + Vector3.up * 5f, ForceMode.Impulse); // 실패 시 기본 힘을 } } } // ───────────────────────────────────────────────────────── // 디버그 기즈모 오버라이드 — 공격(사격) 범위 추가 표시 // ───────────────────────────────────────────────────────── 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); // 그릴거에요 -> 발사 위치 점을 } } }