Practice_Unity/Assets/Scripts/AI/DetectionSystem.cs
SeonKyu_Kim b2e290df76 작업
시야감지 작업 완료
에너미 상태 완료
상태 변환 로직 완료

필요한건 todo 파일 보면서 확인 할 것
Todo
1. 에너미 추가학기 위한 path 하는 작업 하기
2025-09-24 18:06:09 +09:00

214 lines
7.9 KiB
C#

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<DetectionStage> 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;
}
}
}