using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.AI;
// Define the possible states the animal can be in
public enum AnimalState
{
Idle,
Moving,
}
// Ensure the GameObject has a NavMeshAgent component
[RequireComponent(typeof(NavMeshAgent))]
public class Animal : MonoBehaviour
{
[Header("Wander")]
public float wanderDistance = 20f; // Maximum radius to pick a random destination for wandering
public float walkSpeed = 5f; // Speed at which the animal moves when wandering
public float maxWalkTime = 3f; // Maximum time the animal should walk before stopping and idling again
[Header("Idle")]
public float idleTime = 10f; // Base idle time between movements
// Components and state tracking
protected NavMeshAgent navMeshAgent; // Cached reference to the NavMeshAgent
protected AnimalState currentState = AnimalState.Idle; // Current state of the animal
private void Start()
{
InitializeAnimal(); // Setup on start
}
// Initial setup for the animal
protected virtual void InitializeAnimal()
{
navMeshAgent = GetComponent();
navMeshAgent.speed = walkSpeed; // Set movement speed
currentState = AnimalState.Idle; // Start in idle state
UpdateState(); // Begin state logic
}
// Called when the state changes to handle behavior
protected virtual void UpdateState()
{
switch (currentState)
{
case AnimalState.Idle:
HandleIdleState(); // Begin idle behavior
break;
case AnimalState.Moving:
HandleMovingState(); // Begin movement behavior
break;
}
}
// Finds a random valid point on the NavMesh near the given origin
protected Vector3 GetRandomNavMeshPosition(Vector3 origin, float distance)
{
Vector3 randomDirection = Random.insideUnitSphere * distance;
randomDirection += origin;
NavMeshHit navMeshHit;
if (NavMesh.SamplePosition(randomDirection, out navMeshHit, distance, NavMesh.AllAreas))
{
return navMeshHit.position; // Found a valid NavMesh point
}
else
{
return GetRandomNavMeshPosition(origin, distance); // Try again recursively
}
}
// Logic for idle state (wait before wandering)
protected virtual void HandleIdleState()
{
StartCoroutine(WaitToMove());
}
// Coroutine to wait a random amount of time before moving
private IEnumerator WaitToMove()
{
float waitTime = Random.Range(idleTime / 2, idleTime * 2); // Add randomness to idling
yield return new WaitForSeconds(waitTime);
// Choose a new random destination and begin moving
Vector3 randomDestination = GetRandomNavMeshPosition(transform.position, wanderDistance);
navMeshAgent.SetDestination(randomDestination);
SetState(AnimalState.Moving); // Switch to moving state
}
// Logic for moving state (wait until destination reached or timeout)
protected virtual void HandleMovingState()
{
StartCoroutine(WaitToReachDestination());
}
// Coroutine that waits for the animal to reach destination or timeout
private IEnumerator WaitToReachDestination()
{
float startTime = Time.time;
while (navMeshAgent.pathPending || navMeshAgent.remainingDistance > navMeshAgent.stoppingDistance)
{
// If the animal has walked for too long, reset and go idle
if (Time.time - startTime > maxWalkTime)
{
navMeshAgent.ResetPath();
SetState(AnimalState.Idle);
yield break;
}
yield return null; // Wait until the next frame
}
SetState(AnimalState.Idle); // Destination reached successfully
}
// Changes the state only if it's different from the current one
protected void SetState(AnimalState newState)
{
if (currentState == newState)
{
return; // Do nothing if state hasn't changed
}
currentState = newState;
OnStateChanged(newState); // Notify and update logic
}
// Called when the state changes – can be overridden for custom behavior
protected virtual void OnStateChanged(AnimalState newState)
{
UpdateState(); // Trigger behavior for the new state
}
}