DialogueAggro issue FIXED [ALMOST...]

Ahh, this one is not a question, rather it’s a short documentary. If you’ve been secretly spying on my posts for now, you may have noticed that I had a serious problem with my AggroGroup recently, and it’s the fact that for god knows what reason, it wasn’t working properly for a while. After a few hours of extreme investigation, I figured out why. Here’s what made it work for me extremely well:

  1. Ensure your ‘Max Nav Path Length’ in your ‘Mover.cs’ script is set to a high enough value, which will allow your guards to actually be able to catch up with you (if the guy is too far, he won’t be able to reach you. At that point, he’ll just ignore you. If your enemies will be called from a very far distance from you, you’ll absolutely want to crank this one up!)
  2. Inject this block of code in your ‘AggroGroup.Activate()’ method, under the ‘if’ statement that gets your fighters’ CombatTarget component (AFTER THE ‘if’ BLOCK, NOT INSIDE IT). Don’t forget to delete the debuggers, unless you want to improve on this code:
if (fighter.TryGetComponent(out NavMeshAgent navMeshAgent))
                {

                    navMeshAgent.enabled = shouldActivate;

                    if (shouldActivate)
                    {
                        Transform playerTransform = GameObject.FindGameObjectWithTag("Player").transform;
                        Debug.Log($"Setting destination to: {playerTransform.position}");
                        navMeshAgent.SetDestination(playerTransform.position);
                        Debug.Log($"{navMeshAgent.SetDestination(playerTransform.position)}");
                        navMeshAgent.isStopped = false;
                        fighter.GetComponent<Mover>().MoveTo(playerTransform.position, 1.0f);
                        Debug.Log("Fighter coming at you");

                    }

                }
  1. Inject a single line into your ‘AggroGroup.RemoveFighterFromGroup()’ and ‘void Start()’ (PLACE IT AT THE TOP OF YOUR Start() function, else it won’t work!) function:
            // Remove null or destroyed fighters from the list
            fighters.RemoveAll(fighter => fighter == null || fighter.IsDestroyed());

This ensures you have no null fighters in your group. If your group is initially crowded with empty fighters, your aggroGroup simply won’t work!

  1. Put this on top of your foreach loop in ‘AggroGroup.Activate()’:
                if (fighter == null) {

                    Debug.Log("Null fighter found");
                    return;

                }

It’s a safety check to ensure you don’t accidentally throw Null fighters into your AggroGroup (again, another safety check to ensure the group works properly).

But I do have a severe problem. When exporting the game from the engine, this just doesn’t work for some reason… My guess is, it has something to do with the fighters list

If you’re running your game in Development mode, you can read the log messages in Player.Log (warning, these are verbose).

Safe to say I went there and went “what’s going on here”? I’m really baffled…

Just like when in Unity and you can read the Debug.Logs to get an idea what’s happening, you can also read the Player Log, and look for the relative Debug.Logs.

The difference is that in the Player Log there are many entries you don’t find in the regular log. And, of course, the logs have full stack traces.

I’ll give this a test when my mind cools down (severe day of debugging…). Frankly speaking though, I am 99% sure it’s because the null fighter list does not get cleaned. I can’t, for the life of me, figure out how to code my empty list to be cleaned out. If you can help me, that would be amazing

Edit: it works now (and yes, it was the fighters’ list full of null characters), but with a catch. Remember the issue we had with the quest events system, where we had to hit the ‘quit’ button twice to quit the dialogue because an event was being called? Yup, that quit button issue is back now :slight_smile: - the code at the top of my ‘AggroGroup.Activate()’ is now as follows (it’s the closest I can get to cleaning my fighters’ list):

public void Activate(bool shouldActivate)
        {

            Debug.Log($"Aggrogroup {name} Activate({shouldActivate})");
            isActivated = shouldActivate;
            foreach (Fighter fighter in fighters)
            {

                if (fighter == null) {

                    Debug.Log("Null fighter found");
                    fighters.RemoveAll(fighter => fighter == null || fighter.IsDestroyed());

                }

// Rest of the code...

the main difference is the first ‘if’ statement in the foreach loop

What I don’t understand is why the AggroGroup has any null fighters in the first place…

If you’re AggroGroup is being used by Respawnables, it should start empty, when out of the game there should be NO fighters in it.
If your AggroGroup is not being used by Respawnables, then fighters should be in it, and they should all be valid.

Believe me, I’m trying just as hard as you to figure out why it has null fighters… it’s the main issue right now for me. If you need a current update of my code, here it is:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using RPG.Respawnables;
using RPG.Attributes;
using UnityEngine.AI;
using RPG.Movement;
using Unity.VisualScripting;

namespace RPG.Combat {

    public class AggroGroup : MonoBehaviour
    {

        [SerializeField] List<Fighter> fighters = new List<Fighter>();    // fighters to aggregate when our player takes a wrong dialogue turn (and pisses everyone off)
        [SerializeField] bool activateOnStart = false;
        private bool isActivated;

        private void Start() {

            // Ensures guards are not active to fight you, if you didn't trigger them:
            Activate(activateOnStart);

        }

        /* private void Awake() {

            Health playerHealth = GameObject.FindWithTag("Player").GetComponent<Health>();
            if (playerHealth) playerHealth.onDie.AddListener(() => Activate(activateOnStart));

        } */

        /* public void Activate(bool shouldActivate) {

            isActivated = shouldActivate;

            foreach (Fighter fighter in fighters) {

                CombatTarget target = fighter.GetComponent<CombatTarget>();
                
                if (target != null) {

                target.enabled = shouldActivate;

                }

                fighter.enabled = shouldActivate;

            }

        } */

        /* public void Activate(bool shouldActivate) {

            isActivated = shouldActivate;
            foreach (Fighter fighter in fighters) {

                if (!fighter) continue;
                fighter.enabled = shouldActivate;
                if (fighter.TryGetComponent(out CombatTarget target)) {

                    target.enabled = shouldActivate;

                }

            }

        } */

        public void Activate(bool shouldActivate)
        {

            fighters.RemoveAll(fighter => fighter == null || fighter.IsDestroyed());
            Debug.Log($"Aggrogroup {name} Activate({shouldActivate})");
            isActivated = shouldActivate;
            foreach (Fighter fighter in fighters)
            {

                if (fighter == null) {

                    Debug.Log("Null fighter found"); 
                    RemoveFighterFromGroup(fighter);

                }

                if (!fighter)
                {
                    Debug.Log($"Invalid fighter in configuration");
                    continue;
                }
                Debug.Log($"Setting {fighter}'s status to {shouldActivate}");
                fighter.enabled = shouldActivate;

                if (fighter.TryGetComponent(out CombatTarget target))
                {
                    target.enabled = shouldActivate;
                    Debug.Log($"{fighter}'s Combat target set to {target.enabled}");
                }

                // ----------------------------- TEST FUNCTION (Delete if failed): Setting destination of enemies to rush to attack the player: ----------------------                
                if (fighter.TryGetComponent(out NavMeshAgent navMeshAgent))
                {
                    navMeshAgent.enabled = shouldActivate;

                    if (shouldActivate)
                    {
                        Transform playerTransform = GameObject.FindGameObjectWithTag("Player").transform;
                        Debug.Log($"Setting destination to: {playerTransform.position}");
                        navMeshAgent.SetDestination(playerTransform.position);
                        Debug.Log($"{navMeshAgent.SetDestination(playerTransform.position)}");
                        navMeshAgent.isStopped = false;
                        fighter.GetComponent<Mover>().MoveTo(playerTransform.position, 1.0f);
                        Debug.Log("Fighter coming at you");

                    }
                }
                // --------------------------- END OF TEST FUNCTION --------------------------------------------------------------------------------------------------

                Debug.Log($"{fighter}'s enabled state = {fighter.enabled}");
            }
        }


        /* public void AddFighterToGroup(Fighter fighter) {

            if (fighters.Contains(fighter)) return;
            fighters.Add(fighter);
            if (fighter.TryGetComponent(out CombatTarget target)) {

                target.enabled = isActivated;   // synchronizing fighters with the remainder of the AggroGroup

            }

        } */

        public void AddFighterToGroup(Fighter fighter) {

            if (fighters.Contains(fighter)) return;
            fighters.Add(fighter);
            fighter.enabled = isActivated;
            if (fighter.TryGetComponent(out CombatTarget target)) target.enabled = isActivated;

        }

        public void RemoveFighterFromGroup(Fighter fighter) {

            // Remove fighters from the list
            fighters.Remove(fighter);

            // Remove null or destroyed fighters from the list
            // fighters.RemoveAll(fighter => fighter == null || fighter.IsDestroyed());

        }

    }

}

Above all else, the guards sometimes will want to come fight me, and for god knows what reason, I swear the guy will be midway through on his way to me and just go ‘nah, don’t feel like fighting him…’ (when it works!), then go back to patrolling

  • Are the null fighters an issue in a non-respawnable Aggrogroup?

Yes, they are

Then the problem is likely in the inspector… Are there empty entries in the Fighters list in the inspector?

With my current code, when the game starts, yes there are. However, they are quickly cleaned up by the ‘RemoveAll’ and ‘RemoveFighterFromGroup’ functions I placed in ‘Activate’ at the end of my dialogue with the guard that triggers the entire group

Again, sometimes it works… Sometimes it doesn’t (and it leans to ‘no’ in the game build, and I don’t know why).

Why? A non-respawnable aggro group should only have fighters that exist within the scene in it. Any null entries should be deleted before the game starts.
An aggrogroup tied to respawnables should always have an empty list when the game is not being played.

If I knew, I would’ve fixed it hours ago… This is one bug that’ll probably cost me all night to figure out :frowning:

I desperately want to know why this isn’t the case…! In other words, when the game is stopped from running, the fighters’ list still keeps hold of whatever it had prior to the game being stopped from running, hence why I do the cleaning at the start of the ‘Activate()’ function itself

I’m searching for ways to clean this up in the backscenes, not just the Unity Editor for example

???
That’s um… well… not correct behaviour…

Go into Project Settings and look for Editor, then scroll down and make sure that these buttons are all unchecked:

Yup, they’re unchecked… Anyway, the system works fine now, with the exception of one thing. One guard in the group is a bit far from the player, and he acts a little… weird. Sometimes he comes rushing from the end of the world to get involved into the fight, sometimes he rushes and changes his mind midway through (AI taking over already…?), and sometimes he just doesn’t care. My guess is, it’s something to do with the NavMeshAgent

The other guy (the closer one) nearly doesn’t mind rushing to fight every time now

It has more to do with the normal behaviour of our characters. When they’re too far from the player, they tend to go back to what they were doing as that’s what they were designed to do. AggroGroup characters need to be within shouting range of the AIConversant that triggers the action.

And I strongly recommend against trying to tweak AIController to compensate for this, you’ll find yourself in deeper bugs, likely with all characters chasing the player from wherever they are on the map.

That would honestly be so much fun to watch for a day. Imagine all the players going wild on their money back because the entire city comes hunting them down… :rofl:

I did attempt to modify the Max Nav Mesh Path Length variable in the inspector for mover though, I was hoping this would make them want to pursue me from further distances but… didn’t work well for that guy apparently

That’s because the real problem is the chase distance. If the guard is too far away, even with a valid fighter, he’s just not that into you.

Now you could set the AIController’s chase distance to be silly high on characters that are in the AggroGroup (in the scene, you would edit the chase distance in scene, for respawnables, the prefab you’re using (which shouldn’t be the same prefab as in scene characters anyways) could have the chase distance cranked. Just don’t do this with characters that are NOT in the AggroGroup.

For example, you could have all of the city guards in the Aggrogroup (so peaceful and friendly, right?) and then when you do something felony stupid and trigger the Aggrogroup all the guards come running. A bit like in real life, if you rob a bank all these people with guns and uniforms suddenly show up from everywhere, almost like they’re all in the same Aggrogroup

Privacy & Terms