Hi. Welcome back to this tutorial looking at how to create an NPCs Finite State Machine.
So far, we've created the Patrol for our little green tank.
Let's now create the Chase, however before we start doing that, I want to create a Base Class for our state
machines because we're going to need to have information about where the player is and where the NPC
is, to work out distances and angles and things like that and we can do that in each of our state machine
behaviors that we've added but we'll have to keep doing that over and over again.
So if we create a Base Class, we can put all that information in just once.
So let's go into the Assets and our project. We'll create another C# script - let's call that
NPCBaseFSM.
Ok.
Now
open that up and in it we'll add this code.
So notice it's not a MonoBehaviour, it's a StateMachineBehaviour and it's NPCBaseFSM
Inside of here, I'm going to set up a link to our NPC and also one for our opponent and then I'm
also putting in some properties for speed, rotational speed and accuracy as well so that we can change
those as we like, rather than having them hardcoded back in the previous code.
Now the function that we're really interested in overriding and we're using is OnStateEnter.
So every time we enter into a state, we want to grab hold of the NPC and the opponent
and you'll notice the opponent here is getting a component that's attached to the NPC itself called TankAI
and we will write this bit in a moment but this is getting hold of the player and we'll have the tank
character know about the actual player, so that it can turn and shoot and move towards it and that type
of thing.
So this is our Base Class and because we don't have this TankAI script written yet, you're going to
get an error saying that it can't find it when you save it.
So let's write that TankAI script now.
So back in our project, let's create another C# file and call it TankAI and open that up
and in this code we will add this.
So the beginnings of this TankAI script is that it has a link to the player
and then we have a public method call 'GetPlayer', which you saw called before, from our previous code and it's
going to return the 'player'.
So we can use that to work out the distance between the tank and the player a little later on.
So save that,
go back into Unity, what you want to do with that code is drag-and-drop it onto the Tank. Select the Tank in
the Hierarchy and you'll see the script attached.
Now it's got the Player space here, the exposed variable that we created.
Drag your Player from the Hierarchy into that space, so now that it knows and it's linked to it, it will
be able to work out its distance.
Now with that new Base Class, we need to fix our Patrol, so open up your Patrol and instead of inheriting
from the StateMachineBehaviour, we're going to inherit from NPCBaseFSM
and that's going to include our NPC, so we don't need that...we can remove it from inside of here.
We can also remove the setup from in here because it already is going to happen in our OnStateEnter
that's back in our Base Class
but to ensure that runs, we need to call it.
So let's add a line of code and to get back to our base classes OnStateEnter, we call base.OnStateEnter
and pass through the same parameters that are coming through to here and that will set up our
NPC.
Now the NPC is used in our OnStateUpdate and it's already in there.
The things that we do need to change in here,
remember we added in accuracy, speed and rotational speed?
So in the rotation here, we can put our rotSpeed in there (which is coming from this class, remember
at the top.
We've got speed, rotSpeed and accuracy) and this here's going to be speed
and then our accuracy is used up here to determine whether we need to go to the next waypoint, so we'll change
that to accuracy.
Now if you use those values for anything else in your code, then you can just come back to here and
change them on there for your NPC.
At this point it's probably worth saving everything and just testing that it still works, so
let's just press Play and the Patrol code is still working.
All right, so let's move on to getting our Chase state working. In your Animator, create a new state.
So, 'right-click > Create State > Empty' and we will call that 'Chase', so go over to the Inspector and give
it a new name. Now 'Chase' will get transitioned-to from Patrol, so right-click on Patrol and go
'Make Transition'.
You'll then get a little arrow that you can click on something else with. You want to click it on to
'Chase'.
This is your transition
and if you select it, you'll see over in the Inspector, a place where we can add our conditions.
The other thing to take note of in here also, is this 'Has Exit Time'. The Has Exit Time will wait until
patrol has finished
whatever it's doing in its Update in order to transition over to Chase.
Now, if you just want it to completely interrupt it, then get rid of it, untick it and then it won't
wait for that.
So if you find you've got lags between transitioning, it'll be because you've still got this Has Exit Time
ticked on all of your transitions and it's up to you whether you want it there or not.
Now the condition for going from Patrol-to-Chase, let's say it's going to be when the player gets within
a distance of 20 units.
So we need a parameter on here for 'distance'.
So over in the Animator, on the side, there's a Layers and there's a Parameters tab. You want to click Parameters
and then there's a little + sign, click that...we're going to add a Float.
So add a float and we'll call it 'distance'.
Initially we want the NPC to be a long way away from the player.
We don't want this to trigger immediately, so if 'distance' is set to 0, this will trigger immediately.
Let's set it to something ridiculous like 100 which is totally outside the realm
and then what we do here on the transition is click on it. Over in the Inspector, in Conditions, hit the +
and it'll immediately come up with distance because that's the only thing it knows about
and then 'Greater' than 0. We're saying that this condition is 'distance is Less than 20'.
So we'll put a 20 in there.
So when this value here gets to less than 20, the transition will happen and the Chase state will start.
The next thing we need to do is update what this distance here is,
otherwise it will never get down to below 20
and the distance will be based on the distance between us (our player) and our NPC.
Now you could calculate the distance between the player and the NPC inside of your state machine but
you want to be doing that over and over again and it's a condition that is used throughout your entire
state machine and therefore it makes sense to do it externally to this
and the code for that will go on the TankAI
instead. Doing it this way will also show you how to inject a value from your MonoBehaviour into
the animation system so that you can change that condition.
First of all, in the TankAI script, you're going to need to grab hold of the animator, so we'll create
an animator 'anim'
at the top here and then in the Start function, we'll set that up so that it points to the Animator
that's attached to your game object (or the tank
in this case) so it will be anim = GetComponent for the Animator...like that.
So now you have your Animator set up.
Now the distance is calculated between this object (the tank) and the player that you've got here
and so you can inject that into that 'distance' value in the Update, so you can constantly be working out
the distance and sending it through to the state machine.
So in Update,
let's add this piece of code...and what it's doing is it's taking your Animator and calling the SetFloat
method on it.
What that will do is set the distance value
(make sure it's in quotes here and it's spelled exactly the same as you have your parameter up there because it's referring
to that) and then whatever you put in the next part of the method will be the value that it's going to
change it to.
I'm using a Vector3.Distance to calculate the distance between the position of our tank and
the position of the player.
So let's have a look how this works.
Save that and switch back into Unity and press Play.
Now if you look over here at the distance (as this is playing) you'll see it changing and that's because
the tank is moving.
You'll also have noticed that there was a transition when this distance got below 20.
So let's just play that again and just watch it.
So I'm going to move the player this time and as I get within 20, the transition down to Chase happens
and then it's stuck in this Chase because it's got nowhere else to go.
There's also no code added as a behavior there, so the tank is not going to do anything at all but we know
that it's been triggered because we saw it here and we saw the tank stop.
So time to add in our Chase behavior code, so select Chase in your Animator and go over to the Inspector...
click on [Add Behaviour]...
this time it's going to be called 'Chase', [Create and Add] and you'll get Chase appear down the bottom and
let's open that up and to Chase, we're going to open up the same lot of methods that we had before in
the Patrol and add some code into the Update.
Chase now looks like this
(and let me just bring this out).
You're only missing on the edge
the squiggly brackets but just so you can see the code completely. Chase will inherit from NPCBaseFSM
to pick up the details about the NPC and then OnStateEnter will call our base.OnStateEnter
again, in order to just set that up again because remember, it's not going to get it passed through from
Patrol because as far as they're concerned, they don't even know about each other,
so it's got no way of getting that unless it gets it from the base.
Now we've got our OnStateUpdate that's going to do all the work here and all this is doing is just
setting up the Chase code, which will have the tank turn towards the player and follow the player's tank.
So first of all you need to work out what the direction is between the opponent (which is the player) and
the NPC itself.
That's the direction.
Then we slerp in that direction using our rotSpeed that we've got inside of our Base Class and then
we run a Translate on here based on the speed along the forward vector of the tank.
This movement code here is pretty much the same as what we've got for turning towards waypoints and
pushing the NPC in that direction.
All right.
Save all of that and then switch back to Unity and we can try this out.
What should happen is we will 'patrol' until we get to a distance of less than 20
and then the 'chase' will start and in the chase, the tank will just keep following our player, so let's
get our player over there
and we're now in chase mode
and if I just keep moving my player, you'll see that it'll follow it and it's going to follow it forever because
there's no way for it to transition back to Patrol
but that's easily fixed because all the code is in place for that.
So let's just stop playing and to transition back from Chase to Patrol, when the distance becomes
greater than 20, then we want the tank just to go back to Patrol.
So right-click on Chase, Make Transition and drop the arrow on to Patrol, select the arrow and then
let's go over to the Inspector,
untick 'Has Exit Time' add the condition of 'distance greater than 20'.
So we now have...less than 20 we'll
go from Patrol to Chase and then greater than 20,
we'll go back to patrolling and because these state behaviors are set up in here,
that's as simple as it gets.
We're just injecting the distance constantly from our TankAI
and the state machine's doing the rest of the work. So let's press Play and I'll move my tank over.
So we're now in Chase mode...and the tank will follow and turn towards my player...
but then if I drive away...and get beyond 20, patrol will set back in and the tank will go back to patrolling
and remember the OnStateEnter for Patrol
makes the tank go back to the first waypoint every time in its set-up.
OK so that's setting up the chase. In the next tutorial,
we'll look at adding in 'Attack'.