Search Unity

  1. Unity 2020.1 has been released.
    Dismiss Notice
  2. We are looking for feedback on the experimental Unity Safe Mode which is aiming to help you resolve compilation errors faster during project startup.
    Dismiss Notice
  3. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice

Help getting animations timed correctly with NavMeshAgent

Discussion in 'Scripting' started by ex0r, Jul 2, 2020.

  1. ex0r

    ex0r

    Joined:
    Nov 9, 2012
    Posts:
    27
    So I am fairly new to Unity, but I have been learning as I go. I am working on a mob AI controller that searches for close players, and if one is close by it starts chasing them. If none are, it goes back to patrolling pre-defined waypoints. There are different animations for chasing and normal patrol.

    After playing around with it for about two days, I finally got the chase and move animations right, but I can't figure out how to get the 'howl' animation to only play once the state of the enemy was changed from 'patrolling' to 'chasing'. The desired effect is that if the mob is patrolling and a target comes within distance, it stands up, howls, and then begins chase, but only does this as long as it wasn't previously chasing. (It must've been patrolling).

    I can't figure out how to setup the animator and the script to allow the howl to trigger only once and complete before starting the "chase" and changing the enemy state.

    Any help would be appreciated.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.AI;
    5. using TMPro;
    6.  
    7.  
    8. // Need to redo all of this. The first thing the mob should do after it spawns, is head to the first waypoint. Then check if a player is close by, and if so set them to the target.
    9. // We also need to build this so that if the monster is currently chasing somebody, they first do the 'Howl' animation, proceeded by the chase animation (which should be a run).
    10. // Once the player gets out of distance from the monster, it goes back to the 'patrol' animation (which should be a walk or crawl)
    11.  
    12. public class mobController : MonoBehaviour
    13. {
    14.  
    15.     public string mobName;
    16.     public GameObject mobNameObject;
    17.     public TMP_Text mobNameText;
    18.     public float chaseDistance;
    19.     public float deathDistance;
    20.  
    21.     private NavMeshAgent agent;
    22.     private Animator animatorController;
    23.  
    24.     private int currentWaypoint = 0;
    25.  
    26.     enum EnemyStates
    27.     {
    28.         Patrolling,
    29.         Chasing
    30.     }
    31.  
    32.     [SerializeField]
    33.     GameObject target;
    34.  
    35.     [SerializeField]
    36.     AudioClip chaseMusic;
    37.  
    38.     [SerializeField]
    39.     Transform[] waypoints;
    40.  
    41.     [SerializeField]
    42.     EnemyStates currentState;
    43.  
    44.     // Start is called before the first frame update
    45.     void Start()
    46.     {
    47.         agent = GetComponent<NavMeshAgent>();
    48.         target = this.gameObject;
    49.         animatorController = GetComponent<Animator>();
    50.     }
    51.  
    52.     // Update is called once per frame
    53.     void Update()
    54.     {
    55.  
    56.         // Loop through every player, see if any are close. If they are, set the mob to chasing. If not, set it to patrolling.
    57.         GameObject[] gos = GameObject.FindGameObjectsWithTag("Player");
    58.         GameObject closest = null;
    59.        
    60.         Vector3 position = transform.position;
    61.  
    62.         float localdistance = Vector3.Distance(target.transform.position, transform.position);
    63.  
    64.         if (localdistance < chaseDistance)
    65.         {
    66.             Debug.Log("You are being chased!");
    67.         }
    68.  
    69.         // Loop through all players, see if the mob is near anyone. If so, and it's not the original target, start chasing them instead.
    70.         foreach (GameObject go in gos)
    71.         {
    72.             float distance = Vector3.Distance(go.transform.position, transform.position);
    73.  
    74.             // This will need updated eventually. Right now, if the player gets above the chase distance, the mob will go back to patrolling.
    75.             // We should probably make this a decision based thing. (Should the mob follow indefinitely, until another target gets closer?
    76.  
    77.             if (distance < chaseDistance)
    78.             {
    79.                 target = go;
    80.  
    81.                 target.GetComponentInChildren<AudioSource>().clip = chaseMusic;
    82.                 if (!target.GetComponentInChildren<AudioSource>().isPlaying)
    83.                 {
    84.                     target.GetComponentInChildren<AudioSource>().Play();
    85.                 }
    86.  
    87.                 // Play the howl animation to start the chase
    88.                 if (currentState != EnemyStates.Chasing)
    89.                 {
    90.                     Debug.Log("Chasing after you");
    91.                     animatorController.SetBool("move", false);
    92.                     animatorController.SetBool("howl", true);
    93.                     StartCoroutine(StopHowl());
    94.  
    95.                     currentState = EnemyStates.Chasing;
    96.                     animatorController.SetBool("chase", true);
    97.  
    98.                 }
    99.             }
    100.             else
    101.             {
    102.                 currentState = EnemyStates.Patrolling;
    103.  
    104.                 if (target != this.gameObject)
    105.                 {
    106.                     target.GetComponentInChildren<AudioSource>().Stop();
    107.                 }
    108.             }
    109.  
    110.             // If the monster is close to a target player, do the death sequence.
    111.             if (distance <= deathDistance)
    112.             {
    113.                 agent.isStopped = true;
    114.                 animatorController.SetBool("move", false);
    115.                 animatorController.SetBool("attack", true);
    116.                 target.GetComponent<CharacterController>().GameOver();
    117.  
    118.                 // Then reset the target to self until next update
    119.                 target = this.gameObject;
    120.                 currentState = EnemyStates.Patrolling;
    121.             }
    122.         }
    123.  
    124.         // If the mob is not chasing anyone, return to waypoint patrolling
    125.         if (Vector3.Distance(transform.position, waypoints[currentWaypoint].position) <= 0.6f)
    126.         {
    127.             Debug.Log("Updating waypoint");
    128.             currentWaypoint++;
    129.  
    130.             if (currentWaypoint == waypoints.Length)
    131.             {
    132.                 currentWaypoint = 0;
    133.             }
    134.         } else
    135.         {
    136.             Debug.Log("Too far away from waypoint");
    137.         }
    138.  
    139.  
    140.         if (currentState == EnemyStates.Chasing)
    141.         {
    142.             agent.SetDestination(target.transform.position);
    143.             animatorController.SetBool("chase", true);
    144.         } else
    145.         {
    146.             agent.SetDestination(waypoints[currentWaypoint].position);
    147.             animatorController.SetBool("chase", false);
    148.             animatorController.SetBool("move", true);
    149.         }
    150.     }
    151.  
    152.     IEnumerator StopHowl()
    153.     {
    154.         yield return new WaitForSeconds(1);
    155.         animatorController.SetBool("howl", false);
    156.     }
    157. }
    158.  
     
  2. UnityFuchs

    UnityFuchs

    Joined:
    Apr 18, 2018
    Posts:
    22
    I haven't used NavMeshAgent yet, but i try to help.
    Line 88: if (currentState != EnemyStates.Chasing)
    Because you put everything in the Update(), this will be called again and again and again and ...
    Line 88: 1. if enemy IS NOT chasing, then
    Line 91: 2. stop move
    Line 92: 3. start howl
    Line 93: 4. stop howl after 1 second
    //Problem: In 1 second, Update() is called like 60 times and so the howl starts 60 times, too.

    Maybe, it is better to stop using Update() for this. You can use the Animator Controller and make transitions from one animation to another.

    transitions example (is this understandable? :D)
    idle-anim + patrol-anim + idle-anim
    patrol-anim + howl-anim + chase-anim + idle-anim + patrol-anim
    idle-anim + howl-anim + chaseanim + idle-anim
     
  3. ex0r

    ex0r

    Joined:
    Nov 9, 2012
    Posts:
    27
    Navmesh moves the entity by itself using setdestination and root animation on the animations. If I do the animations directly from the controller the navmesh agent doesn't move.

    The Howl state itself doesn't even trigger. It transitions from walking straight to chasing animations. It should be going to idle first, then transitioning to Howl, back to idle, then chasing transition.
     
  4. Zer0Cool

    Zer0Cool

    Joined:
    Oct 24, 2014
    Posts:
    141
    If you want to use the root motions of your animatons you shouldnt use the agents SetDestination function. Because in this case the agent will move the transform of your AI and the animation controller will move the transform too, in result quite a mess.
     
  5. ex0r

    ex0r

    Joined:
    Nov 9, 2012
    Posts:
    27
    Isn't Setdestination the method used to control the navmesh agent and make it pathfind correctly? I don't understand how you can't not use setdestination with the navmesh. When I used the non root motion animations with setdestination they never played the mob just slid across the floor in idle state.

    Yeah, just tested it by removing the root motion animation and replacing it with the non root motion version. In the current iteration of code, the mob doesn't even move they just stand in one spot and do the animation in place.
     
    Last edited: Jul 2, 2020
  6. Zer0Cool

    Zer0Cool

    Joined:
    Oct 24, 2014
    Posts:
    141
    Ok my statement was too unclear ;)
    Yes you use the SetDestination for the agent but not use the agent to move your character (but only applies if you want to use root animations, without root animations its not a problem, here you only have to control the speed of the animations)
    .
    Here is it explained more in detail. In short this mixes the nav agent desired position with the movement driven by the root animations:
    https://docs.unity3d.com/Manual/nav-CouplingAnimationAndNavigation.html
     
    Last edited: Jul 2, 2020
  7. ex0r

    ex0r

    Joined:
    Nov 9, 2012
    Posts:
    27
    I ended up figuring out what happened. I had the 'use root motion' box ticked on the animator component. As soon as I removed that, the navmesh started moving again and at the speed I requested it to, too.

    However I need to refactor my code so that it doesn't update the enemy state every second as it's causing problems with the other animations too.
     
unityunity