129 lines
4.6 KiB
C#
129 lines
4.6 KiB
C#
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<NavMeshAgent>();
|
|
_attackScript = GetComponent<EnemyAttack>();
|
|
|
|
// 인스펙터에서 비어있다면 태그로 자동으로 찾음
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 레이캐스트를 쏴서 전방에 길을 막는 IDamageable(성벽)이 있는지 확인
|
|
/// </summary>
|
|
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<IDamageable>();
|
|
|
|
// 대상이 존재하고, 그 대상이 현재 코어가 아닐 때 (즉, 가로막는 성벽일 때)
|
|
if (damageable != null && hit.collider.transform != _finalTarget)
|
|
{
|
|
_currentTarget = hit.collider.transform;
|
|
|
|
// 공격 스크립트에도 즉시 새로운 타겟을 알려줌
|
|
_attackScript.SetAttackTarget(_currentTarget);
|
|
|
|
// 일단 멈추고 다음 루프에서 사거리 체크를 받도록 함
|
|
_agent.isStopped = true;
|
|
_agent.velocity = Vector3.zero;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 외부(스패너 등)에서 최종 목적지를 설정해줄 때 사용
|
|
/// </summary>
|
|
public void SetFinalTarget(Transform target)
|
|
{
|
|
_finalTarget = target;
|
|
_currentTarget = target;
|
|
}
|
|
} |