Respawning enemies in the RPG course

Then the issue with the PatrolPath is not here at this point… probably the randomized waypoint thing… that’s a separate topic, so we won’t address that here.
We can address my mistake in OnValidate, however. Rather than calling Respawn() in OnValidate, we’re going to do things a bit more manually…

        private void OnValidate()
        {
            if (spawnableEnemy != lastSpawnableEnemy)
            {
                lastSpawnableEnemy = spawnableEnemy;
                foreach (Transform child in transform)
                {
                    DestroyImmediate(child.gameObject);
                }
                Instantiate(spawnableEnemy, transform);
            }
        }

Fixed it (as in, I changed the ‘OnValidate()’ function to follow yours), but the issue of the patrolPath still persists. I don’t honestly think the issue is with the Random Waypoint index either. I went and tried changing my Cycle of the waypoints back to follow the code Sam provided us, and the issue still persisted.

Is the Patrol behaviour working correctly on a normal enemy? One that starts in the game, not under a SpawnManager?

Yes Brian, it works correctly over there

Ok here’s what I figured out: The moment the Patrol Path becomes a Prefab that we assign to our enemy, both Enemy versions (the prefab and the hierarchy version) flat out refuse to follow it, and I have no idea why.

Brian, Brian, I got it to work. The problem was never inside the code. The problem was CHOOSING THE PATROL PATH INSIDE THE SCENE ITSELF. Here’s what I mean:

When assigning your Patrol Path in the Respawn Manager script through your inspector, you DONT choose the Prefab Patrol Path Asset. Instead, you click on ‘Scene’ and choose your Patrol Path over there. This was a headache for the two of us to deal with, but we got it to work. Thank you so much for being with me as we figure this out Brian!

The Patrol Paths MUST BE ON THE Game Hierarchy Scene for this to work. You might want to mention this in the post for future visitors :slight_smile:

Mention managed. :slight_smile:

1 Like

Hi again Brian. As mentioned on Udemy, I had a problem of including Spawned enemies (through Spawn Points) into the mix of my Aggrevated Enemies when one guard triggers everyone to get into the fight, due to a threat through dialogue for instance. We also had other dialogue issues, but that would be better left for either the Udemy conversations, or another question. Here is the full question, if you need to refer to it :slight_smile:

"Hi all, recently following Brian’s instructions on GameDev Tv about how to make enemies Spawnable, I ended up deleting my enemies from the scene and spawning them using Spawn points instead. My problem is, this means the enemies are now not exactly on the scene to be called for this function to work properly, instead they belong to a Spawn Point, and I was hoping to be able to make that work for my game (i.e: Bring the enemies along, which belong to the Spawn Points into the fight). How do I make the guards (which are called via empty Spawn Points in the scene) callable by this function in the scene? I also want to anger them once, I’m not sure if this code means angering them everytime they respawn or not (i.e: when they come back to life, they will completely forget this fight ever happened, unless I get close enough to them)

I also had to delete the CombatTarget line for my other guards to be attackable. Is this going to be properly fixed down the line? I want to be able to attack other guards as I will without having a connecting script to work on everyone. For that, I tried creating a variant of my CombatTarget script, but that did not work as expected (Edit: it did, but setting things up in the scene, and then applying them to the prefab, and then getting the AI Conversant and CombatTarget_DialogueGuards script in order (for the Raycast scripts to function properly) was a Prefab headache)

Edit 2: Now I can talk to the guard when he’s dead… Does Sam address this down the line? What do we need to consider if we were to make this a Spawnable Entity As well?

And one last thing, why is it that everytime the Player gets to talk, it displays as a button? Is there a way we can make it that a Menu Choice button only shows up when options exist rather than that it only occurs whenever the player has to speak?"

For the Aggro groups, let’s make a couple changes to AggroGroup…
First, change the declaration for Fighters from a Fighter array to a List

[SerializeField] List<Fighter> fighters = new List<Fighter>();

Then add some helper methods to add and subtract Fighters…

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

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

Then in the RespawnManager, add a field for the Aggrogroup

[SerializeField] AggroGroup aggroGroup;

Now, on Respawn, add the line

            if (aggroGroup != null)
            {
                aggroGroup.AddFighter(spawnedEnemy.GetComponent<Fighter>());
            }

and in OnDeath add

            if (aggroGroup != null)
            {
                aggroGroup.RemoveFighter(spawnedEnemy.GetComponent<Fighter>());
            }

This should pretty much take care of that.

This is not covered in the course, however it’s not terribly difficult to implement, in the IHandleRaycast of AIConversant… You might want to check to see if the Health component attached to the AIConversant is alive or dead, if it’s dead, just return false.

Set this up as a question by itself under Dialogues and Quests. I can tell you that it’s not properly fixed because this is not a problem in the course version of the code. You shouldn’t need a variant, you should just be able to check if the Fighter is enabled, and return false if it isn’t.

Yup, that worked. Here’s my code if anyone wants to have a look at it:

In SerializeField zone:

[SerializeField] Health thisCharacterHealth;

In the HandleRaycast Function, under Dialogue Check:

if (thisCharacterHealth.IsDead()) { return false; }

and don’t forget ‘using RPG.Attributes’ at the top

Hi Brian, thank you for the script, but the instructions on that are a bit unclear. How do we use this new knowledge to make this system work?

On Udemy? Will do :slight_smile:

Ok I hope this isn’t considered necroing a thread but my console is getting spammed with warnings and errors after trying this. I am not trying to do anything with Patrol Paths – just doing the basics. I also applied Brian’s fix to OnValidate but it just moved the errors and warnings around but didn’t actually get rid of them.

It seems to work fine but the spamming of errors and warnings is too much. It makes it hard to use my logs.

here’s what I’m seeing

  1. I’m getting multiple instances of this error upon entering play mode. 2 separate batches of 5-6 instances per RespawnManager
Destroying GameObjects immediately is not permitted during physics trigger/contact, animation event callbacks, rendering callbacks or OnValidate. You must use Destroy instead.
UnityEngine.Object:DestroyImmediate (UnityEngine.Object)
RPG.Respawnables.RespawnManager:OnValidate () (at Assets/Scripts/Respawnables/RespawnManager.cs:90)

  1. Also getting multiple variations of this warning upon entering play mode. 2 sets of ~13 warnings
SendMessage cannot be called during Awake, CheckConsistency, or OnValidate (Canvas: OnCanvasHierarchyChanged)
UnityEngine.Object:Instantiate<RPG.Control.AIController> (RPG.Control.AIController,UnityEngine.Transform)
RPG.Respawnables.RespawnManager:OnValidate () (at Assets/Scripts/Respawnables/RespawnManager.cs:92)
  1. A few instances of this warning after entering playmode
Failed to create agent because there is no valid NavMesh
UnityEngine.Object:Instantiate<RPG.Control.AIController> (RPG.Control.AIController,UnityEngine.Transform)
RPG.Respawnables.RespawnManager:OnValidate () (at Assets/Scripts/Respawnables/RespawnManager.cs:92)
  1. A few instances of this one after exiting playmode
Destroying GameObjects immediately is not permitted during physics trigger/contact, animation event callbacks, rendering callbacks or OnValidate. You must use Destroy instead.
UnityEngine.Object:DestroyImmediate (UnityEngine.Object)
RPG.Respawnables.RespawnManager:OnValidate () (at Assets/Scripts/Respawnables/RespawnManager.cs:90)
  1. A few instances of this one again
SendMessage cannot be called during Awake, CheckConsistency, or OnValidate (Canvas: OnCanvasHierarchyChanged)
UnityEngine.Object:Instantiate<RPG.Control.AIController> (RPG.Control.AIController,UnityEngine.Transform)
RPG.Respawnables.RespawnManager:OnValidate () (at Assets/Scripts/Respawnables/RespawnManager.cs:92)

  1. and then a few instances of this other one triggered on Awake
Destroy may not be called from edit mode! Use DestroyImmediate instead.
Destroying an object in edit mode destroys it permanently.
UnityEngine.Object:Destroy (UnityEngine.Object)
RPG.Respawnables.RespawnManager:Respawn () (at Assets/Scripts/Respawnables/RespawnManager.cs:63)
RPG.Respawnables.RespawnManager:Awake () (at Assets/Scripts/Respawnables/RespawnManager.cs:28)

My “fix” is to

  1. Convert the Awake() to a Start. Same code otherwise.
        private void Start()
        {
            Respawn();
        }
  1. Remove OnValidate()

This fix seems to work but it has some behaviour I am confused by.

When I start a play mode session with no child objects and then exit playmode, the SpawnableEnemy it spawns is retained as a child object even after play mode ends. Why does it not disappear at the end of play mode (like a spawned weapon or spawned fireball?). Again I’m starting off with no child objects in edit mode so I don’t understand why something created during run time persists when I exit play mode?

EDIT: It seems what’s happening is that the Start method is being called when I exit playmode which is triggering a Respawn. Ok but why is start being called? Is it because we’re inheriting from SaveableEntity which has this [ExecuteAlways] at the top?

I’ll note that I still get this error right when I exit play mode even after my “fix” which seems to provide evidence that Start is being called when I exit play mode.

Destroy may not be called from edit mode! Use DestroyImmediate instead.
Destroying an object in edit mode destroys it permanently.
UnityEngine.Object:Destroy (UnityEngine.Object)
RPG.Respawnables.RespawnManager:Respawn () (at Assets/Scripts/Respawnables/RespawnManager.cs:69)
RPG.Respawnables.RespawnManager:Start () (at Assets/Scripts/Respawnables/RespawnManager.cs:33)

I’m now looking into this Procedurally Spawned Characters as a possible solution.

This no longer works in Unity 2022 and later. I learned this the hard way when I moved SpellbornHunter from Unity 2021 to Unity 2022.

The whole trick was designed so that you could change the prefab and then it would automagically spawn the cloned character as a place holder. This addresses pretty much your entire thread of issues. You simply can’t use this OnValidate logic at all.

What you can do is in Start(), you can destroy everything under the spawner and then spawn the character. This means, of course, though, that you’ll have to add the preview character manually.

Thanks. What explains Start getting called after I exit play mode? Is it my guess of having [ExecuteAlways] on the class we inherit from?

Assuming it’s that, then I will resolve this.

I think my path will be to use the “Procedurally Spawned Characters” solution you created. I tried it and added a reply to your post on a few places I got stuck.

See what happens when you pull ExecuteAlways…

There is actually another flaw in the ointment… The character isn’t spawned until Start() but if you call RestoreState() you’ll get an NRE… (Was just covering this with another student on Udemy)

The solution:

bool hasBeenRestored;
void Start()
{
    if(!hasBeenRestored) Respawn();
}

And as the first line in RestoreState()

Respawn();
hasBeenRestored=true;

What did you mean by “pull”. As is use [ExecuteAlways] or remove [ExecuteAlways]. I think you meant the former because I didn’t remove it.

I was uncomfortable with [ExecuteAlways] and how we’re generating GUIDs from the moment I saw it :rofl: :rofl: :rofl: I was totally expecting something like this thread would happen at some point in the not too distant future.

Privacy & Terms