Untitled

 avatar
unknown
plain_text
a month ago
17 kB
2
Indexable
// This namespace gives access to Unity's basic classes like MonoBehaviour, Transform, Vector3, Time, and Gizmos.
using UnityEngine;

// This namespace gives access to Unity's AI Navigation system, including NavMeshAgent.
using UnityEngine.AI;

// This makes sure the GameObject has a NavMeshAgent.
// If the GameObject does not have one, Unity will automatically add it.
[RequireComponent(typeof(NavMeshAgent))]

// This makes sure the GameObject has an Animator component.
// The Animator is needed for Idle, Walk, and Run animations.
[RequireComponent(typeof(Animator))]
public class EnemyNavMeshAI : MonoBehaviour
{
    // This enum stores the possible behavior states of the enemy.
    // Idle means the enemy is not moving.
    // Patrol means the enemy walks between patrol points.
    // Chase means the enemy runs after the player.
    private enum EnemyState
    {
        Idle,
        Patrol,
        Chase
    }

    // -----------------------------
    // REFERENCES
    // -----------------------------

    [Header("References")]

    // This is the player that the enemy will chase.
    // You can assign this manually in the Inspector.
    [SerializeField] private Transform player;

    // These are the patrol points where the enemy will walk.
    // Create empty GameObjects in the scene and assign them here.
    [SerializeField] private Transform[] patrolPoints;

    // -----------------------------
    // DETECTION SETTINGS
    // -----------------------------

    [Header("Detection")]

    // If the player is inside this range, the enemy starts chasing.
    [SerializeField] private float chaseRange = 10f;

    // If the player becomes farther than this range, the enemy stops chasing.
    // This should be bigger than chaseRange to prevent rapid switching.
    [SerializeField] private float loseRange = 16f;

    // -----------------------------
    // MOVEMENT SETTINGS
    // -----------------------------

    [Header("Movement")]

    // The speed of the enemy while patrolling.
    [SerializeField] private float walkSpeed = 2f;

    // The speed of the enemy while chasing.
    [SerializeField] private float runSpeed = 5f;

    // How close the enemy can get to a patrol point before stopping.
    [SerializeField] private float patrolStoppingDistance = 0.2f;

    // How close the enemy can get to the player before stopping.
    // Useful if you later add attack behavior.
    [SerializeField] private float chaseStoppingDistance = 1.8f;

    // Extra distance check for deciding whether the patrol point has been reached.
    [SerializeField] private float waypointReachDistance = 0.4f;

    // How long the enemy waits at each patrol point.
    [SerializeField] private float waitTimeAtWaypoint = 1.5f;

    // If true, the enemy chooses random patrol points.
    // If false, the enemy follows the patrol points in order.
    [SerializeField] private bool randomPatrol = false;

    // -----------------------------
    // ANIMATION SETTINGS
    // -----------------------------

    [Header("Animation")]

    // This must match the float parameter name inside the Animator Controller.
    // In this lesson, the Animator parameter is named "Speed".
    [SerializeField] private string speedParameter = "Speed";

    // This smooths the animation value so the transition does not look too sudden.
    [SerializeField] private float animationDampTime = 0.1f;

    // -----------------------------
    // ROTATION SETTINGS
    // -----------------------------

    [Header("Rotation")]

    // How quickly the enemy rotates to face the player when close.
    [SerializeField] private float facePlayerSpeed = 8f;

    // -----------------------------
    // PRIVATE VARIABLES
    // -----------------------------

    // Reference to the NavMeshAgent component.
    private NavMeshAgent agent;

    // Reference to the Animator component.
    private Animator animator;

    // Stores the enemy's current behavior state.
    private EnemyState currentState;

    // Used to allow the first state to initialize correctly.
    private bool stateInitialized;

    // Stores which patrol point the enemy is currently moving toward.
    private int patrolIndex;

    // Counts how long the enemy has waited at a patrol point.
    private float waitTimer;

    // This property checks if the patrolPoints array exists and has at least one patrol point.
    private bool HasPatrolPoints
    {
        get
        {
            return patrolPoints != null && patrolPoints.Length > 0;
        }
    }

    // Awake runs before Start.
    // We use Awake to get component references.
    private void Awake()
    {
        // Get the NavMeshAgent attached to this enemy.
        agent = GetComponent<NavMeshAgent>();

        // Get the Animator attached to this enemy.
        animator = GetComponent<Animator>();

        // If the player was not assigned in the Inspector,
        // try to find a GameObject with the tag "Player".
        if (player == null)
        {
            GameObject playerObject = GameObject.FindGameObjectWithTag("Player");

            // If a tagged Player object was found, store its Transform.
            if (playerObject != null)
            {
                player = playerObject.transform;
            }
        }
    }

    // Start runs once before the first frame update.
    private void Start()
    {
        // If patrol points exist, start the enemy in Patrol state.
        if (HasPatrolPoints)
        {
            ChangeState(EnemyState.Patrol);
        }
        // If no patrol points exist, the enemy will stay idle.
        else
        {
            ChangeState(EnemyState.Idle);
        }
    }

    // Update runs once every frame.
    private void Update()
    {
        // First, check if the player is close enough to chase
        // or far enough to stop chasing.
        CheckForPlayer();

        // Run behavior depending on the current state.
        switch (currentState)
        {
            case EnemyState.Idle:
                UpdateIdle();
                break;

            case EnemyState.Patrol:
                UpdatePatrol();
                break;

            case EnemyState.Chase:
                UpdateChase();
                break;
        }

        // Update the animation after movement behavior is processed.
        UpdateAnimations();
    }

    // This method checks the player's distance from the enemy.
    private void CheckForPlayer()
    {
        // If there is no player reference, do nothing.
        if (player == null)
        {
            return;
        }

        // Measure the distance between the enemy and the player.
        float distanceToPlayer = Vector3.Distance(transform.position, player.position);

        // If the enemy is not already chasing and the player is near,
        // switch to Chase state.
        if (currentState != EnemyState.Chase && distanceToPlayer <= chaseRange)
        {
            ChangeState(EnemyState.Chase);
        }
        // If the enemy is chasing but the player gets too far,
        // return to Patrol or Idle.
        else if (currentState == EnemyState.Chase && distanceToPlayer >= loseRange)
        {
            // If patrol points exist, go back to patrolling.
            if (HasPatrolPoints)
            {
                ChangeState(EnemyState.Patrol);
            }
            // If no patrol points exist, go back to idle.
            else
            {
                ChangeState(EnemyState.Idle);
            }
        }
    }

    // This method changes the enemy's current state.
    private void ChangeState(EnemyState newState)
    {
        // If the state was already initialized and the enemy is already in this state,
        // do not restart the same state again.
        if (stateInitialized && currentState == newState)
        {
            return;
        }

        // Mark that at least one state has now been initialized.
        stateInitialized = true;

        // Store the new state.
        currentState = newState;

        // Run the correct "Enter" method for the new state.
        switch (currentState)
        {
            case EnemyState.Idle:
                EnterIdle();
                break;

            case EnemyState.Patrol:
                EnterPatrol();
                break;

            case EnemyState.Chase:
                EnterChase();
                break;
        }
    }

    // -----------------------------
    // IDLE STATE
    // -----------------------------

    // This method runs once when the enemy enters Idle state.
    private void EnterIdle()
    {
        // Stop the NavMeshAgent from moving.
        agent.isStopped = true;

        // Clear the current path.
        agent.ResetPath();

        // Reset the waiting timer.
        waitTimer = 0f;
    }

    // This method runs every frame while the enemy is in Idle state.
    private void UpdateIdle()
    {
        // The enemy stays still here.
        // The CheckForPlayer method will switch it to Chase if the player gets close.
    }

    // -----------------------------
    // PATROL STATE
    // -----------------------------

    // This method runs once when the enemy enters Patrol state.
    private void EnterPatrol()
    {
        // If there are no patrol points, switch to Idle instead.
        if (!HasPatrolPoints)
        {
            ChangeState(EnemyState.Idle);
            return;
        }

        // Allow the NavMeshAgent to move.
        agent.isStopped = false;

        // Set the agent speed to walking speed.
        agent.speed = walkSpeed;

        // Set how close the agent should get to the patrol point.
        agent.stoppingDistance = patrolStoppingDistance;

        // Reset the wait timer.
        waitTimer = 0f;

        // Move toward the current patrol point.
        SetCurrentPatrolDestination();
    }

    // This method runs every frame while the enemy is patrolling.
    private void UpdatePatrol()
    {
        // If patrol points were removed, switch to Idle.
        if (!HasPatrolPoints)
        {
            ChangeState(EnemyState.Idle);
            return;
        }

        // If the enemy has not reached the destination yet, keep moving.
        if (!ReachedDestination())
        {
            return;
        }

        // If the enemy reached the patrol point, stop moving.
        agent.isStopped = true;

        // Count how long the enemy has waited.
        waitTimer += Time.deltaTime;

        // If the enemy has waited long enough, choose the next patrol point.
        if (waitTimer >= waitTimeAtWaypoint)
        {
            // Reset the wait timer.
            waitTimer = 0f;

            // Choose the next patrol point.
            ChooseNextPatrolPoint();

            // Allow the enemy to move again.
            agent.isStopped = false;

            // Send the enemy to the new patrol point.
            SetCurrentPatrolDestination();
        }
    }

    // -----------------------------
    // CHASE STATE
    // -----------------------------

    // This method runs once when the enemy enters Chase state.
    private void EnterChase()
    {
        // Allow the NavMeshAgent to move.
        agent.isStopped = false;

        // Set the agent speed to running speed.
        agent.speed = runSpeed;

        // Set how close the enemy should get to the player.
        agent.stoppingDistance = chaseStoppingDistance;

        // Reset the wait timer because chasing does not use patrol waiting.
        waitTimer = 0f;
    }

    // This method runs every frame while the enemy is chasing.
    private void UpdateChase()
    {
        // If the player no longer exists, return to Patrol or Idle.
        if (player == null)
        {
            if (HasPatrolPoints)
            {
                ChangeState(EnemyState.Patrol);
            }
            else
            {
                ChangeState(EnemyState.Idle);
            }

            return;
        }

        // Measure the distance between the enemy and the player.
        float distanceToPlayer = Vector3.Distance(transform.position, player.position);

        // If the enemy is close enough to the player, stop moving.
        if (distanceToPlayer <= chaseStoppingDistance)
        {
            // Stop the agent.
            agent.isStopped = true;

            // Clear the current path.
            agent.ResetPath();

            // Face the player.
            FaceTarget(player.position);
        }
        // If the player is still far enough, keep chasing.
        else
        {
            // Make sure the agent is allowed to move.
            agent.isStopped = false;

            // Set the player's position as the destination.
            agent.SetDestination(player.position);
        }
    }

    // -----------------------------
    // DESTINATION CHECKING
    // -----------------------------

    // This method checks if the NavMeshAgent has reached its current destination.
    private bool ReachedDestination()
    {
        // If the path is still being calculated, the destination is not reached yet.
        if (agent.pathPending)
        {
            return false;
        }

        // Sometimes remainingDistance can be infinity while the path is unknown.
        // If that happens, do not count it as reached.
        if (agent.remainingDistance == Mathf.Infinity)
        {
            return false;
        }

        // Use whichever value is bigger:
        // the agent's stopping distance or our custom waypoint reach distance.
        float reachDistance = Mathf.Max(agent.stoppingDistance, waypointReachDistance);

        // If the remaining distance is small enough, the destination is reached.
        return agent.remainingDistance <= reachDistance;
    }

    // This method sends the enemy to the current patrol point.
    private void SetCurrentPatrolDestination()
    {
        // If there are no patrol points, do nothing.
        if (!HasPatrolPoints)
        {
            return;
        }

        // Get the patrol point at the current index.
        Transform point = patrolPoints[patrolIndex];

        // If this patrol point is missing, choose another one.
        if (point == null)
        {
            ChooseNextPatrolPoint();
            point = patrolPoints[patrolIndex];
        }

        // If the patrol point is valid, set it as the agent's destination.
        if (point != null)
        {
            agent.SetDestination(point.position);
        }
    }

    // This method chooses the next patrol point.
    private void ChooseNextPatrolPoint()
    {
        // If there are no patrol points, do nothing.
        if (!HasPatrolPoints)
        {
            return;
        }

        // If random patrol is enabled and there is more than one patrol point,
        // choose a random patrol point.
        if (randomPatrol && patrolPoints.Length > 1)
        {
            // Store the current index first.
            int nextIndex = patrolIndex;

            // Keep choosing until the next point is different from the current point.
            while (nextIndex == patrolIndex)
            {
                nextIndex = Random.Range(0, patrolPoints.Length);
            }

            // Set the new random patrol index.
            patrolIndex = nextIndex;
        }
        // If random patrol is disabled, go to the next patrol point in order.
        else
        {
            // Move to the next patrol point.
            patrolIndex++;

            // If the patrol index goes past the array length,
            // reset it back to the first patrol point.
            if (patrolIndex >= patrolPoints.Length)
            {
                patrolIndex = 0;
            }
        }
    }

 

    // -----------------------------
    // GIZMOS
    // -----------------------------

    // This draws visual detection ranges in the Scene view when the enemy is selected.
    private void OnDrawGizmosSelected()
    {
        // Draw the chase range using yellow.
        Gizmos.color = Color.yellow;
        Gizmos.DrawWireSphere(transform.position, chaseRange);

        // Draw the lose range using red.
        Gizmos.color = Color.red;
        Gizmos.DrawWireSphere(transform.position, loseRange);
    }
}
Editor is loading...
Leave a Comment