Moving our Agents

General AI Behaviours

Since we’re mimicking the previous behaviours of the Survival Shooter, our agent should:

  1. Go to the where the player is
  2. Attacking the Player
  3. Stopping Actions Upon Death
  4. Stopping the Agent on Death

Let’s get started with setting up our agent to move.

Note

If you’re using the Asset Store version, the following components can be removed from Zombear, Zombunny, and the Hellaphant prefabs.

1. Moving the Agent

The agent needs to understand where it’s supposed to move and when it should stop moving. In this case, it’s quite simple. Every agent knows where the player, Sleepy Boy, is at any point in time.

Let’s create a Variable in Assets/Scripts/AI/Variables, called Vector3Variable.cs. This will store the player’s position and share that information to all nodes within the Template.

// Vector3Variable.cs
using InitialPrefabs.DaniAI;
using UnityEngine;

// This creates a Variable which stores a Vector3
public class Vector3Variable : GenericVariable<Vector3> { }

When the script is done compiling, head back to the Editor and switch over to the Variables tab in the Inspector. Click the (+) sign and add the Vector3Variable. By default, the Variable’s name will have the class' name. Change the name to “Position” for convenience.

add-a-variable

Our variable stores the player’s position, but it needs to be updated. For this, we create an Observer called GetPlayerPosition.cs. The observer will cache the player within the scene and always update its current position.

// GetPlayerPosition.cs
using InitialPrefabs.DaniAI;
using UnityEngine;

public class GetPlayerPosition : BoolObserver {
    
    public string vector3VariableName = "Position";

    private Vector3Variable playerPosition;
    private Transform player;

    // Use this for initialization
    public override void OnStart () {
        // Get a reference to the variable in DaniAI
        playerPosition = Template.GetVariable<Vector3Variable> (vector3VariableName);

        // Get the reference to the Player
        player = GameObject.FindGameObjectWithTag ("Player").transform;
    }

    // Runs every frame like Unity's Update () call
    public override bool OnObserverUpdate () {
        // Set the player's position
        playerPosition.Value = player.transform.position;

        // For this basic AI, we'll always return true
        return true;
    }
}

Once created inside the editor, create a Decision (we’ll call it Attack Player) and hook it up to our Get Player Position Observer.

Conditions

Our GetPlayerPosition always returns true, likewise our Condition must evaulate that output. Click on the connection and set the Comparison value to True.

condition

We need a final component to create this entire context. We write a simple Move.cs Action Script.

using UnityEngine;
using UnityEngine.AI;

/// <summary>
/// Moves the agent towards a specified position.
/// </summary>
public class Move : Action {

    [Tooltip ("How far should the AI stop before the target destination?")]
    public float stoppingDistance = 1.2f;

    [Header ("Variables")]
    [Tooltip ("What is the name of the Vector3Variable?")]
    public string vector3VariableName = "Position";

    private Vector3Variable position;
    private NavMeshAgent navAgent;

    public override void OnStart () {
        // Get the variable
        position = Template.GetVariable<Vector3Variable> (vector3VariableName);

        // Get a reference to the NavMeshAgent
        navAgent = GetComponent<NavMeshAgent> ();
    }

    public override void OnActionStart () {
        // Ensure that the NavMeshAgent is not stopped and set the destination
        navAgent.isStopped = false;
    }

    public override ActionState OnActionUpdate () {
        // The player is always moving - so the NavMeshAgent needs to update its position
        navAgent.SetDestination (position.Value);

        // If the NavMeshAgent is close enough to the player, succeed the Action
        if (navAgent.hasPath && navAgent.remainingDistance <= stoppingDistance) {
            return ActionState.Success;
        } else {
            // otherwise continue running the Action
            return ActionState.Running;
        }
    }

    public override void OnActionEnd (ActionState state) {
        // Stop the NavMeshAgent and clear the path as long as the NavMeshAgent 
        // is on the NavMesh
        if (navAgent.isOnNavMesh) {
            navAgent.isStopped = true;
            navAgent.ResetPath ();
        }
    }
}

Let’s add Move to our Zombie Brain and begin adding this to our Zombear, Zombunny, and the Hellaphant prefabs.

adding-move

Finally, add the AIBrain component and apply the prefabs!

adding-brains

You should get a behaviour like the following gif.

agent-movements