Aggrevated-by-dialogue Enemy Respawn

Alright so as the title suggests, I’m trying to discover a way to anger a group of guards through dialogue (Quests & Dialogues course, Lecture 51), but the unique point here is that I have a Respawn Manager attached to the guards (so they can die and come back to life after their death). As far as we have reached, this was Brians’ suggestion:

You'll need a way to add them to the Aggro Group....



You can add methods to the AggroGroup to add and remove members.

Then in Respawn Manager, add a SerializedField for the AggroGroup

When the character is created, if there is an AggroGroup add it to the AggroGroup

When the character dies, if there is an AggroGroup, remove it from the AggroGroup.



The DialogueTrigger, on the other hand has the problem that it can't link to an AggroGroup (because a Prefab can't link to a scene object).  So we're going to have to trick things out a bit.

You'll need a MonoBehavior that has two methods:

SetAggroGroup - The Respawn Manager will do this if it finds this MonoBehaviour on the character, it should take in an AggroGroup and store that value in a variable.

CallAggroGroup - This is the method you will link in the DialogueTrigger.  It will check to make sure the Aggrogroup isn't null, and then call the appropriate method.

I gave his suggestion a go, and this is how it ended up before I got stuck:

RespawnManager.Respawn():

// TEST: Delete if failed
            if (aggroGroup != null) {
 
                dialogueAggro.SetAggroGroup();
                aggroGroup.AddFighter(spawnedEnemy.GetComponent<Fighter>());
 
            }

RespawnManager.OnDeath():

// TEST: Delete if failed
            if (aggroGroup != null) {
 
                aggroGroup.RemoveFighter(spawnedEnemy.GetComponent<Fighter>());
 
            }

AggroGroup.cs Add and Remove Method:

public void AddFighter(Fighter fighter) {
 
            fighters.Add(fighter);
 
        }
 
        public void RemoveFighter(Fighter fighter) {
 
            fighters.Remove(fighter);
 
        }

DialogueAggro.cs (a new script I created to host the ‘SetAggroGroup()’ and ‘CallAggroGroup()’ functions (also the most confusing one to figure out):

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using RPG.Combat;
 
namespace RPG.Respawnables {
 
public class DialogueAggro : MonoBehaviour
{
    
    [SerializeField] AggroGroup aggroGroup;
 
    public void CallAggroGroup() {
 
        if (aggroGroup != null) SetAggroGroup(); 
 
    }
 
    public void SetAggroGroup(AggroGroup aggroGroup2) {
 
        aggroGroup2 = aggroGroup;
 
    }
 
}
 
}

DialogueTrigger.cs:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using RPG.Respawnables;
using RPG.Combat;
 
namespace RPG.Dialogue {
 
    public class DialogueTrigger : MonoBehaviour
    {
        [SerializeField] string action;
        [SerializeField] UnityEvent onTrigger;
 
        // TEST: Delete if failed
        [SerializeField] AggroGroup aggroGroup;
 
        public void Trigger(string actionToTrigger) {
 
            if (actionToTrigger == action) {
 
                // TEST: Delete if failed:
                if (aggroGroup != null) DialogueAggro.CallAggroGroup();
 
                onTrigger.Invoke();
                // if the action we want to activate is equal to our 'actionToTrigger',
                // we will launch that action (using 'Invoke()')
 
            }
 
        }
        
    }
 
}
 

Needless to say, this did not work as expected (Mainly because I didn’t fully understand how to code it…). What fixes can be done here?

Here’s the link to the original conversation, if it helps in anyway: https://www.udemy.com/course/unity-dialogue-quests/learn/lecture/22861315#questions/20490710/

I’m not quite sure what the purpose of this is… I can tell you functionally that calling CallAggroGroup will do nothing (in fact, I’m not even sure how it compiles, as SetAggroGroup is called without a parameter AggroGroup)! Calling SetAggroGroup will overwrite the value of the aggroGroup2 value passed into the method with the aggroGroup in the Serialized Field…

Let’s rework this a bit…

DialogueAggro.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using RPG.Combat;
 
namespace RPG.Respawnables {
//This class will only go on the character PREFAB that has a dialogue.
public class DialogueAggro : MonoBehaviour
{
    //As this is going to be on a prefab, you cannot serialize a scene reference in a prefab...
    //[SerializeField] AggroGroup aggroGroup; 
    AggroGroup aggroGroup;
 
    //This is the method that your DialogueTrigger will call to Aggrevate. 
    public void CallAggroGroup() 
    { 
       if(aggroGroup!=null) aggroGroup.Activate(true); 
    }
 
    //Sets the aggrogroup, called by RespawnManager so that when the trigger fires, this
    //class has a link to the AggroGroup.
    public void SetAggroGroup(AggroGroup aggroGroup2) 
    {
        aggroGroup = aggroGroup2;
    }

} 
}

This requires some adjustments to the AggroGroup class, as we want to add or remove fighters from the AggroGroup depending on their status.

AggroGroup.cs
using System.Collections.Generic;
using UnityEngine;

namespace RPG.Combat
{
    public class AggroGroup : MonoBehaviour
    {
        //[SerializeField] Fighter[] fighters; 
        //Making this a list so that it can be easily added to or subtracted from
        private List<Fighter> fighters = new List<Fighter>();
        [SerializeField] bool activateOnStart = false;
        private bool isActivated;
        private void Start() {
            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 AddFighterToGroup(Fighter fighter)
        {
            if (fighters.Contains(fighter)) return;
            fighters.Add(fighter);
            if (fighter.TryGetComponent(out CombatTarget target))
            {
                target.enabled = isActivated; //keeps Fighters in sync with remainder of Aggrogroup
            }
        }

        public void RemoveFighterFromGroup(Fighter fighter)
        {
            fighters.Remove(fighter); //Because parameter to Remove can be null, no need to null check.
        }
        
    }
}

I’m assuming you’ve done that, as you’re referencing aggroGroup in your RespawnManager.

That’s exactly correct.

No modifications are required for DialogueTrigger.
In your DialogueTrigger UnityEvent, link to the prefabl’s DialogueAggro and the method CallAggroGroup

OK so I just have a small compilation error that is preventing me from running this code, and it’s in this block in ‘RespawnManager.Respawn()’:

if(spawnedEnemy.TryGetComponent(out DialogueAggro dialogueAggro) 
                   && aggroGroup!=null)
                {
                      dialogueAggro.SetAggroGroup(dialogueAggro);
                }
                aggroGroup.AddFighter(spawnedEnemy.GetComponent<Fighter>());

The compiler complains to me in the ‘if’ condition that ‘dialogueAggro’ cannot be converted directly from an ‘RPG.Respawnables.DialogueAggro’ straight to an ‘RPG.Combat.AggroGroup’… How do we fix this?

30 demerits for the TA, but I’m going to challenge you to figure out what I did wrong in that method…
What type of class is SetAggroGroup taking in?
What other object in the method is of the class SetAggroGroup() wants as a parameter…

The type of class SetAggroGroup is set in is a ‘dialogueAggro’ (RPG.Respawnables) class, which also happens to be the same class as ‘AggroGroup’, hence we replace ‘dialogueAggro’ with ‘aggroGroup’ is my guess. Correct?

Did it comple?

Yes. Now I’m trying to figure out how to make this whole thing work in Unity, as the ‘AggroGroup’ only accepts fighters and not respawn managers (wait, do we include the fighters in the Aggro Group, and integrate that into our Respawn Manager?)

Doesn’t this look like a Fighter?

Yes it does, should I include that in ‘dialogueAggro.SetAggroGroup()?’

DialogueAggro ONLY takes an Aggrogroup, nothing else.
We set that with

if(spawnedEnemy.TryGetComponent(out DialogueAggro dialogueAggro) 
                   && aggroGroup!=null)
                {
                      dialogueAggro.SetAggroGroup(aggroGroup);
                }

The Aggrogroup takes just the Fighter component. We set that in

aggroGroup.AddFighter(spawnedEnemy.GetComponent<Fighter>());

We’re only setting the DialogueAggro.aggroGroup if the character is going to trigger an AggroGroup, hence the if statement.
We’re always adding the fighter to the aggroGroup if there is an aggroGroup.

OK so let me just get this straight, because I’m slightly baffled on how all this works:

In the inspector of our ‘RespawnManager.cs’ script, we set our Aggro Group by uploading the scene object ‘Aggro Guard Group’ to the serialized field, right? and then the Respawner will check if we have a Spawnable enemy or an Aggro Group to respawn, and based on what it finds it prioritizes the Aggro Group over a normal enemy?

You do not respawn the Aggro Group
The AggroGroup stays in the scene, and each RespawnManager has a

[SerializeField] AggroGroup aggroGroup;

If the Respawn Manager has no aggroGroup in the field, then it just respawns the enemy and doesn’t worry about it.
If it DOES have an aggroGroup, then when it spawns in a character, it adds the enemy to the AggroGroup. If the character has a DialogueAggro, that means it is meant to trigger an AggroGroup at some point, so it passes the aggroGroup on to the character’s DialogueAggro.
At no point does the RespawnManager spawn or respawn the AggroGroup object itself, that should exist in the scene and be linked to each RespawnManager whose character should be in the AggroGroup.,

OK So in an attempt to set this up, I placed an empty gameobject that parents my dialogue guard, and named it ‘Aggro Guard Group’. In that object, I have attached two scripts: The Aggro Group (which has my Dialogue Guard as a fighter), and the Respawn Manager, which has only my Aggro Group as an input (apart from the hide and Respawn time), but that didn’t get him to Respawn. Any suggestions of what I did wrong?

Edit: That became a huge problem for all my guards, where they don’t Patrol or Respawn anymore… Something went off. Here’s the Stack Trace of the NullReferenceException error I’m getting (which is pretty much the same for all guards apart from the aggregated-by-dialogue one):

NullReferenceException: Object reference not set to an instance of an object
RPG.Respawnables.RespawnManager.Respawn () (at Assets/Project Backup/Scripts/Respawnables/RespawnManager.cs:56)
RPG.Respawnables.RespawnManager.Start () (at Assets/Project Backup/Scripts/Respawnables/RespawnManager.cs:47)

Line 47:

private void Start() {

if (!hasBeenRestored) Respawn();

}

line 56:

private void Respawn()
        {

            // ---------------- Some Extra Functionality ---------------------------------------------

            if (spawnedEnemy.TryGetComponent(out DialogueAggro dialogueAggro) && aggroGroup != null) {

                dialogueAggro.SetAggroGroup(aggroGroup);

            }

            aggroGroup.AddFighter(spawnedEnemy.GetComponent<Fighter>());

// Rest of the Function
}

The Respawn Manager should be spawning in an assigned prefab…

Patrol paths are another subject, though a bit simpler. You simply need to have a Patrol Path serialized field on the Respawn Manager, and when the prefab for the character is spawned in, you’ll need to assign the Patrol Path (if any) to the character’s AIController… (You’ll need to add a method to AIController to set the Patrolpath.

The Patrol Paths were working perfectly fine for all my normal enemies until this point, and they all had Respawn Managers on them :sweat_smile: - Does re-structuring the code help by any chance?

Edit: I placed a parent prefab (which has the Aggro Group as a child, and that child has the guard as its child), that hosts the Respawn Manager for the aggro group, and it still didn’t get anything to work

Edit 2: Yes it is, yes it is a coding structure error. I don’t think this block:

if (spawnedEnemy.TryGetComponent(out DialogueAggro dialogueAggro) && aggroGroup != null) {

                dialogueAggro.SetAggroGroup(aggroGroup);

            }

            aggroGroup.AddFighter(spawnedEnemy.GetComponent<Fighter>());

Goes at the top of ‘Respawn()’. I’ll try re-arranging it and seeing where it can stay and work as expected

It goes after you instantiate spawnedEnemy, but within Respawn(), not at the top.

I placed it at the bottom of the function, after respawning and assigning patrol paths to everyone, which fixed the major problem of the rest of the guards, making this issue idle. However, Respawners are still failing to work for my Dialogue-triggered guard (apparently it’s because the input doesn’t accept scene-based references, only assets, and the asset prefab of this system is flat out rejected by the scene-based Respawn Manager). If anything, the Parent Respawner deletes the Aggro Group child when we start the game for some reason, so technically the guard doesn’t even exist in-game now

The parent respawner deletes all of it’s children if I remember correctly. The AggroGroup should be a separate GameObject… Or, you could have the AggroGroup, and have a respawner for each enemy in the AggroGroup as child gameObjects of the AggroGroup.

Privacy & Terms