I am failing to get "enemies" to target more than one target type

I wanted to add a hunter / prey feature to my project. As a jumping off point I modified the AIController script to create another script, as follows below, to simulate random wandering of game animals. I wanted to also include a method that causes the agent to “flee” from an enemy agent.

Pertinent Information -
*Using Unity 2022.2. Same version throughout.
*Have imported assets that are either open licensed or my own crafting. Insofar
no conflicts have been observed.
*Asset load is a little bloated (culling pending) so editor is starting to bog down a bit.
I haven’t noticed any performance hits from my scene once it is fully compiled and
running but Unity callbacks in the editor and Microsoft VS are going all molasses on
me .
*All involved agents have colliders, are tagged correctly, and have Health scripts. All
prefabs function as intended if assigned the scripts exactly as demonstrated in the
course.

Expected Result -

  • Agent picks a random point as a destination and travels to that point.
  • Agent will nullify its current path and re-path (Flee) if an enemy is within the alert
    range.
  • Agent returns to its random wander behavior when no enemy is detected.

Actual Result -

  • Agent picks a random point as a destination and travels to that point.
  • Agent will nullify its current path and re-path only if the enemy is tagged
    “Player”. Any other tags (including Unity’s other default tags) will return null.
  • Agent returns to its random wander cycle when no enemy is detected.

Several names of functions and variables have been changed to fit with my other, off the course scripts. It should read clearly enough though.

namespace UnitSystem
{
public class WanderingGrazerAI : MonoBehaviour
{
NavMeshAgent agent;
Health health;
Animator animator;
GameObject player;
GameObject predator;
[SerializeField] float walkSpeed = 1.3f;
[SerializeField] float runSpeed = 7;
[SerializeField] float wanderRange;
[SerializeField] float alertRange = 10;
/* The next field sets a position that the agent will return to before moving
again in a different, “random” direction.
Instead of a specified return position you can set this transform to the
transform of the agent to keep the agent moving without boudaries */
[SerializeField] Transform anchor;
[SerializeField] float grazeTime = 3.26f;

    void Awake()
    {
        agent = GetComponent<NavMeshAgent>();
        animator = GetComponent<Animator>();
        player = GameObject.FindWithTag("Player");
        predator = GameObject.FindWithTag("Enemy");
    }

    void Start()
    {
        GameObject[] enemyArray = GameObject.FindGameObjectsWithTag("Enemy");
        Debug.Log("Length: " + enemyArray.Length);
        for (int e = 0; e < enemyArray.Length; e++)
        { Debug.Log(enemyArray[e].name, enemyArray[e]); }

        health = GetComponent<Health>();
    }

    void FixedUpdate()
    {
        agent.enabled = !health.IsDead();

        if (agent.remainingDistance <= agent.stoppingDistance)
        {
            Vector3 point;
            if (RandomPoint(anchor.position, wanderRange, out point))
            { 
                agent.SetDestination(point);
            }
        }
        if (InDanger(player)) { Flee(player); }
        if (InDanger(predator)) { Flee(predator); }

        AnimateMovement();
    }

    bool RandomPoint(Vector3 center, float range, out Vector3 result)
    {
        Vector3 randomPoint = center + Random.insideUnitSphere * range;
        NavMeshHit hit;
        if (NavMesh.SamplePosition(randomPoint, out hit, 1.0f, NavMesh.AllAreas))
        {
            result = hit.position;
            return true;
        }

        result = Vector3.zero;
        return false;
    }

    void AnimateMovement()
    {
        Vector3 localVelocity = transform.InverseTransformDirection(agent.velocity);
        float velocity = localVelocity.z;
        animator.SetFloat("Velocity", velocity);
    }

    void Flee(GameObject enemy)
    {
        Vector3 newRandomPoint = 15f * ((transform.position + Random.insideUnitSphere) * alertRange);

        if (InDanger(enemy))
        {
            Vector3 approachedFromDirection = (transform.position - player.transform.position).normalized;
            agent.speed = runSpeed;
            Vector3 runPosition = approachedFromDirection * -25;
            agent.SetDestination(runPosition);
            animator.SetTrigger("Flee");
        }
        else if (!InDanger(enemy))
        {
            agent.speed = walkSpeed;
            agent.SetDestination(newRandomPoint);
            animator.ResetTrigger("Flee");
        }
    }

    bool InDanger(GameObject enemy)
    {
        float distance = Vector3.Distance(enemy.transform.position, transform.position);
        return distance < alertRange;
    }

    void OnDrawGizmos()
    {
        Gizmos.color = Color.yellow;
        Gizmos.DrawWireSphere(transform.position, alertRange);
    }
}

}

My heart tells me I am shooting somewhere in the same room as the solution. I think I have just been square pegging it. If a more competent person has the time, I would welcome their insight.

Thanks in advance.

EDIT The debug log in the Start method does indeed accurately return an array of enemy units.

1 Like

As the code is written, the agent will flee only if the very first enemy found by the FindWithTag is within range. If you have multiple enemies, only the one will serve as a predator. I’m guessing you’ll want multiple enemies…

For this, you’ll need to keep track of multiple enemies, which means a list of enemies may be in order. this brings with it a few challenges, however…

List<Health> predators = new List<Health>();  

Why Health? So we can tell if they are still alive.

void Awake()
{
    foreach(Health otherHealth in FindGameObjectsOfType<Health>())
    {
          if(otherHealth.gameObject.CompareTag("Enemy") || otherHealth.gameObject.CompareTag("Player"))
          predators.Add(otherHealth);
    }
    //rest of Awake
}

//In FixedUpdate
foreach(Health otherHealth in predators)
{
    if(otherHealth.IsAlive && InDanger(otherHealth.gameObject)) { Flee(otherHealth.gameObject); }
}

Since an enemy or a player can be considered to be a predator, it makes sense to simply combine them into one array of Player.

1 Like

For some reason I wasn’t thinking of cycling through a mass array. I probably had a reason but it wasn’t important enough to remember; like that dangling grazeTime float (but I know where I am going with that one).

Foreach is much cleaner. I am going to give it a go and report when I have something to report.

Thank you.

1 Like

Slicker than a pig’s bottom, Brian! Sending you big sloppy kisses.

PS. FindObjectsOfType instead of FindGameObjectOfType.

1 Like

I would personally not make a list of enemies/predators in Awake. In the InDanger method I’d do a Physics.OverlapSphere and check if there are any enemies/predators in the sphere. I’d also only do that every 10 physics frames or so, but that number depends on the size of the sphere. The smaller the sphere, the more frequent you would need to check, and that would bog down performance a little. You could even hybrid it a little by having a large sphere detect enemies on interval and add those to an ‘alert’ list. Then run through only those in the list more frequently with the above code and a smaller ‘danger’ radius. But if you gonna sphere it, sphere it. I’m sure the physics is far more optimised than our ‘foreach’ would be. There’s no point in checking enemies on the other side of the map. I remember seeing a talk where the people that did F.E.A.R. had a performance issue and eventually discovered that rats on the other side of the map were making decisions on how to behave every frame, even when no-one could see them. It was a GOAP system which is already a little performance heavy, so having rats run their decision making for no reason was a bit rough

1 Like

Performance is co-deity with stability. I am currently sighting in on the latter. Optimization is ever present in my thinking but to focus TOO much on that during this roughout phase, with my limited but maturing skills, my project would more likely than not turn into a spaghetti-pile data labyrinth that not even the bravest of Code Ninjas dare venture into.

But yes… I heartily embrace all avenues to better performance down to even negligible improvements. When the need to do so can no longer be procrastinated, I will embark on the lonely, monotone-voiced quest for software optimization videos.

1 Like

Al good. The post isn’t only aimed at performance, though. Consider spawning new enemies at some point. Those will not be part of the list created in Awake and therefore won’t be checked. Roughing it out is good. I said this somewhere in a different post before (and I got it from someone else);

1 Like

I don’t disagree with any of this. My solution was the “make it work” variety, to change as little code as possible to work with what @Christopher_Emerson had started with.

Ultimately, my approach would likely be to use something like the Targeter from the Third Person course, combined with a component Team which contains a reference to a ScriptableObject Faction (I already use the Faction in my big project).
The Faction contains a List of Factions which are friendly to it (others being hostile)

List<Faction> friendlyFactions; //Factions the AI ignores, or the Player cannot attack 

Any faction not in this list would be considered hostile and attack immediately.

For critters (the guys that are afraid of predators, any faction not in this list would be considered a predator and they would run away.

Then you would only need to distance check the Targets within the Targeter’s range. As @bixarrio stated, that distance check doesn’t need to be every frame, and if nothing is within Targeter, you would need no check. By restricting the number of checks, you also take some wear and tear off of the NavMeshAgent, as it only has to calculate a new path every x frames instead every frame.

2 Likes

Some of your points hit home. The idea of a hunting mechanic supports the scattering of enemies if under heavy fire (AoE spells, stones from a catapult set piece, ect…) that would either be enabled or instantiated during runtime.

I am currently mentally invested in this marathon of a course but my satisfaction with the material and its teachers encourages me to investigate this very same 3rd person course some time down the road.

I don’t like to smear underserved accolades wantonly so when I say you both, Brian and bixarrio, have much to praised for in your patience, professionalism, and perspective; I truly mean it. Those are three good "P"s.

Thank you for your help here and hopefully not too often in the future.

1 Like

I’d like to pass on an accolade to you. Your question was much easier to answer because you included the information I needed to provide at least the start of an answer

  • What are you trying to accomplish?
  • What were your expectations?
  • What was the actual result?
  • Show your code or error messages as needed.

By the way, this question/mechanic intrigued me so much that I will be including it in my tutorial on merging the Third Person Combat and Traversal course into the RPG series project. It’s certainly something I’ve had in mind (in fact, in my current project, the Factions are already in place, so if guards and bandits encounter each other, they’ll try to kill one another). Adding in the concept of running away/hiding is a natural extension.

Back when I used to play WoW WAY too much, I remember being amused (and sometimes annoyed) when a wolf would walk by a critter and kill it. It was a small detail, but it added to the illusion that the characters and beasts in the game were not just things to kill but had their own lives to live.

2 Likes

I’d be lying if I said that I’m not excited about this. I am really, really, really excited, to be fair (and just like Christopher, I’ve been mentally invested in this project for a while as well)

I have one request as well, but I can’t speak about this in public. I’ll shoot you a message, you can have a look, and let me know if it’s possible :slight_smile:

Performance is something I highly recommend you take care of from the start itself, and deal with the bugs as soon as you find them, before it becomes a serious mess down the line. I still have banking performance issues in my game (it’s something I recently discovered (the longer I started playing my own game to find similar issues), that over time, my game literally slows down because of banking, with bigger and bigger frame drops over time when banking (usually it’s solved by restarting the game, but then it comes back over time… I’m sure players won’t appreciate this)), and once I convert the game to third person, it’ll be the first thing I deal with (again…). I’m not sure how to tackle it, but I’m hoping I can find something soon about it (the problem itself is hard to explain through text)

Privacy & Terms