In the Line Of Sight workshop, we set up a variable called 'state' to keep track of the activity the NPC
was currently performing. A.I. methods using states are very common in NPC programming and no more focused
upon, than in the case of Finite State Machines.
In this lecture, we're going to build a Finite State Machine from scratch and use it to control a tank
that will be performing 'guarding' & 'chasing' functions.
A Finite State Machine is a conceptual machine that can be in exactly 1 of any given number of finite
states any time. A Finite State Machine is used in the field of Artificial Intelligence to define
(among other things) the behaviors of computer-controlled characters. It is represented as a graph with nodes that
represent the states and transitions, that define the path from one node to the next. Once a state has
been entered,
the NPC stays in that state until a condition on the transition is met.
Here we have a Finite State Machine for simple guarding behavior.
If the NPC is in the Patrol state, it will continue performing the associated behaviors until a condition
is met on any of the transitions exiting the state.
In this case, it is waiting until the player is a distance less than 20 away.
Once that condition is met, the NPC transitions into the Chase state. If you've been using Unity and think
the Finite State Machine concept looks familiar, it's because it is! The mecanim system uses Finite
State Machines to control animation behaviors and it's exactly what we are about to use to define the
behavior of the little green tank. To follow along
as I build this F.S.M., download the starter file that comes with this lecture.
This uses a lot of the assets from the tanks tutorial that Unity released a couple of years ago and
I've repurposed them to use them in this situation.
When you first open the scene that's called Tanks, you'll see this. So we've got a little green tank
which will be our NPC and we have a tank that we can drive - it already has a script on it. If you press
Play, you'll be able to use the arrow keys to drive it around...like that.
The first thing we need to do for our green tank is to create an animation controller.
So go into the project and click on Create and you're looking for Animator Controller, which is off my
list here.
Let me bring this up for you...Create > Animator Controller.
If you've used animation before in Unity, you'll be familiar with this.
We're going to call this our NPCFSM, like that. To start creating the states,
double-click on the NPCFSM and that will open up the Animator window.
The first state that we'll create is Patrol. Right-click in the Animator window, go 'Create State > Empty' then
select that state and over in the Inspector, you can change its name.
Instead of assigning this state with animations we will assign it with behaviors and over in the Inspector,
you'll see that there's an 'Add Behaviour' button.
Select that and we'll create a new script. We'll call it 'Patrol' and 'Create and Add' that. That will create
a C# file called Patrol in your Assets folder that is now associated with this Patrol state
and if you double-click and open this up, you'll find a whole bunch of commented-out code which is triggered
by that state. Any state in a state machine has 3 processes that it runs at different times.
It has an Enter, it has an Update and it has an Exit. Enter runs as soon as the state is transitioned to.
The state then enters the endless loop of the Update. Update continues to run until the transition
out of the state is triggered. At this point, before leaving the state,
the Exit method is run. Back in our state machine behavior script, we'll find each of these methods included.
The first is an Enter
(let's take away the comments for that).
The next is the Update (remove the comments for that as well)
and lastly there's an Exit
(let's also get rid of those comments).
The other methods ('OnStateMove' and 'OnStateIK') are used by the animation system.
We're not doing animation in this case, so let's just remove those.
We'll now build a really simple 'waypoint following' system into this code. At the top,
I'm going to define 3 values like this.
So the first one is 'NPC' and this is to grab hold of the NPC that is using this code via their Animator
and we'll get to that in a moment.
Next we need an array of all the waypoints, so that we can loop through those and move the NPC around
and then we need to keep track of which waypoint we're currently going towards, which will be in this
int called currentWP.
Now to get hold of the waypoints, because this is a scriptable object
there's no way for the Inspector to send values through to this and therefore we need to grab them at
runtime.
We can add an Awake method (which I'll just do up here) to this
and what it's going to do is set waypoints to find all of the GameObjects with the tag of 'waypoint'.
Once you've got that in there, if you just switch back to Unity (I'll just save it) and have a
look at the waypoints that I've added and you'll see that they're tagged with 'waypoint'.
So that's how the script is going to find them. OnStateEnter
is going to run every time this state gets transitioned to, no matter what the NPC happens to
be doing. So you need to consider what you need in here that's going to set up the NPC for patrolling,
in this case.
Now what I want to do is to add these.
First of all, I'm going to grab hold of the NPC (which is our value that we've got defined at the top).
Now we can get the NPC running this script using the animator object that's being passed through here
and I'm only setting this up just to save myself a bit of typing, as far as having to type this in every
time I wanted to get the transform or position of the NPC.
So that's just grabbing hold of that.
Now every time we come into Patrol, I'm going to tell the NPC that it needs to go to waypoint 0.
So if it gets interrupted in its quest for patrolling (by having to chase somebody) then it will always
go back and start at position 0 and re-count again from there.
So that's OnStateEnter. OnStateUpdate is going to run over and over again, while the NPC is in this
state.
So this is where we're going to put the code for patrolling and moving around those waypoints.
What this is doing is, first of all, it's asking if there's any waypoints at all (so if the waypoint array
is 0 in length, then 'return' because there's no point trying to move around non-existent
waypoints).
Next, it's doing a distance calculation and this distance calculation works out where the NPC is in relation
to the current waypoint it's heading towards. If it's reached the current waypoint within an accuracy
in this case, of 3, then it will go and get a new waypoint and so it will update waypoint by 1. Starts at
0,
so now it's going to go up to 1.
It then will ask if the waypoint is greater than or equal to the length of the array,
it will set the current waypoint back to 0.
So this will cycle around (or count up to the end and then cycle back around) and will create a circuit
of waypoints.
After that is some code that will move the tank forward, as well as make it face in the direction of
the waypoint that it's traveling towards.
First we calculate the direction in which we want the tank to travel and then using that direction, we
apply a slerp.
So Quaternion.Slerp is going to slowly turn the NPC towards the waypoint.
Now you can modify how fast this rotation happens with this quantifier here.
So at the moment, the speed of the rotation is set to 1.
After that, we then do a Translate and we push the NPC along its z-axis (which is its forward-facing axis).
So the rotation is doing all the work as far as getting orientated with the waypoint and this
Translate is just pushing it forward
and the speed, in this case, for our forward direction is a value of 2 and that's where you can change
it if you want to be moving faster. For OnStateExit,
there isn't anything we need at this point so we'll just leave that empty.
So now let's save our Patrol and we'll flick back into Unity. What we'll do now is on our green
tank,
we're going to add this animation (which is our finite state machine instead of an animation,
but the process is the same). So we will add a component ...we'll add an Animator and then into the Controller
part of the Animator, we want to drag-and-drop our
NPCFSM that we just created. Of course, there's no Avatar or Root Motion to apply because we're
just using the behavior system within this Finite State Machine to drive the movement of our tank.
Now, with all that set up, we can press Play.
You can see that behavior working in the Animator window, if you select the current state that's
going.
If you can't see that, select the tank in the Hierarchy as well.
What this is showing you is that the Update method is running over and over again.
Now there's no transitions out of here to trigger anything else to happen, except that this little green
tank is just going to go around the waypoints in the order that they end up in that waypoint array.
In the next tutorial, we'll start adding more states to our Finite State Machine so that the tank can
start shooting and chasing the player around.