시야감지 작업 완료 에너미 상태 완료 상태 변환 로직 완료 필요한건 todo 파일 보면서 확인 할 것 Todo 1. 에너미 추가학기 위한 path 하는 작업 하기
214 lines
7.9 KiB
C#
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;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
}
|