In this lecture, we will examine 'Flocking'.
Flocking has to be one of my favorite A.I. techniques.
It involves simple rules that generate complex behaviors that are mesmerizing. Flocking can be used to
procedurally generate the movement of groups of individuals that like to stay together, while moving
towards a common goal.
The flight of birds comes to mind but this technique can be used pretty much anywhere you have a group
that needs complex moving behaviors that you don't want to program by hand.
In this workshop, we'll be creating flocking (or schooling) fish. To follow along, download the 'AIfish' Unity
package that comes with this lecture.
Open up a new Unity project for 3D and bring that package in.
When you add it into the Assets, you'll find that there is a green fish, a pink fish and a scene called Swimming. If you
open up the Swimming scene itself,
you'll get the green fish all by itself with the camera set up pointing at it
and if you press Play, you'll find that that green fish has a swimming animation attached to it.
Now, in order to create flocking you need more than 1 fish
in this case because flocking is a group activity.
So we're going to create a Flocking Manager
and in the Hierarchy, just right-click in there and add an empty and call it FlockManager.
This is not unlike the other Managers that we've had before for other agents, where we wanted to create
multiple versions of them so that we can send messages and keep track of them.
In this case, the fish need to know about each other, so the FlockManager is going to create a whole bunch
of fish and then be able to tell all of the individuals about the other fish in the flock (or school
I guess, but I'm calling it flock).
Right, so we've got our FlockManager, which is an 'empty'. Create a C# script and call it FlockManager
and then that script will be attached to our FlockManager.
Now we're going to open up that script and add in this code. The code is quite simple.
It creates an array of fish, instantiates all of those fish objects and puts them at a random location
around the location of your FlockManager.
Right.
So first of all you'll need an exposed variable, public fishPrefab and we pass in our fish prefab
here (which can be your green fish or the pink fish or whatever else). You can also do this with cubes, it won't
make any difference
and then a public numFish that we've got set to 20 (but this will allow you to change the number of
fish you've got in a particular flock, through the Inspector).
We're then going to create our array of allFish
and so as we instantiate them, they're going to go into this allFish and then we have a Vector3
called swimLimits.
This is the range of locations that the fish can be generated within.
So this is like a box that is around your flock manager and it's 5x5x5 which makes it
10x10x10 because this is a radius value
and then we'll use the swimLimits to generate the location of our fish within that box.
So this is all happening down in Start.
First of all, we create all of the fish and we make the array big enough to hold them.
Then there's the 'for' loop, that loops around that number of times and each time creates a position to
put the fish and this position is based on the position of the flock manager plus a random
Vector3 value that is around those swimLimits (so between 'minus swimLimits' and 'positive swimLimits') which will
give you the range of positions.
Now if you want it to be a bigger box of positions, then you would just make these values bigger here or
you could make them smaller if you want a tight-knit group.
Then, once we've got this position that we've calculated, we then put the fish (we instantiate it from our prefab)
at the position that we just calculated, using a neutral rotation.
You could also set this to the fish prefab rotation
if you've got them pointing in a particular direction, it doesn't really matter
and that will fill up this array.
So if you save that, flick back into Unity, select your FlockManager and you want to set the fish prefab.
The fish prefab is not this green fish that's in the scene.
Ok.
So that's the one that was used to make the initial prefab.
In fact just delete it, so that you aren't confused by it and then grab hold of your fish prefab
(which is in your Assets) and drag-and-drop that into the FlockManager.
We're going to create 20 fish and it will fill up this array here.
So press Play and there's your 20 fish
(and if I zoom out, you'll see them) within the confines of the swim limits that we've created.
Now if you want more fish than that, let's say you wanted 50 fish...50 in here and press Play and you'll get 50 fish.
You can see them, they've all appeared in the Hierarchy and they're all in the Inspector under the
allFish array and that's
so each fish can find every other fish that's in their flock.
Ok, it's time to get the fish moving. To begin, we're going to set up some speed values for the fish and
we'll do that in the FlockManager. So we'll make the FlockManager a place where we can set all the
different variables for the particular fish that belong to that flock and then you can have
multiple flocks for different types of fish in your scene.
So I've been up the FlockManager script again. What you're going to do is add in some public values
as I've got here.
First of all, in the Inspector, I've just added a little 'Header' line, which sections it off and makes
it easy to find when you're trying to set them.
Then I've got a 'Range' here, between 0.0f and 5.0f which will give you a slider
in the Inspector and then public minSpeed and public maxSpeed, with the same values for the range.
Once you've added in those 5 lines, just save that and switch back over into Unity and click on the
Flock Manager and you'll find in the Inspector that we've now got those settings. We've got 'Fish Settings',
Min and Max Speed (which you can then change with the sliders as you like and you'll be able to do that
in real-time as well and pass through those speed values to the fish). For the fish, we'll now create a
new C# file and call it 'Flock'.
This is the code that goes on to the fish prefab, so drag-and-drop that onto your green fish and while we're here,
we'll drag-and-drop it onto the pink fish as well.
Just select the green fish down in the Assets and check over in the Inspector to make sure that script is actually
attached to it.
Now open it up and the first thing we'll need is a link back to our manager, so we'll create a
'public FlockManager myManager' and then we need a float for the speed.
Now in the Start, we're going to set speed to equal Random.Range between the managers minimum speed setting
and the managers maximum speed setting. In the Update,
we'll put a Translate statement to drive the fish forward along their forward axis or their Z axis.
So notice that the value of 'speed' is going into the Z axis.
If your fish happen to be facing in a different direction...
so if I just go back to my fish and grab a fish, so the blue axis here is facing forward and that's
the one that it's going to be pushing it along.
If your fish happens to have a different axis, then you'll have to adjust your code to be pushing it along
that axis instead.
Ok, so we'll delete that fish and go back to the code and just save that, then in your FlockManager
script, if we open that up, after you've created a fish inside of that for-loop, you want to set
it's FlockManager (or myManager) to this particular FlockManager.
So add this code in and now both of those files (the Flock and the FlockManager) will be linked together.
Back in the Editor, press Play and you'll see the results of the code.
So you've got your fish that are now going forward.
They have random speeds on them.
Of course, they are going to go out of scene very quickly.
You can see that they're moving at different speeds (if you can get in closer). Yep.
So they've all got different speeds, but they're all moving along
their Z axis. Ok, so now it's time to add in our 'flocking rules'.
I mentioned the flocking rules at the start of this section
but let's go over them again, in detail.
They are: Move towards the average position of the group; Align with the average heading of the group and
Avoid crowding other group members.
Moving towards the average position of the group involves taking all the individual members positions,
adding them together and dividing by the number in the group.
We can then work out where an individual is, in relation to the average location and have them turn towards
it. To align with the average heading of the group,
we add all the headings of the individuals together and divide by the size of the group.
This gives an average vector for individuals to try and align their movement. To avoid neighbours,
an individual must be aware of where the neighbours
are
and then turn away from them.
Then to calculate a new heading, an individual will add together the vector for moving towards the center
of a group, the vector to align with the groups heading and the vector to turn away from a neighbour.
Before we start editing the Flock code, we're going to just add a few more values into the
FlockManager (ones that you can change at runtime, so that you can see different behaviors in the fish)
and these settings for the behaviors will be available to all the fish in that particular
flock.
So I'm adding in here another Range for something called neighbourDistance and another Range for
rotationSpeed.
So once the fish start to get locations that they need to move towards, they're going to have to turn
around at some point and we'll be using a slerp for that and that needs a rotation speed,
so I've added that in there.
Now, the other thing with this neighbourDistance is that you can have a flock of something and everything
in that flock might be considered your neighbour
or you might only want to consider the closest few (within one meter of you) as your
actual neighbour, so you can therefore modify that and you get different behaviors out of the entire group
when they're only considering those immediately around them as neighbours rather than everybody.
Now for the changes to the Flock. Go to your Flock.cs code.
We're going to create a new method called 'ApplyRules' and that method is getting called down here
so there's no other changes to the top, just inside of the Update
and then we add in ApplyRules.
Now, this ApplyRules is doing those 3 rules that we just spoke about, with the different vector calculations.
First of all it's creating a holder, to get hold of all of the fish in the current flock.
So these are the fish that we want to try and find the average position, the average heading etc. and
they're the fish that will be considered your neighbours (or at least they're the whole set and the neighbours
will be part of that set, as you'll see in a minute).
Now we're calculating the average center of the groups, so we're going to store that in 'vcentre'. Then we're
calculating the average avoidance vector as well.
So it's not just 1 neighbour that we want to avoid,
it's EVERYBODY
and so that's also an average.
So you have to calculate all of the avoidance vectors and then average them out.
So initially they're set to 0.
Then we have this float called 'gSpeed'
and this is a global speed of the group (an average speed that the group
is moving in) and we'll use that in our calculations as well.
Then we have a float 'nDistance', which is our neighbour distance.
So we are going to have to ask each fish how far away they are and then work out if we consider them
to be close enough to be in our group
and so we have this 'groupSize' counter here, that will count how many of the fish in the flock are in
our group, so the group is like this small section of the flock that is within that neighbourDistance
value.
Now with these values initialized to 0, essentially, we're going to loop through all of the fish
in the flock.
So we use a foreach loop.
Then we're going to ask if the current fish (which is going to be this game object
here: go)
as long as that's not equal to the fish running this code at this time, then we're going to do the process
because you don't need to find the average of yourself, you don't have to calculate the distance
to yourself,
so this just makes sure we don't calculate fish's data for itself.
Then we work out the distance of the neighbour, so remember we're looping around this for-loop, picking out each fish
and then we're working out the distance with a Vector3.Distance between the game object in our arrays
position and our position in the world and then we're asking if that distance
is 'less than or equal to' the neighbourDistance, which makes that a fish of interest (one that we're going
to consider part of our close-knit group) then we will add that particular fish's position to the 'vcentre'.
vcentre will be our average position of our group,
so, therefore, we have to loop around and add all of the positions together
and then we divide it by the group size.
So at the same time we add it, we also increase the group size by 1.
So after this loop has gone through, we can calculate the average.
Next, we have an if-statement to test our neighbourDistance being less than a much smaller value, which
I've hard-coded here as 1.0
and we could, in fact, put this in the FlockManager as another distance value but this is how close
we're allowed to be to another fish before we should be considering avoiding it.
Now if, in this case, the distance is less than 1.0f, then the avoid average vector is going to be added to
and so it has it's own value + the vector away from that fish.
So the vector away from something (and we looked at this in a crowd simulation we wanted to move away)
was our current position minus the position we want to get away from
and so that adds up as we loop through here as well.
Now the global speed is adding together all the speeds of the particular fish from the flock.
So we go ahead and grab hold of the Flock code that's attached to the fish that we're looking at at
the moment and then we grab its speed and add it to this gSpeed value.
Now by the time the code finishes this foreach loop, we've looped through every fish in the flock and
we've examined it. If it's considered to be a close neighbour, then we're finding its position and also
its avoidance vector as well and adding all of that up.
Oh and of course the speed. Now underneath the foreach loop,
we have an if-statement. The if-statement is asking if the group size is > 0. If the fish
is moving along and there is no one around it and the group size becomes 0,
it's not going to apply these rules. It's just going to keep moving in a straight line.
So if this fish IS influenced by another fish (or another group of fish), we then find the average to the
center.
So that's the 'vcentre' vector that we calculated when we added them all together,
now we divide by the group size. The speed itself of the fish is set to the global speed,
divided by the group size.
With that information, we can work out the direction that the fish needs to travel in (or the direction
the fish wants to travel in).
So its 'direction' is equal to the 'vcentre array' + 'vavoid array' - our current position.
Now if that means that direction is 0,
we're already facing in the right direction.
If it's not equal to zero then we need to rotate
and so we do a transform.rotation and we use our Slerp that we've used before for other NPCs and
we do a little rotation according to our rotation speed
and what this will do is slowly turn the fish towards the direction it needs to. It's not going to snap
right around to the perfect direction
and it's this slerp (and slerping a little bit by little bit) and constantly updating your position and
checking where you are with respect to all of the other fish, that it causes this flocking to occur,
as you're about to see. Time on this video is about to run out, so quickly we'll just press Play (make
sure that in your FlockManager you do give a Neighbour Distance and a Rotation Speed, otherwise
you're not going to get any rotation at all
and then you'll start to see the flocking occurring.
We'll investigate this more in the next video.