using System.Collections; using UnityEngine; using UnityEngine.AI; [RequireComponent(typeof(NavMeshAgent))] public class EnemyMoveDefault : MonoBehaviour { [Header("Movement Settings")] [SerializeField] private Transform _finalTarget; // 최종 목적지 (Core) [SerializeField] private float _updateInterval = 0.2f; // 경로 갱신 간격 [SerializeField] private float _detectionRange = 1.5f; // 전방 장애물 감지 거리 private Transform _currentTarget; // 현재 추적 중인 대상 private NavMeshAgent _agent; private EnemyAttack _attackScript; private Coroutine _pathUpdateCoroutine; // EnemyAttack 스크립트에서 현재 타겟을 참조하기 위한 프로퍼티 public Transform Target => _currentTarget; void Awake() { _agent = GetComponent(); _attackScript = GetComponent(); // 인스펙터에서 비어있다면 태그로 자동으로 찾음 if (_finalTarget == null) { GameObject core = GameObject.FindWithTag("Core"); // Core 오브젝트에 "Core" 태그를 달아주세요. if (core != null) _finalTarget = core.transform; } _currentTarget = _finalTarget; } void OnEnable() { if (_pathUpdateCoroutine != null) StopCoroutine(_pathUpdateCoroutine); _pathUpdateCoroutine = StartCoroutine(UpdatePathRoutine()); } void OnDisable() { if (_pathUpdateCoroutine != null) StopCoroutine(_pathUpdateCoroutine); } IEnumerator UpdatePathRoutine() { // Agent가 NavMesh에 배치될 시간을 줌 yield return null; WaitForSeconds delay = new WaitForSeconds(_updateInterval); while (true) { // 1. 타겟 유효성 체크: 타겟이 파괴되었으면 다시 코어로 설정 if (_currentTarget == null || !_currentTarget.gameObject.activeInHierarchy) { _currentTarget = _finalTarget; _agent.isStopped = false; } if (_currentTarget != null && _agent.isOnNavMesh) { float distance = Vector3.Distance(transform.position, _currentTarget.position); // 2. 공격 사거리 체크 (EnemyAttack의 사거리 참조) if (distance <= _attackScript.attackRange) { // 사거리 안이면 멈춤 _agent.isStopped = true; _agent.velocity = Vector3.zero; } else { // 3. 사거리 밖이면 전방 장애물(성벽 등) 감지 DetectObstacle(); // 4. 장애물 때문에 멈춘 게 아니라면 목적지로 전진 if (!_agent.isStopped) { _agent.SetDestination(_currentTarget.position); } } } yield return delay; } } /// /// 레이캐스트를 쏴서 전방에 길을 막는 IDamageable(성벽)이 있는지 확인 /// private void DetectObstacle() { RaycastHit hit; // 적의 위치(발밑)보다 약간 위(가슴 높이)에서 전방으로 레이 발사 Vector3 rayOrigin = transform.position + Vector3.up * 1.0f; // Scene 뷰에서 레이를 시각적으로 확인 (디버깅용) Debug.DrawRay(rayOrigin, transform.forward * _detectionRange, Color.yellow); if (Physics.Raycast(rayOrigin, transform.forward, out hit, _detectionRange)) { // 부딪힌 대상에게서 IDamageable 인터페이스 확인 IDamageable damageable = hit.collider.GetComponentInParent(); // 대상이 존재하고, 그 대상이 현재 코어가 아닐 때 (즉, 가로막는 성벽일 때) if (damageable != null && hit.collider.transform != _finalTarget) { _currentTarget = hit.collider.transform; // 공격 스크립트에도 즉시 새로운 타겟을 알려줌 _attackScript.SetAttackTarget(_currentTarget); // 일단 멈추고 다음 루프에서 사거리 체크를 받도록 함 _agent.isStopped = true; _agent.velocity = Vector3.zero; } } } /// /// 외부(스패너 등)에서 최종 목적지를 설정해줄 때 사용 /// public void SetFinalTarget(Transform target) { _finalTarget = target; _currentTarget = target; } }