using UnityEngine; using UnityEngine.AI; namespace UnityAgent { /// /// AI NPC that wanders between random points and chases the player when /// they enter the aggro radius. Uses Unity's NavMeshAgent. /// [RequireComponent(typeof(NavMeshAgent))] public class NPCController : MonoBehaviour { public enum State { Idle, Wander, Chase } [Header("References")] public Transform player; [Header("Behaviour")] public State initialState = State.Wander; public float wanderRadius = 20f; public float aggroRange = 12f; public float loseSightRange = 20f; public float idleDuration = 2f; public float wanderSpeed = 2.5f; public float chaseSpeed = 5f; [Header("Debug")] public bool drawGizmos = true; private NavMeshAgent _agent; private State _state; private float _stateTimer; private Vector3 _startPos; private void Awake() { _agent = GetComponent(); _agent.speed = wanderSpeed; } private void Start() { _state = initialState; _startPos = transform.position; if (player == null && GameObject.FindGameObjectWithTag("Player") != null) player = GameObject.FindGameObjectWithTag("Player").transform; } private void Update() { switch (_state) { case State.Idle: TickIdle(); break; case State.Wander: TickWander(); break; case State.Chase: TickChase(); break; } } private void TickIdle() { _stateTimer -= Time.deltaTime; if (PlayerInRange(aggroRange)) { EnterChase(); return; } if (_stateTimer <= 0f) EnterWander(); } private void TickWander() { if (PlayerInRange(aggroRange)) { EnterChase(); return; } if (_agent.remainingDistance <= _agent.stoppingDistance && !_agent.pathPending) EnterIdle(); } private void TickChase() { if (player == null) { EnterWander(); return; } _agent.SetDestination(player.position); if (!PlayerInRange(loseSightRange)) EnterWander(); } private void EnterIdle() { _state = State.Idle; _stateTimer = idleDuration; _agent.isStopped = true; } private void EnterWander() { _state = State.Wander; _agent.speed = wanderSpeed; _agent.isStopped = false; Vector3 dir = Random.insideUnitSphere * wanderRadius; dir.y = 0f; Vector3 target = _startPos + dir; if (NavMesh.SamplePosition(target, out NavMeshHit hit, wanderRadius, NavMesh.AllAreas)) _agent.SetDestination(hit.position); } private void EnterChase() { _state = State.Chase; _agent.speed = chaseSpeed; _agent.isStopped = false; } private bool PlayerInRange(float range) { if (player == null) return false; return Vector3.Distance(transform.position, player.position) <= range; } private void OnDrawGizmosSelected() { if (!drawGizmos) return; Gizmos.color = Color.yellow; Gizmos.DrawWireSphere(transform.position, aggroRange); Gizmos.color = Color.red; Gizmos.DrawWireSphere(transform.position, loseSightRange); Gizmos.color = Color.green; Gizmos.DrawWireSphere(_startPos == Vector3.zero ? transform.position : _startPos, wanderRadius); } } }