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