2
\$\begingroup\$

I want my enemies to follow the player, stop at a certain distance and when enemy is within that distance he will walk away.

[SerializeField] Transform target;
[SerializeField] float followRange = 5f;
[SerializeField] float fleeRange = 2f;

NavMeshAgent navMeshAgent;
float distToTarget = Mathf.Infinity;

public Animator anim;
[SerializeField] bool hasTarget = false;
[SerializeField] bool hasDanger = false;

void Start()
{
    navMeshAgent = GetComponent<NavMeshAgent>();
}

void Update()
{
    distToTarget = Vector3.Distance(target.position, transform.position);

    if(distToTarget <= followRange && distToTarget >= (fleeRange - 1f))
    {
        hasTarget = true;
    }
    else
    {
        hasTarget = false;
    }

    if(distToTarget <= fleeRange)
    {
        hasDanger = true;
    }
    else
    {
        hasDanger = false;
    }

    if(hasTarget && hasDanger)
    {
        navMeshAgent.SetDestination(transform.position);
    }

    HasTarget();
    HasDanger();
}

void HasTarget()
{
if(hasTarget)
    {
        navMeshAgent.SetDestination(new Vector3 (target.position.x, transform.position.y, target.position.z));
        anim.SetInteger("Status_walk", 1);
    }
    else
    {
        navMeshAgent.SetDestination(transform.position);
        anim.SetInteger("Status_walk", 0);
    }
if(hasDanger){return;}
}

void HasDanger()
{
if(hasDanger)
    {
    Vector3 toTarget = target.position - transform.position;
    if(Vector3.Distance(target.position, transform.position) < fleeRange)
    {
        Vector3 targetPosition = toTarget.normalized * -fleeRange;
        navMeshAgent.SetDestination(targetPosition);
    }
    }
if(hasTarget){return;}
}

This code kinda works, but they just walk up to the target, walk away, walk back and so on.

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

The NavMeshAgent actually has a property stoppingDistance that allows you to control the radius around the destination which will cause the agent to consider the target "reached". Unfortunately there is no good way to detect when a NavMeshAgent reached its destination and then react to this event. You can only check if its moving at all, but it could also have stopped because it can't find a valid path.

One simple pattern for implementing AI behaviors in games is the state-machine. A simple way to implement one is to have:

  1. An enum with constants representing each state the AI actor can be in.
  2. A variable of that type that holds the current state.
  3. a switch/case tree in the Update method which causes the object to behaves differently depending on the current state.

Here is a (abridged and simplified) example from my current project:

public class HunterAI : MonoBehaviour {

    private enum State {
        IDLE,
        CHASING_PREY,
        RETURNING_TO_START
    }
    private State currentState = State.IDLE;

    private NavMeshAgent myNavMeshAgent;
    private Vector3 startLocation;

    private Prey chasedPrey;
    [SerializeField] private float preyDetectRadius = 5f;
    [SerializeField] private float preyInteractionRadius = 1f;

    

    private void Start() {
        myNavMeshAgent = GetComponent<NavMeshAgent>();
        startLocation = transform.position;
    }

    private void Update() {

        switch (currentState) {
            case State.IDLE:
                myNavMeshAgent.destination = startLocation;
                // look for the prey
                var objectsAround = Physics.OverlapSphere(transform.position, preyDetectRadius);
                foreach (var o in objectsAround) {
                    if (o.TryGetComponent<Prey>(out chasedPrey)) {
                        currentState = State.CHASING_PREY;
                        break;
                    }
                }
                break;
            case State.CHASING_PREY:
                // navigate to prey
                myNavMeshAgent.destination = chasedPrey.transform.position;
                // check if caught
                
                if (myNavMeshAgent.remainingDistance <= preyInteractionRadius) {
                    currentState = State.RETURNING_TO_START;
                }
                break;
            case State.RETURNING_TO_START:
                myNavMeshAgent.destination = startLocation;
                if(myNavMeshAgent.remainingDistance <= 0f) {
                    currentState = State.IDLE;
                }
                break;                    
        }            
    }
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .