Mike Cuddy Mike Cuddy - 1 month ago 20
C# Question

SetDestination and active agent/NavMesh issue with Unity Game

I am working in unity 5 on a game and I am currently getting this error:

"SetDestination" can only be called on an active agent that has been placed on a NavMesh.
UnityEngine.NavMeshAgent:SetDestination(Vector3)
EnemyMovement:Update() (at Assets/Scripts/Enemy/EnemyMovement.cs:25)

My code for the enemyMovement script is:

using UnityEngine;
using System.Collections;

public class EnemyMovement : MonoBehaviour
{
Transform player;
PlayerHealth playerHealth;
EnemyHealth enemyHealth;
NavMeshAgent nav;


void Awake ()
{
player = GameObject.FindGameObjectWithTag ("Player").transform;
playerHealth = player.GetComponent <PlayerHealth> ();
enemyHealth = GetComponent <EnemyHealth> ();
nav = GetComponent <NavMeshAgent> ();
}


void Update ()
{
if(enemyHealth.currentHealth > 0 && playerHealth.currentHealth > 0)
{
nav.SetDestination (player.position);
}
else
{
nav.enabled = false;
}
}
}


Now I know that there is almost an identical question here: Unity - "SetDestination" can only be called on an active agent that has been placed on a NavMesh. UnityEngine.NavMeshAgent:SetDestination(Vector3) However, the question does not seem to have a firm response and I have tried doing some of the responses and nothing appears to work. I will say that my game does work fine. However, I have added an ability that if the player presses 'B' then all of the enemies will die on the screen. When I press 'B' the enemies die but the system 'crashes' and I get that error message.

EnemyManager.cs:

public void Kill(int InstanceID)
{
if (EnemyManager.Instance.EnemyList.ContainsKey(InstanceID))
EnemyManager.Instance.EnemyList.Remove(InstanceID);
}

public void DeathToAll()
{
Dictionary<int, Object> TempEnemyList = new Dictionary<int, Object>(EnemyManager.Instance.EnemyList);
foreach (KeyValuePair<int, Object> kvp in TempEnemyList)
{
// kvp.Key; // = InstanceID
// kvp.Value; // = GameObject

GameObject go = (GameObject)kvp.Value;
go.GetComponent<EnemyHealth>().Death();
}

}


Enemyhealth.cs

public void Death ()
{
// The enemy is dead.
isDead = true;

// Turn the collider into a trigger so shots can pass through it.
capsuleCollider.isTrigger = true;

// Tell the animator that the enemy is dead.
anim.SetTrigger ("Dead");

// Change the audio clip of the audio source to the death clip and play it (this will stop the hurt clip playing).
enemyAudio.clip = deathClip;
enemyAudio.Play ();

// Kill the Enemy (remove from EnemyList)
// Get the game object associated with "this" EneymHealth script
// Then get the InstanceID of that game object.
// That is the game object that needs to be killed.
EnemyManager.Instance.Kill(this.gameObject.GetInstanceID());


}

Answer

The problem here is that while you're checking if enemyHealth.currentHealth > 0 before calling nav.SetDestination(), that value doesn't get changed when the enemies are killed by the player's ability. Instead, it seems that the flag isDead is set on the enemy, without affecting the actual health.

To ensure nav.SetDestination() doesn't get called when the enemy is dead (because it seems at that point, the enemy is no longer a valid active agent), just add an additional condition to your if statement:

if(enemyHealth.currentHealth > 0 && !enemyHealth.isDead && playerHealth.currentHealth > 0)
{
    nav.SetDestination (player.position);
}

Hope this helps! Let me know if you have any questions. (You may need to make isDead public for this to work.)

An alternative solution is to make a property IsDead which returns whether enemyHealth.currentHealth > 0, and kill enemies by setting their health to 0. Then you don't need to have a health tracker and a separate isDead flag that must be explicitly set. That's more of a design decision though as it won't affect the functionality.

Comments