In this third part of the flocking workshop, we're going to create fish that can detect obstacles in their
environment and turn away from them.
Now to begin with, what I've created here is exactly the same program that you've had before from the previous
one but I've added in a ground plane with some sand texture on it and this pillar, which will be our obstacle.
You can add anything into the scene as an obstacle.
This particular pillar I got from the Asset Store here, it's a free one, it's called Stylize Sand Pillar,
if you want to use it. The first thing that you'll need to do is to add a collider to your object, so select
your object and go over to the Inspector...[Add Component]...Box Collider.
So we're going to test if the fish is about to collide with these objects.
It's a bit different to the bounding box testers that we were doing before. To thoroughly test this,
you'll also need to make sure that the fish are going to swim through your object.
So to begin with, we'll go to the FlockManager and the FlockManager
in my case here is just sitting above this pillar.
I'm going to move it down so that it's inside of that pillar and the fish need to try and swim through
it in order to get to the FlockManager's position as their goal position and to make sure that doesn't
change while we're testing,
you'll need to go to your FlockManager code and in the random part in this Update,
let's just comment out our setting of the random goal location and just use the goal location that is
set to the FlockManager.
So if you just save that, go back and Play to test that the fish actually swim through your object, you
can see them now, starting to swim towards the goal of the FlockManager and going straight through the
collider.
What's really important at this point is because we put a Box Collider on our pillar, we don't want a physics
collider on the fish because we're not going to do it that way.
So make sure that your fish don't have a collider.
If you need your flocking objects to have a collider on them, you will have to use different physics
layers to make sure that the fish do not collide with your objects in the scene.
Now the way we're going to achieve the avoidance of obstacles with a physics collider on them is to do
a physics raycast from the position of each fish along its forward vector.
We then test if this ray has collided with something and if it has, we turn the fish away.
We'll integrate this functionality into the Flock script, so open up your Flock script. In the Update,
where we're determining if we're still within our bounding box (which was here),
we'll add a second test which is our physics raycast and it's going to be an 'OR' in this if-statement.
So if we're outside of the box OR we've hit something with our raycast. To perform a raycast, we
need to create our RaycastHit item 'hit' and then we use Physics.Raycast, the position where
the ray is starting from and then the direction of the ray (in this case, it's going to be the forward
direction of your fish, multiplied by how long you want that ray to be).
Now if you make this quite long, the fish will think it's colliding with something when it's quite a
long way away from it.
If you make it short, then the fish will get right up close to it before it
decides it needs to turn.
So this one here (the forward vector itself) is only going to be a length of 1.
So it's already normalized and then multiplying it by...it's up to you, it's how much you want the fish
to react based on how close they are and then we're grabbing hold of the hit point of our raycast. Now
there is another raycast function that you can run that just tells you that you have hit something
and doesn't return where you've hit
but we'll be using this hit point a little bit later.
Now that's our 'OR' part
and then we go to
'turning = true' if our raycast has hit something.
Now, what I'm just going to do under here is put in a little debug line (and you might like to do this
too) because when you're trying to debug A.I. it's an absolute headache
if you're sitting there looking at the screen and trying to figure out what your characters are doing and
whether they're doing the right thing. In this case, we want to know WHEN the fish is turning and we want
to know WHY the fish is turning.
So I'm going to draw the actual ray that the fish is projecting out when it's in turning mode and that's
going to help us a lot.
So just save that once you've done it and then we'll go back into Unity and press Play.
Ok.
Now you can see a lot of red lines going on. These are
your raycasts
of fish when they're turning, so if they're facing towards the pillar, then their raycast when
it hits the pillar, it will show up as red and it will indicate that they're going into turning mode
as well.
Now you can see, in this case, they're circling around it, they've gotten into this sort of rhythm where they're
not turning into here.
We could go into the FlockManager and make their rotational speed stronger, so they will turn.
You can see there. Now they're all turning into that pillar but they don't appear to be turning away from
the pillar.
The reason for that is the way we've coded where they're going to turn TO.
If you remember
(let's go back into the code) when we're turning, we're going to turn towards the center of the
FlockManager and the center of the FlockManager is also the goal
and we've currently got it sitting right in the middle of our pillar.
So this isn't going to work, in this case because every time our ray collides
with something, it's going to say
"Ok,
I have to turn around but I have to turn around towards the center of my manager"
and therefore the thing we're trying to turn away is conflicting with where we're going to turn to.
So we have to come up with a different location and a different goal for our fish 'on turning'
and for that, we'll take a little bit of inspiration from a game of billiards.
When the fish's raycast hits the obstacle, it does so at a particular angle.
Now if the fish were a ball, you could predict how it would bounce off the surface of the obstacle just
like a billiard ball bounces off the side of a table. From physics, we know that the angle of incidence (a)
equals the angle of reflection (b).
Therefore, we can calculate a new goal location for our fish using the vector traveling away from the
obstacle.
In other words, we want to align the fish's forward vector with the reflection vector. To calculate the
reflection vector, we make use of the hit point that the raycast generates and the 'reflect' method available
in Unity. The reflect method requires the incoming vector and the normal of the surface that was hit and
returns the outgoing vector. To put this into our code, we go back into the Flock.cs
script, in the Update method and what we're going to do is recalculate this direction based on what's
happening.
So if you've hit the side of the container (you've come outside of the bounding box) then we want the direction
to be back to the center of that box.
Otherwise if you've hit something, then you want to turn around according to the reflection vector.
So let's begin up here. After the RaycastHit,
notice I've also just added in this new RaycastHit here, so that I don't get an undefined hit when
I go to use it later on.
So we set it to at least something and initialize it. Under here,
I'm going to move this 'direction' from down in 'turning'. Let's take that out of there
and put it up here.
Now you don't have to set it to anything here and you could set it to 0 because it's going to be
defined in the next two if-statements.
So after that (let me put a space to make it clearer) we'll do 2 'if' tests.
We won't use this 'OR' any more.
So we'll get rid of the 'OR', we'll make that a self-contained if-statement.
So if the position is outside the bounding box, then we're going to put 'turning = true'
and at this point is where we want direction to equal THAT.
Now it's already set to that, so if it's set to that you don't need to set it to that again. If you set
that to 0 up there, then you could take THIS, put it down here
and then just set this to Vector3.zero;
Now the next if-statement will be an 'else if'
on the physics raycast and in this case, if it has hit something, then turning again is true, we'll remove our
debug line because we don't need that anymore and instead 'direction = Vector3.Reflect'.
This is the Reflect method in Unity that we can use and the Reflect happens given an incoming vector (which
is our forward vector on our fish) and the normal to the hit (which will be hit.normal).
So the RaycastHit contains all sorts of information about the position that was hit,
it's normal and a whole bunch of other stuff that you can look up in the Unity manual.
This will now create 'direction' to be our reflection vector which will get used down here
IF we are turning so direction's still down here, we haven't made any changes to that whatsoever.
So save that and now we can go and see what we've got.
Press Play and we will now find that our fish do not swim through the pillar.
They might cut the edge and they might have parts of them that go through the pillar because remember
we're doing a raycast, so that's a 'single line' out to check whether we've hit something. We're not checking
for their entire body having hit something.
You could put a collider on it and have the fish turn around when it hits. You'd have to make
that collider a little bit longer than the fish, so it knew when it hit something and then start to turn
but this is a very cheap way (processor-wise) to do it
and you can see here, now that we've got these fish (and if I go back to my flock manager and just increase
their rotation speed to get them getting closer to each other)
they are avoiding the column.
Let's go back to the FlockManager code and put back our random locations for our goalPos in the Update
that we took out before. Let's save that and now we can also add in another flock.
So what I'll do is grab that FlockManager, we'll move it outside the column and then do a Ctrl-D
to duplicate it and might just move it over there
and the prefabs, in this case, I'll make the pink fish.
So the pink fish
you should already have included and it will have the Flock script attached to it.
Ok, so let's press Play and now we have 2 separate flocks of fish
and you can change the behaviors of each flock just by setting their Rotation Speed, their Neighbour Distance, their
Maximum Speed...from here,
the possibilities are pretty much endless.
You can have a really nice aquarium set up with this flocking
and you could also replace the fish with birds if you wanted something in the sea as well.
So I hope you enjoyed that because flocking is really my favorite A.I. algorithm
and I just find it absolutely mesmerizing, the results you get from such simple rules.