Issues when Programming NPC Behaviour with Finite State Machines

Hi,

im working on a basic enemyAI and ive got stuck.

The AI is simple, i use empty gameobjects as waypoints wich my enemy will search using :

waypoints = GameObject.FindGameObjectsWithTag(“EnemyWaypoint”);

the AI got three states, Patrol, Chase and Attack, all three states are working fine but the only problem ive got is when i put another enemy on the scene.

Then both enemys will navigate the same path. Ive tried to make the waypoints array in the Patrol script public so i can drop waypoints to it but i cant get it done.

am i missing something?

And how can i get multiple enemys patrol the same area with different waypoints?

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NPCBaseFinalStateMachine : StateMachineBehaviour {

    public GameObject NPC;
    public UnityEngine.AI.NavMeshAgent agent;
    public GameObject opponent;
    public float speed = 2.0f;
    public float rotationSpeed = 1.0f;
    public float accuracy = 3.0f;

    public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        NPC = animator.gameObject;
        opponent = NPC.GetComponent<EnemyAI>().GetPlayer();
        agent = NPC.GetComponent<UnityEngine.AI.NavMeshAgent>();
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Patrol : NPCBaseFinalStateMachine
{

    public GameObject[] waypoints;
    int currentWP;

    private void Awake()
    {
        waypoints = GameObject.FindGameObjectsWithTag("EnemyWaypoint");

    }

    // OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        base.OnStateEnter(animator, stateInfo, layerIndex);
        agent.enabled = true;
        currentWP = 0;
    }

    // OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
    override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        if (waypoints.Length == 0) return;

        if (Vector3.Distance(waypoints[currentWP].transform.position, NPC.transform.position) < accuracy)
        {
            currentWP++;

            if (currentWP >= waypoints.Length)
            {
                currentWP = 0;
            }
        }

        agent.SetDestination(waypoints[currentWP].transform.position);

        /*

        var direction = waypoints[currentWP].transform.position - NPC.transform.position;

        NPC.transform.rotation = Quaternion.Slerp(NPC.transform.rotation, Quaternion.LookRotation(direction),
            rotationSpeed * Time.deltaTime);

        NPC.transform.Translate(0, 0, Time.deltaTime * speed);
        */
    }

    // OnStateExit is called when a transition ends and the state machine finishes evaluating this state
    override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        agent.enabled = false;
    }
}

Enemy AI Script if needed

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyAI : MonoBehaviour {

    Animator anim;

    public GameObject player;
    public GameObject projectile;
    public GameObject turret;

    public GameObject GetPlayer()
    {
        return player;
    }

    void Fire()
    {
        Instantiate(projectile, turret.transform.position, turret.transform.rotation);
    }
   
    public void StopFiring()
    {
        CancelInvoke("Fire");
    }

    public void StartFiring()
    {
        InvokeRepeating("Fire", 1f, 1f);
    }

    // Use this for initialization
    void Start ()
    {
        anim = GetComponent<Animator>();
    }
   
    // Update is called once per frame
    void Update ()
    {
        anim.SetFloat("distance", Vector3.Distance(transform.position, player.transform.position));
    }
}

just try to shuffle your waypoint’s array. They are always in the same order so your AI follow always the same path.

i was thinking on making the path more random, but my problem is that i dont want the second enemy to patrol on the first enemys “zone”, does that makes sense?

By the same “zone”, you mean a waypoint is patrol by only one AI?

i mean like,

lets say we have a platform which have 10 waypoints.

We also got 2 enemy’s on the same platform,

I want to have one enemy on the left side of the platform patrolling 5 waypoints meanwhile the second enemy is on the right side of the platform patrolling the rest of the five waypoints, without patrolling eachothers side.

what happens now, is that the enemys patrol on eachothers side and i dont want that to happen.

Thanks for quick answers

Ok so a waypoint is not shared between AI. Now it depend if AI spawn during the game or if they are present at the beginning.

-If they spawn at runtime, you have to notify other AI and reasign the waypoints between them.
-If not, you just have to code some kind of manager that split waypoint between them.

The AI are present at the beginning, so i have to code a waypoint manager,

any idea on how i could do it?

Group your waypoints. Add a public variable to each waypoint like “WaypointGroup” and set the group as you place them. So the “WayPointGroup” on one platform might be “Platform1” and the waypoints on another will be “Platform2”. Then set a variable for the EnemyAI to coincide with the group that it should be looking for. Before you set the Navmesh destination, make sure that he only searches the waypoints in the desired group.

I recently posted something similar. I’m using a Commander object to dictate the “role” of each enemy and then each enemy will have it’s own script that determines how they should carry it out. At this point, I’m just trying to figure out how to clean it up so it’s easier to scale. Check it out if you get a chance, maybe you can help me out.

Another option would be to set an empty gameobject (let’s call it “anchor”) in the middle of each platform and set a “MaxDistance” variable and then, before setting the NavMesh destination, check to see if Vector3.Distance(anchor.transform.position, waypoint.transform.position) <= MaxDistance but that would be more complicated than just assigning groups.

pm

You can also apply a Cluster algorithm to divide all the waypoints in N arrays where N is the number of AI. First you create N arrays. You put one random waypoint in each arrays. We consider them as the “center” of the zone. Then you Parse every waypoints and add them to the array they are the closest from (the “center”). When its done, you recalculate the “center” of each zone (average of every arrays’s points). Finaly you parse every waypoints and perform the same operations until you made no changes to your arrays. You just have to assign one array to each of your AI.