How to exclude certain navmesh obstacles on runtime?
Hello, I'm trying to make a Tower Defense style game, and I've been stuck on one problem. Before I tell what the problem is, let me explain what I've been working on so far.
I'm scripting for enemies which do NOT follow pre-set path, but actively ATTACK towers near them on their way to the core(Life of the player). There are two types of towers: Attacker and Blocker. Attacker turret, just as what its name implements, attacks enemies within it's attack range. Blocker turret cannot attack enemies, but they can block enemies' path to the core and literally manipulate the battlefield by being deployed on it. I attached navmesh obstacle object component to towers(both blocker and attacker) and set the carve option on.
And here comes my problem. When the blocker turrets are already deployed on the battlefield, the enemies(they have navmesh agent component) look for the shortest path to the core(which is not what I intended). I'd rather want them to act like if they didn't know what's up ahead , and recognize turrets' navmesh obstacle component only when they get close enough to the turrets(in the search radius). For this, enemy should only recognize navmesh obstacles of turrets within its search radius, and ignore those that are outside of its search radius.
There is also a priority system(Enemies prioritize Attacker turrets than Blocker turrets), and I mention this for you to understand the script ahead better. please ignore debug.logs, I added them to keep track on the script. This script is attached to the enemy, and I hasn't added attack system of enemy.
private Transform core; //transform of the core
private NavMeshAgent agent; //navmeshagent of this enemy object
private Transform currentTarget, tr;
private float distanceToTarget;
private List<Transform> turretsInRange = new List<Transform>();
private float distance;
void Start()
{
agent = this.transform.parent.gameObject.GetComponent<NavMeshAgent>();
tr = GetComponent<Transform>();
core = GameObject.FindGameObjectWithTag("CORE").transform;
SetTarget(currentTarget = core.transform);
// at the begining, currentTarget is set to be the core
StartCoroutine("Scan"); // Scans nearby turrets every 0.2 seconds
}
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Debug.Log("$$$$$$$$$$$$$$$$$$$");
Debug.Log(currentTarget.tag.ToString() + " " +
GetRemainingDistance(currentTarget).ToString());
}
}
void SetTarget(Transform targetPoint) // sets destination of navmesh agent
{
Debug.Log("Target ReSet");
agent.SetDestination(targetPoint.position);
Debug.Log(currentTarget.tag.ToString() + " " +
GetRemainingDistance(currentTarget).ToString());
//GetRemainingDistance method returns the path length between
this enemy and the transform sent as parameter
}
void SearchTarget(bool hasAttacker)
{
Debug.Log("has Attacker " + hasAttacker.ToString());
float distanceToPotentialTarget, distanceToCurrentTarget;
float minDistanceToPotentialTarget = Mathf.Infinity;
Transform minPotentialTarget = currentTarget;
distanceToCurrentTarget = GetRemainingDistance(currentTarget);
if (hasAttacker && currentTarget.tag != "ATTACKER")
// if attacker is nearby, reset the distance to current target to infinity
distanceToCurrentTarget = Mathf.Infinity;
if (turretsInRange.Count != 0)
{
foreach (Transform potentialTarget in turretsInRange)
{
//Debug.Log(turretsInRange.Count.ToString() +
" distance to potential target" + distanceToPotentialTarget.ToString());
if ((!hasAttacker && potentialTarget.tag == "BLOCKER")
|| potentialTarget.tag == "ATTACKER")
{
Debug.Log(potentialTarget.tag);
distanceToPotentialTarget = GetRemainingDistance(potentialTarget);
if (distanceToPotentialTarget < minDistanceToPotentialTarget)
// finds the most closest turret
{
minDistanceToPotentialTarget = distanceToPotentialTarget;
minPotentialTarget = potentialTarget;
}
}
}
//Debug.Log(minDistanceToPotentialTarget.ToString());
//Debug.Log(GetRemainingDistance(currentTarget).ToString());
if (minDistanceToPotentialTarget < distanceToCurrentTarget)
// if there's any new turret closer than the current target-
// which was found 0.2s before, reset the destination of navmesh agent
SetTarget(currentTarget = minPotentialTarget);
}
else // if there's no turrets in search radius, set the destination to core
{
Debug.Log("Destination set to Core");
if (currentTarget != core)
SetTarget(currentTarget = core);
}
}
IEnumerator Scan()
{
bool hasAttacker = false; // this is for prioritizing system
while (true)
{
Collider[] turretList = Physics.OverlapSphere(tr.position, 5f);
// used physics.overlap instead of trigger because trigger didn't work out quite well...
turretsInRange.Clear();
// puts transform of all turrets nearby after cleaning up the turretsinrange list
foreach (Collider oneTurret in turretList)
{
if (oneTurret.gameObject != null)
if ((oneTurret.tag == "ATTACKER" || oneTurret.tag == "BLOCKER") &&
GetRemainingDistance(oneTurret.transform) <= 15f) // 15 is search radius
{
if (oneTurret.tag == "ATTACKER")
hasAttacker = true;
turretsInRange.Add(oneTurret.transform);
}
}
SearchTarget(hasAttacker);
hasAttacker = false;
yield return new WaitForSeconds(0.2f);
}
}
float GetRemainingDistance(Transform target)
// made this method because navmesh.remainingdistance doesn't work with many corners
{
NavMeshPath path = new NavMeshPath();
if (target == null)
return Mathf.Infinity;
NavMesh.CalculatePath(tr.position, target.position, NavMesh.AllAreas, path);
if (path.status == NavMeshPathStatus.PathPartial || path.corners.Length == 0)
return Mathf.Infinity;
Debug.Log("corners!!!!!!!!");
Debug.Log(path.corners.Length.ToString());
Vector3 previousCorner = path.corners[0];
float lengthSoFar = 0.0F;
int i = 1;
while (i < path.corners.Length)
{
Vector3 currentCorner = path.corners[i];
lengthSoFar += Vector3.Distance(previousCorner, currentCorner);
previousCorner = currentCorner;
i++;
}
return lengthSoFar;
}
Wonder if I shouldn't have used navmesh at all for this? If there's any improvement that can be made, please let me know.
I think your question is a bit TL:DR. Perhaps try to make it more concise and on-point?
Answer by Nevermore-Games · Aug 06, 2018 at 04:39 PM
Hey, hope this is still relevant- just turn carve off. Carve is what's causing the obstacles to pathfind around the towers to begin with. Without carve, your units will only compensate for the obstacle when they're close to it.
If this doesn't work, you're going to have to write your own A* algorithm. While Unity's pathfinding is certainly potent, it isn't going to handle every edge case perfectly. This is one of the risks of using a prebuilt system- sometimes the way it implements a system is not going to perfectly suit your needs. Hope that helps!
Answer by Harinezumi · Feb 06, 2018 at 11:58 AM
I think your game idea is very interesting, and you definitely need pathfinding for it, but you might need to change how you use it (and maybe Unity's default navmesh is not enough). The reason I say this is that the NavmeshAgents have global information of the environment at all time, which means they will choose a new path as soon as something changes, e.g. when you put down an obstacle, while what you want is a more "natural" exploring behaviour (which happens when you don't have global information), more like that of a real AI.
One possible approach is that you set the destination of your enemies at the beginning, and then only set it again when they "see" an obstacle, but I'm not sure if the path is not invalidated once a NavmeshObstacle has been added. So another approach is that your enemies don't go directly to the core, but decide on a direction based on an algorithm and update their destination only when they get there or when there is a new obstacle. How this direction is decided depends on you, but it could be anything from general direction of the core (as if they saw it from the distance over the walls), or to closest corner of a wall, or even random.
And finally, some resources for navmeshes:
- NavMeshComponents: Unity's experimental navmesh "extension"
- A* Project: amazing navmesh implementation - even the free version might be enough
- Introduction to A*: a really good, in-depth A* pathfinding explanation by a Stanford professor. Good for having an idea what is going on with navmeshes, or maybe implement your own. The later chapters talk about exploring behaviour as well, maybe that's what interests you the most