Visualizzazione post con etichetta ai. Mostra tutti i post
Visualizzazione post con etichetta ai. Mostra tutti i post

lunedì 20 luglio 2015

Experimenting with decentralized control for swarm shape formation

Abstract

Objective of the project is to experiment with a decentralized control for swarm shape formation in a virtual environment.
Approach taken to the problem is the use of motor schema for aggregation of different behaviour forces used to provide control and autonomy of the swarm in respect of the user and some simple task like obstacle avoidance.
The techniques proposed can be used as an approach to deal with shape formation of virtual entities in videogames or as means to create graphical effects in particle systems.

Introduction

Swarm systems are the one made by a collection of individual entities, all acting on their own to reach a global collective goal. This type of systems are becoming more and more common due to their highly flexibility and ability to react to change, usually inspired by natural biological systems they are now used in a variety of useful applications, like: map building, surveillance, exploration, etc.
For means of adaptability and control, while providing their main functionality, the swarm should be able to preserve or form a defined shape, like a sphere, a grid lattice or a line, moreover shape formation can be the main functionality of the swarm, like in an hunting-like environment where predators forms a circle around the prey. On the matter of swarm robots, shape formation is a topic already discussed in research, where most of the resources are focused on dealing with poor sensors capability and limits imposed by the physical world. Not so much is said about using this decentralized approach as an architecture for building interactive applications, like videogames, simulation environments, etc.
This document wants to focus on the practices used for building an interactive application that simulates shape formation for a group of agents in a predefined environment. We’ll focus on the decentralized approach used for forming a circle with variable diameter size, a simple line, a grid lattice with variable vertical lines and a sparse formation in respect of a fixed distance constraint. We’ll see how using the potential field movement schema we can aggregate different behaviours that offers a high flexibility in respect of coupling with multiple tasks, like obstacle avoidance, preserve separation and react to user control.

Assumptions

Since we are not dealing with robots but simple agents in a virtual environment, strong assumptions are made.
Agents knows their exactly position and rotation in space based on common coordinates of the scene, moreover agents have the ability to locate “peers” among the scene and determinate the exact distance from each of them (i.e. they have a “global” sensing capability as stated in [1] and [2]).
Usage of these pattern formation tec
\hniques can still be used on not-complex physical environment with respect of these assumptions, using gyroscopes and indoor or global positioning systems.

Development

Forming a circle

The method used for forming the shape of a circle is based on the CIRCLE Algorithm proposed in [1]. The objective of this algorithm is to form an approximation of a circle of diameter D, given the ability of the agent to perceive each of his peers.
Each agent R continuously monitors the distance from the furthest agent R’ and from the nearest agent R’’ and calculates the distance d between the two, then acts as follow:
  • Case 1. If d > D, then R moves toward R'.
  • Case 2. If d < D - δ, then R moves away from R'.
  • Case 3. If D - δ < d < D, then R moves away from R'.
Where δ is a small constant.
As [1] states, the form of a circle is assured by these three simple cases if the distribution of the agents in the space is uniform, however we overcome this limit by summing to the vector force that create the circle a separation vector force that distanciates peers.
Figure 1 shows results of the application compared to the ones of [1].

Forming a line

Forming a line segment is one of the simplest formation behaviour, all one agent R needs to do is to detect his nearest right peer r(R) and his nearest left peer l(R) and then move to the middle point of the r(R)l(R) segment. Agents that doesn’t have a right or left peer are the point of the line segment and shouldn’t move. Result of the algorithm as proposed in [1] is shown in figure 2.

Forming a sparse formation

Another interesting topology for a swarm is the one in which it’s parts are never too close, but either not too far apart. Using the dispersion method proposed in [2] one can control a swarm so that it doesn’t form a specific shape, but a more sparse and yet uniform in peer distance formation.
The dispersion method described in [2] was slightly modified to work better in a decentralized fashion and works as follows:
  • For every agent O find the three closest agents A,B and C
  • Initialize a force vector to zero
    • if distance of A from O is > d sum to the force vector an attractive force from O to A
    • if distance of A from O is < d sum to the force vector a repulsive force from A to O
  • do the same for B and C
  • apply the force vector to O
The constant d is a given min distance among peers.
Results of the algorithm are shown in the figure.

Forming a Grid Lattice

Different methods for obtaining a grid formation where experienced, however one only seems to show a better adaptability to change among others and it’s based on the use of a square formation algorithm and the line formation algorithm.
Since every agent has the ability to perceive each of his peers position in space, he can calculate the swarm center with a simple average function, then he can find which agents are the vertex of the grid by evaluating the distance vector of each agent from the center.
  • If the agent computing is a vertex, then he executes the square formation algorithm that constrains the x and z coordinates of the agents to form a square. Consider the agent A  vertex of the square formed by the agents ABCD, where A is the upper left edge, B the upper right and so on. A moves on the z axis so that it’s z is the same as B z, the same thing is done for the x coordinate.
  • If the agent computing is not a vertex he still has to compute the square based on his peers position, then he mentally divides the square in vertical zones, based on a given number v of desired vertical lines. Once the agent knows in which vertical zone of the grid he’s in, he execute the line segment algorithm only considering peers in the same vertical zone. If the agent is the edge of the segment, then he should move so that it’s x is the same as the square vertex.
Given an uniformed distribution of the agents in the space, these method produces a pretty good result, however one can surely notice that selection of the vertical zone is based on the initial position of the agent, so a correct grid needs a new force that can better arrange the agents during the formation.
This method is essentially different from the one proposed by [3] that implies an automatic or user controlled coordination between agents to create the grid. Comparison of the two methods is shown in the figures.

Swarm Control and Behaviour

The use of motor schema revealed fundamental for implementing different forces used for both control based on the user input and autonomous behaviour as evading objects.  

Control Forces

  • Movement: Used to move a selected agent in the scene.
  • Rotation: Makes the agent move around the center of the swarm, due to the algorithms used this force interferes with all the formations except for the circle.
  • Expansion/Contraction: The agent move away or towards the center of the swarm.

Other Forces

  • Formation: The force that permits the agent to form a specific pattern with his peers.
  • Avoidance: A force added when the agent perceives an obstacle on his way, due to the nature of the circle and dispersion algorithm the constraint imposed on a single agent prevents the whole swarm to move towards an obstacle. This isn’t the case for the line and grid formation however, that can move near and through obstacles and then recreate their formation. A trigger can be used for circle and dispersion formation to drop the use of the formation force when they perceive something on their path.
  • Separation: A simple separation force among the peers.

Conclusion

In this document we have presented ways of dealing with shape formation of a swarm in respect of some simple patterns like a circle, a line, a grid lattice and an uniform sparse formation. Building and using the application is a mean for experimenting with swarm control and their properties in a virtual environment.
The usage of a potential field pattern for aggregation of the precalculated forces in play revealed as the basic method to control the swarm with no particular drawbacks experienced.
This decentralized approach offers properties of adaptability to the environment, every agent works on his own in respect of the position of his peers, there’s no explicit leader or reference to other agents, as a result of this, there’s not a single point of failure and no need to update references when an agent is destroyed or created.
As the application is an interesting tool to experience with swarm shape formation, it’s always righteous to remember that problems arises when similar techniques are used in the real physical world.

References

[1] Distributed Algorithms for Formation of Geometric Patterns with Many Mobile Robots of Kazuo Sugihara and lchiro Suzuki*- Journal of Robotic Systems 13(3), 127-139 (1996) John Wiley & Sons, inc.
[2] Dispersion and Line Formation in Artificial Swarm Intelligence DONGHWA JEONG and KIJU LEE, Case Western Reserve University
[3] Decentralized control of multi-agent systems for swarming with a given geometric pattern - Teddy M. Cheng∗, Andrey V. Savkin

Download Link

Windows build of the application
C# class for shape formation

sabato 23 maggio 2015

Complex behaviours for autonomous agents in Unity

Intro
This tutorial is the continuation of the precedent blog post about the 3rd person shooter and doesn't follow the Unite2014 tutorial.

Extensions
Starting where the Unite2014 tutorial left off, we are going to extend the project by adding to it some new functionality. The idea is to straighten our programming skills by looking at some ground technique of game programming like steering behaviours.

Behaviour State Automata

Finite State Machines (FSM) can be considered as one of the simplest AI model form, and are commonly used in the majority of games. A state machine basically consists of a finite number of states that are connected in a graph by the transitions between them. A game entity starts with an initial state, and then looks out for the events and rules that will trigger a transition to another state. A game entity can only be in exactly one state at any given time, however it doesn’t mean the entity can exhibit only one active behavior at a time, we can see a state as a collection of multiple active behaviours that work together for the entity (like walking and in the meantime looking for the player).

Let’s give a more complex behaviour to our autonomous enemies, there are a lot of ways to make them smarter, what are we gonna do is follow the automata below and implement all the single behaviours.
  • Chase Player: is a behaviour made of other sub behaviours, basically the entity chases the player while performing:
    • Avoid Shooting: the entity tries to avoid shooting from the player.
    • Search Leader: the entity searches for a leader to follow.
  • Leader Following: the enemy gets behind a leader that uses as a cover from player bullets.
  • Stop Moving: the enemy stops moving when he or the player dies.

This diagram will be the model for our enemy agent, in the next sections we are gonna extend the enemy functionality by adding one after another the different behaviours shown in the diagram, in respect of the transitions for commuting from a behaviour to another.

We aren’t gonna use a framework or extensions for creating the complex behaviour, since the states are few, we can just use some simple conditions clause.

Steering Behaviours

Steering behaviors aim to help autonomous characters move in a realistic manner, by using simple forces that are combined to produce life-like, improvisational navigation around the characters' environment. They are not based on complex strategies involving path planning or global calculations, but instead use local information, such as neighbors' forces. This makes them simple to understand and implement, but still able to produce very complex movement patterns.

Leader Following

The leader following behavior is a composition of other steering forces, all arranged to make a group of characters follow a specific character (the leader). To achieve this we could easily use a seeking behaviour and make the enemy follow the leader with the navigation mesh component, but the result wouldn’t be good enough.

Movement Manager

We want our code to be most flexible and reusable as possible, since the behaviours we are gonna make can be used in our future projects, we would like to implement it in a separate class that will hold all the knowledge.

Still we miss a clear aspect of the entities we want to guide with our Movement Manager, we need an interface.

  1. Create in Script/Interfaces a new c# script called MovementInterfaces.cs
  2. Define your moving entity

public interface IMovingEntity {
Vector3 GetPosition();
Vector3 GetVelocity();
double GetMaxVelocity();
GameObject GetGameObject();
}

3. We’ll now extend our EnemyMovement script to provide such functionalities:

//IMovingEntity

public Vector3 GetPosition(){
return transform.position;
}

public Vector3 GetVelocity(){
return nav.velocity;
}

public double GetMaxVelocity(){
return float.PositiveInfinity;
}

public GameObject GetGameObject(){
return gameObject;
}

4. Finally we’ll create our MovementManager.cs class

public static class MovementManager {

/*
* This static class will be about steering behaviours.
* Collection of functions that return a vector based on some expected behaviour.
*/
}

Following

The first steering behaviour we are gonna write is the following behaviour, in which the entity is attracted to the leader’s back.

First thing we are gonna do is write an utility function to get a point that is behind of a moving entity. A character should try to stay slightly behind the leader during the following process, like an army staying behind its commander. The point to follow (named behind) can be easily calculated based on the target's velocity, since it also represents the character direction.

public static Vector3 GetBehindPosition(IMovingEntity entity, float distance){
Vector3 direction = entity.GetVelocity ();
Vector3 nd = direction.normalized * -1;
return (nd * distance) + entity.GetPosition();
}


Now that we have a way to get this point from a MovingEntity we need a function to get a vector that points in that direction with a given force. This is can be obtained easily by subtracting two point in space.

//vector that points from start to target point
public static Vector3 GetTowardsVector(Vector3 start, Vector3 target){
return (target - start);
}

Now let’s put all together for our public steering behaviour.

public static Vector3 GetFollowLeaderVector(IMovingEntity follower,IMovingEntity leader, float leaderBehindDistance){
//get the point to follow
Vector3 behindp = GetBehindPosition (leader, leaderBehindDistance);
//find the direction vector
return GetTowardsVector (follower.GetPosition (), behindp);
}

Let’s return to our EnemyMovement script and make some changes, so that we can test this out.

First we need to assign to our enemy entity the leader to follow, for now let’s use a public GameObject reference to the leader.

public GameObject leaderReference;
IMovingEntity leader;
EnemyHealth leadeHealt; // we have to know if the leader is alive

   void Awake ()
   {
       player = GameObject.FindGameObjectWithTag ("Player").transform;
       playerHealth = player.GetComponent <PlayerHealth> ();
       enemyHealth = GetComponent <EnemyHealth> ();
       nav = GetComponent <NavMeshAgent> ();
pm = player.GetComponent<PlayerMovement> ();
AssignLeader (leaderReference);
   }


void AssignLeader(GameObject l){
leaderReference = l;
leader = leaderReference.GetComponent<EnemyMovement> ();
leaderHealth = leaderReference.GetComponent<EnemyHealth> ();
}

Let’s remember that a steering behaviour is mostly a combination of difference force summed together, we’ll then use a steering vector to move the enemy obtained as the sum of the precalculated behaviours.

//Behaviour state in witch we follow our group leader
void LeaderFollowing(){
//Get the steering force
Vector3 steering = Vector3.zero;
//follow the leader
Vector3 followingForce = MovementManager.GetFollowLeaderVector (this, leader, 1.8f);
steering += followingForce;

//move the player to the pointed direction using navMesh
Vector3 targetPoint = transform.position + steering;

//Debug Rays
Debug.DrawRay (GetPosition (), followingForce, Color.magenta);
Debug.DrawRay (GetPosition (), steering, Color.yellow);

//Move to destination
nav.SetDestination (targetPoint);
}

We’ll still move the player using the navigation mesh since we don’t want to lose the privilege of evading obstacles.

Now we have to call the function LeaderFollowing in the Update() funciton.

   void Update ()
   {
       if(enemyHealth.currentHealth > 0 && playerHealth.currentHealth > 0)
       {
           
if(leaderHealth != null && !leaderHealth.IsDead())
LeaderFollowing();
else{
//Chase the player
nav.SetDestination (player.position);
}
       }
       else
       {
           nav.enabled = false;
       }
   }

Let’s disable the EnemyManager script, place some enemy on screen (the hellophant makes the perfect leader) and test this out (remember to assign the leader reference).

Separation

The first thing we notice is that all the enemies try to reach the exact spot and form a “blob”, we want to avoid crowding since it doesn’t feel natural this way. This can be avoided using separation, one of the rules of flocking behaviour.

What we are gonna do is to calculate a separation force obtained by the presence of different close entities, this force help entities to keep a distance from each other.

First, let’s build an utility function that gets the close neighborhood from our entity in a specific range.

public static List<GameObject> FindNeighborhood(Vector3 point, float radius, string findingTag){
GameObject[] gos = GameObject.FindGameObjectsWithTag (findingTag);
List<GameObject> neighbor = new List<GameObject> ();
foreach (GameObject go in gos) {
if((go.transform.position - point).magnitude < radius)
neighbor.Add(go);
}
return neighbor;
}

We can get the length of a vector using magnitude, we then use a specific Tag to identify all the enemies in our scene, just remember to change the enemies prefab tag property from the inspector before testing.

Now getting the separation force is easy! All we need to do is get the sum of distances from the entity to the neighborhood, then we normalize, scale and invert the vector.

public static Vector3 GetSeparationVector(IMovingEntity entity, float maxSeparation, float radius, string separationEntityTag){
Vector3 pos = entity.GetPosition ();
List<GameObject> neighbor = FindNeighborhood (pos, radius, separationEntityTag);
Vector3 force = Vector3.zero;
foreach(GameObject go in neighbor){
force += (go.transform.position - pos);
}
// equipare the force
force /= neighbor.Count;
// scale it
force *= maxSeparation;
//return the inverse
return force * -1;
}

Let’s change EnemyMovement and test this out.

void LeaderFollowing(){
//Get the steering force
Vector3 steering = Vector3.zero;
//follow the leader
Vector3 followingForce = MovementManager.GetFollowLeaderVector (this, leader, 1.8f);
steering += followingForce;
//have a bit of separation
Vector3 separationForce = MovementManager.GetSeparationVector (this, enemySeparation, 2.5f, "Enemy");
steering += separationForce;

steering += evadingForce;

//move the player to the pointed direction using navMesh
Vector3 targetPoint = transform.position + steering;

Debug.DrawRay (GetPosition (), followingForce, Color.magenta);
Debug.DrawRay (GetPosition (), separationForce, Color.white);
Debug.DrawRay (GetPosition (), steering, Color.yellow);

nav.SetDestination (targetPoint);
}

Remember to assign the Enemy tag to your enemies. Play with the enemySeparation parameter to get the desired separation degree.

If it does look like a Michael Jackson video, you are good to go!

Avoidance

Separation seems to give a more natural feel at the movement of our crew, but still you can see that something seems off. When the entities are in front of our leader they shouldn’t block is path, instead they should quickly move away.

To achieve this we are gonna use a similar technique to the seeking one, but instead of reaching a point, we are gonna get away from it. This point will be in front of the player and a force will be given only if the entity is close enough to it.
First thing to do is an utility function to get a point in front of an entity.

public static Vector3 GetFrontPosition(IMovingEntity entity, float distance){
Vector3 direction = entity.GetVelocity ();
Vector3 nd = direction.normalized;
return (nd * distance) + entity.GetPosition();
}
Then we write our behaviour function:

public static Vector3 GetEvadeFrontLeaderVector(IMovingEntity entity, IMovingEntity leader, float evadingPointRadius){
Vector3 force = Vector3.zero;
Vector3 ep = entity.GetPosition ();
Vector3 lfront = GetFrontPosition (leader, evadingPointRadius);
//if the entity stands in the leader way
if ((lfront - ep).magnitude <= evadingPointRadius) {
force = GetOppositeVector(ep, lfront).normalized;
}
return force;
}

To go in the opposite direction of a point we can do:
//vector that point the opposite direction of the evadePoint
public static Vector3 GetOppositeVector(Vector3 start, Vector3 evadePoint){
return GetTowardsVector (start, evadePoint) * -1;
}

Let’s edit the EnemyMovement script to add the new behaviour and test it out.

void LeaderFollowing(){
//Get the steering force
Vector3 steering = Vector3.zero;
//follow the leader
Vector3 followingForce = MovementManager.GetFollowLeaderVector (this, leader, 1.8f);
steering += followingForce;
//have a bit of separation
Vector3 separationForce = MovementManager.GetSeparationVector (this, enemySeparation, 2.5f, "Enemy");
steering += separationForce;
//don't get in the laeder way
Vector3 evadingForce = MovementManager.GetEvadeFrontLeaderVector (this, leader, leaderDistanceFront);
evadingForce *= leaderEvadingFrontForce;
steering += evadingForce;

//move the player to the pointed direction using navMesh
Vector3 targetPoint = transform.position + steering;

Debug.DrawRay (GetPosition (), followingForce, Color.magenta);
Debug.DrawRay (GetPosition (), separationForce, Color.white);
Debug.DrawRay (GetPosition (), evadingForce, Color.red);
Debug.DrawRay (GetPosition (), steering, Color.yellow);
nav.SetDestination (targetPoint);
}

Play with the leaderDistance parameter to get the right result.

Avoiding Player Shooting

Looks like we have everything we need to make this work real fast! All we have to do is apply the avoidance technique to a point in front of the player while our enemy is in the chasing player state.

First thing first, let PlayerMovement implement IMovingEntity, then we just write a better version of the ChasePlayer function.

void ChasePlayer(){
//Chase player
Vector3 steering = Vector3.zero;
//by default go towards the player
steering += MovementManager.GetTowardsVector (transform.position, player.position);
//try to evade being shot
Vector3 evadingForce = Vector3.zero;
if (evadePlayerSight) {
evadingForce = MovementManager.GetEvadeFrontLeaderVector (this, pm, 2.5f) * 5.0f;
steering += evadingForce;
}

nav.SetDestination (transform.position + steering);

Debug.DrawRay (transform.position, steering, Color.gray);
Debug.DrawRay (transform.position, evadingForce, Color.blue);
}

At first inspection the enemies seems to behave correctly, however with a little bit more of testing you can easily notice that something seems off. The enemies sometimes seems to run away from the player and there are moment where the sum of the two forces would be zero and therefore they will not really move, making them a simple target. That’s because enemies are currently avoiding a point, when they really should be avoid a sight.


We then need to add a new function to our MovementManager, one that moves the evader away from an entity sight adding a force that is perpendicular to the sight direction.

First off let’s write a function that given a direction it returns a force that’s perpendicular to that direction.

//(Uses Vector3.up)
//gets the perpendicular vector of a direction in xz with a force
public static Vector3 GetCrossVector(Vector3 direction, float force){
//this is because in the 3d scene there are infinite perpendicular vectors to direction
Vector3 crossv = Vector3.Cross (direction, Vector3.up).normalized;
return crossv * force;
}

Vector3.Cross() returns the cross product of the two vectors, the reason we are using Vector.up is because we are interested in the direction that lays on the xz plane, in the 3d space there are infinite perpendicular lines passing through a single line.

Now let’s write the function to avoid an entity sight, have in mind there are multiple ways of doing this, we could have used a raycast for example, but we are gonna do it in a more geometric fashion way, to have a little bit of more flexibility for the sight range of the entity.

//Move away from the entity sight
public static Vector3 GetAvoidDirectionVector(IMovingEntity entity, IMovingEntity evader, float range, float force){
Vector3 avoidv = Vector3.zero;
Transform entityTransform = entity.GetGameObject ().transform;
Vector3 dirVec = entityTransform.forward.normalized; //the direction the entity is facing
Vector3 crossVec = Vector3.Cross (dirVec, evader.GetPosition () - entity.GetPosition ());
//notice that the direction of crossVec is up or down in the y axis, but it's the distance (magnitute) we are interested in.
float dist = crossVec.magnitude; // distance from the direction vector
//Is it in front of the entity?
Vector3 relativePoint = entityTransform.InverseTransformPoint (evader.GetPosition ());
if (dist < range && relativePoint.z > 0.0f) {
avoidv = GetCrossVector(dirVec, force);
if(relativePoint.x > 0.0f)
avoidv *= -1;
}
//Debug.Log("rp.z: " +relativePoint.x);
return avoidv;
}

We use the cross product to find the shortest distance between the sight direction and the evader position point, it could seem a little bit tricky since the result is a vector that points up or down in the y axis, however the magnitude of the result is exactly what we are looking for.

The simple condition dist < range for triggering the force wouldn’t be enough, since we also need to check if the evader is in front of the player and not behind. The easiest way of checking the position of the evader relative to the entity is to use and inverse transformation of the evader position in respect of the entity position. Basically we are gonna get the position of the evader relative to the entity position, moving the axis (center) of the world to the entity position.
We are moving in the xz plane, so everything that has a relative position.z greater than zero is in front of the player.

Also, the perpendicular force we are adding has a fixed direction, we need to invert the direction somehow, so let’s use the relative x axis.

Test the game, and see how the enemies behave, isn’t it way better than before?

Stochastic Behaviour

If the game becomes too much predictable, it’s no longer fun. A game should always provide a good challenge to the player and sometimes adding a bit of probability is an easy way to deal with boring and repetitive patterns our autonomous entities could fall into. In the same way, having an IA too complex could make the game too hard for the player: magine an enemy bot in an FPS game that can always kill the player with a headshot, an opponent in a racing game that always chooses the best route, and overtakes without collision with any obstacle, etc.

The following are the main situations when we would want to let our AI entities change a random decision:
  • Non-intentional: This situation is sometimes a game agent, or perhaps an NPC might need to make a decision randomly, just because it doesn't have enough information to make a perfect decision, and/or it doesn't really matter what decision it makes. Simply making a decision randomly and hoping for the best result is the way to go in such a situation.
  • Intentional: This situation is for perfect AI and stupid AI. As we discussed in the previous examples, we will need to add some randomness purposely, just to make them more realistic, and also to match the difficulty level that the player is comfortable with. Such randomness and probability could be used for things such as hit probabilities, plus or minus random damage on top of base damage. Using randomness and probability we can add a sense of realistic uncertainty to our game and make our AI system somewhat unpredictable.

Searching for the leader

In our project we haven’t still defined the Searching Leader sub behaviour of Chasing Player, what we would like to do is to have the enemy to find and assign the leader autonomously.

The enemy should then be able to distinguish the leader from the common enemies and perceive when one of his kind is near him. This could be easily done by reusing the code of the previous chapter.

  1. Create a new tag called EnemyLeader and assign it to the hellophant prefab
  2. Click apply to save changes to all hellophant instances
  3. Open EnemyMovement script
    1. comment the AssignLeader() call in the awake function
    2. and define our behaviour

void SearchLeader(){

List<GameObject> leaderList = MovementManager.FindNeighborhood (GetPosition (), 8.0f, "EnemyLeader");
if (leaderList.Count > 0) {
//Found a leader, follow him!
AssignLeader(leaderList[0]);
Debug.Log("Assigned leader " + leaderReference.name);
}
}

4. Let’s update the ChasePlayer() function to call SearchLeader

//search for leader
if(leaderReference == null && tag != "EnemyLeader")
SearchLeader();

Let’s hide the hellopant team we used for testing and restore the enemy spawner. What we notice is that every enemy beside the leaders shows the same behaviour, the game should look “smart” and “hard”, however it’s not hard to kill the enemies this way, all you have to do is stay away from the hellophants and kill the enemies behind, or just simply run and kill one team of hellophants at a time.

What can we do to spice up the game now? A little bit of randomness it’s not hard to implement and can give the game that bit of unpredictability every game needs to not look boring in the eyes of the player.

Our enemies should be able to decide themselves if they are gonna start following the leader or simply chase the player. Let’s say they decision is taken at random.

//Has choose to follow the leader or not?
bool choiceMade = false;
public float followingLeaderChance = 30.0f;

void SearchLeader(){
//do nothing if I've already decided
//I stick with my decision
if (choiceMade)
return;

List<GameObject> leaderList = MovementManager.FindNeighborhood (GetPosition (), 8.0f, "EnemyLeader");
if (leaderList.Count > 0) {
//Consider to follow the leader
//Let's say there's a 30% chance
float val = Random.Range(0,100);
if(val <= followingLeaderChance){
AssignLeader(leaderList[0]);
Debug.Log("Assigned leader " + leaderReference.name);
}
choiceMade = true;
}
}

We used the Random.Range function to generate a random number between 0 and 100, then we follow the leader only if this number is lower than the followingLeaderChange public variable. If we decided to follow or not the leader, we stick with this choice, we are gonna stick with this choice.

Let’s test the game, what do you think about it? Does it feel a little bit harder?

Optimization (Optional)

Right now there are dozens of enemy, everyone repeating the same pattern in the update function. Every time an enemy is searching for a leader he is basically using a loop into the game loop only to do a proximity check, and it is done for every enemy in 1/60 of a second. Can we do something to ease this enemy functionality so that it is done only once in a while? We could use coroutines!

Coroutines
A coroutine is like a function that has the ability to pause execution and return control to Unity but then to continue where it left off on the following frame. It is essentially a function declared with a return type of IEnumerator and with the yield return statement included somewhere in the body. The yield return line is the point at which execution will pause and be resumed the following frame. To set a coroutine running, you need to use the StartCoroutine function.

This can be used as a way to spread an effect over a period time but it is also a useful optimization. Many tasks in a game need to be carried out periodically and the most obvious way to do this is to include them in the Update function. However, this function will typically be called many times per second. When a task doesn’t need to be repeated quite so frequently, you can put it in a coroutine to get an update regularly but not every single frame.

We write our code as follow:

private bool coroutineStarted = false;

IEnumerator SearchLeader(){
while (true) {
//do nothing if I've already decided
//I stick with my decision
//if (choiceMade)
// return;

Debug.Log("Searching..");

List<GameObject> leaderList = MovementManager.FindNeighborhood (GetPosition (), 8.0f, "EnemyLeader");
if (leaderList.Count > 0) {
//Consider to follow the leader
//Let's say there's a 30% chance
float val = Random.Range (0, 100);
if (val <= followingLeaderChance) {
AssignLeader (leaderList [0]);
Debug.Log ("Assigned leader " + leaderReference.name);
}
//choiceMade = true;
StopCoroutine("SearchLeader");
}
yield return new WaitForSeconds(1.2f);
}
}

To be invoked the method must return an IEnumerator. We start our coroutine when needed and only once as follow:

if (!coroutineStarted) {
coroutineStarted = true;
StartCoroutine ("SearchLeader");
}


References