using System; using UnityEngine; public class DetectionSystem : MonoBehaviour { public enum DetectionStage { Stage0, Stage1, Stage2, Stage3 } [SerializeField] private DetectionStage _dStage = DetectionStage.Stage0; public DetectionStage DStage { get { return _dStage; } private set { _dStage = value; } } public event Action OnDetectionStageChanged; [Header("Target")] [SerializeField] private LayerMask _targetLayers; [SerializeField] private Transform _targetCharacter; // 타겟 캐릭터 [SerializeField] private Vector3 _lastKnownPosition; // 마지막으로 알려진 위치 [SerializeField] private Vector3 _suspiciousPosition; // 의심스러운 위치 [Header("Sight Detection")] [SerializeField] private float _fieldOfViewAngle = 120f; // 시야각 [SerializeField] private float _maxSightDistance = 40f; // 최대 거리 [SerializeField] private float _detectionDistance = 20f; // 감지 거리 [SerializeField] private float _recognitionDistance = 15f; // 인식 거리 [SerializeField] private LayerMask _obstacleLayers; // 장애물 레이어 [Header("Hearing Detection")] [SerializeField] private float _maxAudioDistanse = 25f; // 청각 감지 최대 거리 private bool _isTargetInSight = false; // 타겟이 시야에 있는지 여부 private float _currentTransitionTimer = 0f; private float _targetTransitionDuration = 0f; public Transform TargetCharacter { get => _targetCharacter; set => _targetCharacter = value; } public Vector3 LastKnownPosition { get => _lastKnownPosition; set => _lastKnownPosition = value; } public Vector3 SuspiciousPosition { get => _suspiciousPosition; set => _suspiciousPosition = value; } public bool IsTargetInSight { get => _isTargetInSight; set => _isTargetInSight = value; } private void Awake() { if (TargetCharacter == null) { Collider[] hitColliders = Physics.OverlapSphere(transform.position, _maxSightDistance, _targetLayers); Transform closestTarget = null; float minDistance = Mathf.Infinity; foreach (var hitCollider in hitColliders) { float distance = Vector3.Distance(transform.position, hitCollider.transform.position); if (distance < minDistance) { minDistance = distance; closestTarget = hitCollider.transform; } } if (closestTarget != null) { TargetCharacter = closestTarget; } else { Debug.LogWarning("No target found within sight distance."); } } } private void Update() { SightDetection(); UpdateDetectionStage(); } private void UpdateDetectionStage(DetectionStage newStage) { if (_dStage != newStage) { _dStage = newStage; OnDetectionStageChanged?.Invoke(_dStage); _currentTransitionTimer = 0f; } } private void SightDetection() { _isTargetInSight = false; if (TargetCharacter == null) return; Vector3 directionToTarget = (TargetCharacter.position - transform.position).normalized; float distanceToTarget = Vector3.Distance(TargetCharacter.position, transform.position); float angleToTarget = Vector3.Angle(transform.forward, directionToTarget); if (angleToTarget < _fieldOfViewAngle / 2f) // 나누기 2 하는 이유 : 시야각이 중심 기준 좌로 60/ 우로 60으로 측정했기 떄문임 { if (distanceToTarget <= _maxSightDistance) // 최대 거리 확인 : 역전환 3->2 갈떄 사용 { RaycastHit hit; Vector3 rayOrigin = transform.position + Vector3.up * 0.5f; // 레이캐스팅 필터링을 위해 레이어 2개 사용 (장애물, 타겟) if (Physics.Raycast(rayOrigin, directionToTarget, out hit, distanceToTarget, _obstacleLayers | _targetLayers)) { // 타겟인가? if (hit.transform == TargetCharacter) { IsTargetInSight = true; LastKnownPosition = TargetCharacter.position; // 마지막 본 위치 확인 } else { IsTargetInSight = false; } } else { IsTargetInSight = false; } } } } private void UpdateDetectionStage() { DetectionStage stage = _dStage; _currentTransitionTimer += Time.deltaTime; switch (_dStage) { case DetectionStage.Stage0: // 타겟이라 불릴만한게 최대 거리에 들어왔는가? if (IsTargetInSight && Vector3.Distance(TargetCharacter.position, transform.position) <= _detectionDistance) { if (_currentTransitionTimer >= 0.5f) { UpdateDetectionStage(DetectionStage.Stage1); _currentTransitionTimer = 0f; } } else _currentTransitionTimer = 0f; break; case DetectionStage.Stage1: // 타겟이라 불릴만한게 감지 거리에 들어왔는가? if (IsTargetInSight && Vector3.Distance(TargetCharacter.position, transform.position) <= _detectionDistance) { if (_currentTransitionTimer >= 1f) { UpdateDetectionStage(DetectionStage.Stage2); _currentTransitionTimer = 0f; } } else { // 이제 역전환 조건 추가하기 if (_currentTransitionTimer >= 3f) { UpdateDetectionStage(DetectionStage.Stage0); _currentTransitionTimer = 0f; } } break; case DetectionStage.Stage2: // 타겟이라 불릴만한게 인식 거리에 들어왔는가? if(IsTargetInSight && Vector3.Distance(TargetCharacter.position, transform.position) <= _recognitionDistance) { UpdateDetectionStage(DetectionStage.Stage3); _currentTransitionTimer = 0f; } else { // 이제 역전환 조건 추가하기 if (_currentTransitionTimer >= 5f) { UpdateDetectionStage(DetectionStage.Stage1); _currentTransitionTimer = 0f; } } break; case DetectionStage.Stage3: if (!IsTargetInSight || Vector3.Distance(TargetCharacter.position, transform.position) <= _maxSightDistance) { // 전환 조건이 아닌 역전환 조건만 추가 if (_currentTransitionTimer >= 3f) { UpdateDetectionStage(DetectionStage.Stage2); _currentTransitionTimer = 0f; } } else { // 시야내로 돌아왔다? 타이머 리셋 _currentTransitionTimer = 0f; } break; } } }