Practice_Unity/Assets/Scripts/AI/DetectionSystem.cs
Seonkyu.kim ccc9d46df7 작업
1. 이동시 Rigdoby와 Nav 충돌나는거 수정
2. 데이터 매니저에서 데이터 받아오는것에 대한 방식을 대폭 수정
3. 스폰 컨트롤러 방식 조금 수정
4. 벽과 플레이어 캐릭터 충돌시 미끄러지는 기능 추가(다른 오브젝트와는 테스트 안해 봄)

- 캐릭터 컨트롤러에 스테이터스 하는거 손 봐야 함
2025-09-25 18:00:21 +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;
}
}
}