Minions in the RPG course

Hi Community,

Just about to finish the 4th course of the RPG series for the second time. I could not find any thread about how to implement minions as an ability, so I thought I would start one. I’ve been trying for the last day to figure out the best way to get this done. I’ve tried creating a new Minion SO with all the setup data needed, I’ve tried using something similar to projectiles, I’ve tried using a new monobehaviour with included basestats, and added a minion class to the characterclass enum and the progression table. So far I have been able to instantiate the minion as a prefab variant of the player (with certain components removed) but thats about as far as I got. Ive also created a minionController class which will hold all the AI logic for the minion, once its fleshed out. This will include things like max distance from player, attack state, follow player etc
So essentially, the summon minion is an ability that uses the self cast strategy, then each instantiated minion is a character variant, with its own AI logic.

Before I spend too much time on this, I wanted to reach out to see if anyone has successfully implemented minions and if so, can they point me to the correct start location (if the way im doing it is not the most efficient). Or if not, does anyone know the best way to get this done.
My hope is that this post thread serves to help anyone in the future create minions.
Your help is appreciated.

Cheers

1 Like

Update 1:
What I have done in the last day is added a sort of state machine, kind of like the aiController monobehaviour. Currently there are 4 states: Idle, move, defend, attack.

  • Idle so far only cancel a move action when the minion is less than maxDistance from the player, but I plan to eventually make it include idle animations.

  • Move uses the Mover.cs to start a move action if the distance between player and minion is more than the maxDistance.

  • Defend does nothing yet

  • Attack is where I spent most of my time. The way I went about this is to implement a number of different ways the minion can attack. First there is a spherecast around the player with an aggroRange. If a collider with a tag of enemy enters that sphereCast, they are added to an array, and the minions attack. Minions will also attack if the player has a combatTarget, essentially if the player is attacking. Lastly, minions will also attack if the player right clicks, and the raycast hit finds an enemy. If any of those conditions are met, a move action is started towards the enemy until the attack distance is reached, then an attack method is called. This will be fleshed out in my next update.

Hopefully I’m on the right track. It all seems to be working so far.

Some pitfalls I can foresee - how to remove minions from the array if the player moves far enough away. How to make sure the minions are only attacking the first enemy they come to in the array, not all at once. I will just need to introduce a for loop for this, and iterate over it based on distance???

Cheers

Update 2:
I havent made much progress, due to being stuck on a few particular problems. I think I have solved those problems but then testing reveals other issues have arisen. So I have completely written my code twice over now and got it back to where it was 3 days ago. Starting to feel like I cannot accomplish this. But Im not done yet. I will push on!

Problems I am stuck on: iterating through the enemies found in the spherecast all and having minions attack them one by one. They seem to skip the first one and go to the second. Right click to attack is not working, and I cannot figure out why. Probably something simple im missing.

Things that are working: experience is being correctly attributed to the player when a minion kills an enemy. When the summon limit is reached, the first summon dies and a new one is summoned in its place, ensuring that only the maximum summon limit (configurable) are summoned at one time. This is not working as intended yet as when I ran a debug on the count, it was counting 1, 2 and 3 minions at the same time. So i think when a minion is killed and a new one takes its place, it starts a new list of minions. for some reason.

Things to work on: enemies aggroing to minions when attacked by minions. Currently they run straight or the player. Minion health, taking damage and death. Different minion types. Having minions attack enemies if those enemies are attacking the player.

Lots to do but hopefully I come up with something and can complete this task. If anyone wants to help, I won’t say no. :smiley:

Cheers

Hi Nizzle, welcome to the community.

Check how the index for the iteration is started. If the minions are picking the second element, then your iteration is probably starting at 1 instead of 0. Lists and arrays are numbered starting with 0. 0,1,2,3 instead of 1,2,3,4 like counting objects in the real world.

The minion summoning is a great idea. Keep at it!

Hey edc237,

Thanks for the reply. I think the problem lies in how im iterating through them. I will link my code for you to check. The code is working, and the minions attack each of the enemies one by one, just weird behaviour in the order. Im trying to use the same theory as the guard patrol locations. Set a current target, when that one dies set current target to next target (which is i+1 in the list).

RaycastHit[] hits = Physics.SphereCastAll(player.transform.position, aggroRange, Vector3.up, 0);
                List<GameObject> enemies = new List<GameObject>();
                foreach (RaycastHit hit in hits)
                {
                    if (hit.collider.CompareTag("Enemy"))
                    {
                        if (hit.collider == this) return;
                        enemies.Add(hit.collider.gameObject);
                        //Currently not keeping currenttarget as the first target - minions go straight to the second target
                        for (int i = 0; i < enemies.Count; i++)
                        {
                            Debug.Log(currentTarget);
                            currentTarget = enemies[i];
                            if (currentTarget.GetComponent<Health>().GetHealthPoints() > 0)
                            {
                                currentTarget = enemies[i];
                            }
                            else if (currentTarget.GetComponent<Health>().GetHealthPoints() <= 0)
                            {
                                GameObject nextTarget = enemies[i + 1];
                                currentTarget = nextTarget;
                            }
                            else if (currentTarget == null)
                            {
                                StopAttack();
                                MoveBehaviour();
                            }

                            mover.StartMoveAction(currentTarget.transform.position);
                            if (Vector3.Distance(transform.position, currentTarget.transform.position) < attackDistance)
                            {
                                Attack();
                            }
                        }
                    }
                }

This is all inside a method called attack behaviour that is called from update.

I know my code is probably not the most efficient wat of doing things, but I feel a sense of accomplishment just for making it function.

I appreciate you and your input. Net big hurdle for me is the aggro to minions, not just the player.

Cheers

Lets look at the flow of your for loop. You have the index set up right. The first thing you do is log the current target. Then you set the current target. Next you check the current target for health points, then you set it again. If it does not have hit points you set it to the next enemy in the list. I can’t see how, but this seems to be forcing the target to the skip the first enemy.

Try something like this (I can’t test this because I don’t have my project set up for this):

for (int i = 0; i < enemies.Count; i++)
                        {
                            // Debug.Log(currentTarget); Move this to after the for loop is finished
                            if (currentTarget.GetComponent<Health>().GetHealthPoints() > 0)
                            {
                                currentTarget = enemies[i];
                            }
                            else if (currentTarget.GetComponent<Health>().GetHealthPoints() <= 0)
                            {
                                return;
                            }
                            else if (currentTarget == null)
                            {
                                StopAttack();
                                MoveBehaviour();
                            }

There are often many paths to the same goal, and we often don’t start with the most efficient approach. In the prototype stages, you’re looking at ways of doing things, keeping what works, discarding what doesn’t. I often overengineer a process and then refine it later as I see combinable solutions and redundant code. Don’t worry, you’re doing just fine.

There is a small issue with using the for loop, in that you appear to be continuing through the loop even if you HAVE found a suitable target… In this case, the loop can be greatly simplified…

currentTarget=null;
foreach(RaycastHit hit in hits)
{
    if(hit.collider.CompareTag("Enemy")) 
    {
         if(hit.collider.gameObject==this.gameObject) continue;  //return exits the entire method
         if(hit.collider.GetComponent<Health>().GetHealthPoints>0)
         {
              currentTarget=hit.collider.gameObject;
              break; //We've established a target, stop iterating over the loop
         }
}
if(currentTarget)
{
    //Why not replace all of this with Fighter.Attack(currentTarget)?
    mover.StartMoveAction(currentTarget.transform.position);
    if(Vector3.Distance(transform.position, currentTarget.transform.position < attackDistance)
    {
         Attack();
    }
}
else
{
     StopAttack();
     MoveBehaviour();
}

Hi Brian,

As always, thank you for your advise. Much cleaner code.

Cheers

Update 3
After yet another complete refactor, I have managed to make progress. I now have a system where minions move to a location the player right clicks at, attacks the target if an enemy is found, cycles through the enemies and attacks them one by one. The enemies also fight back, cycling through the minions one by one and if there arent any, attack the player. Its now an actual battle system and alot of fun.

To do this I had to add a foreach and a for loop to the AiController to cycle through each minion, attack them in order and still aggravate your mates to join in.

Problem at the moment is xp is being awarded PER minion on the field. Should be a relatively easy fix.

The system is definately getting there.
Cheers

1 Like

Hi community,

Minion system working now, but not quite 100%. I will list the problems I am having, if anyone can guide me as how to fix these small issues, that would be grand. I have been trying but cant seem to get my head around the correct approach.

  1. Minions health and damage is only using level 1 health and damage from the progression table. The level is updating correctly, I am using currentLevel = player.GetComponent<BaseStats>().GetLevel(); in update. I tried setting health to be equal to basestats health, but even when the minions are level 2, the health is still level 1 health. I’m sure I’m missing a step here. I have added a minions character class to the enum, and made the minion summon that character class in their base stats component. The default level is 1, so the only thing i can think is that the way I am getting the level is incorrect, and its not updating the base stats level. I tried using the progression object to set the health via the GetLevels method but nothing seemed to work.

  2. I added if(gameObject.tag == "Player" && target.gameObject.tag == "Minion") { return; } to the fighter script, so that the player cannot attack minions, but the cursor is still the attack cursor. How do I get it to stay as the move cursor?

  3. Players and minions collide. How do I turn off the minions collider when its near the player? Or will I need to overhaul the minions collision and turn it off completely?

That is the main problems that I cant seem to figure out. Once the system is fully fleshed out and playing like I want, I will link my code for everyone else to use.

I am going for a system similar to Grim Dawn. Love how minions function in that game.

Cheers

If you set up the minions in their own layer and the player in its layer, then you can turn off interactions between the minion layer and the player layer in the physics layer interaction table. This may solve both issues. Unless you need the player to be able to interact with the minions for some other reason.

Hey Ed,

Unfortunately not. Unless im doing it wrong. Player is on player layer, minions on minion layer. Unticked them in the project settings > Physics > Layer Collision Matrix. I also tried to use the exclude layer option in both the rigid body and the capsule collider.

Cheers

Can you post screen shots of both the player and minion inspectors? Sometimes an extra set of eyes can find little things that are easy to overlook.



Sure can, Let me know if you want to see them with any components opens (Im guessing you will).

Cheers

The cursor is set by the CombatTarget, which implements the IRaycastable interface. You could either not have a CombatTarget on the minions, or add this to the beginning of the CombatTarget’s HandleRaycast method:

if(gameObject.CompareTag("Minion")) return false;

If you’re using the Mover from the course, go into your NavMeshAgent settings. Make sure Obstacle Avoidance is on high quality for every character.

Hi Brian,

The code at the start of the combat target has solved one issue, thank you.

All my characters were already using High Obstacle Avoidance. The issue still persists. Ill find a work around eventually.

Any advice for the level 1 health and damage, even though the minions are level the same level as the player. Im pretty sure Im setting the level wrong. I am just setting the stat.GetLevel to be equal to the player level, in update.

Cheers

So you’re trying to keep the level of the minions always the same level as the Player… Let’s see your current BaseStats code, I especially want to see the Update(), but the whole thing so I can see how you’re handling Player verses Minion vs Enemy

Privacy & Terms