Welcome back to Part 3 of the series on creating a Finite State Machine for an NPC and what we're
going to do now is add in our final state, which will be 'Attack'.
So to begin with, we first need to set up our NPC to do that and it's going to need bullets.
I'm going to go to the tank
(let's find it in the scene)
and we want to shoot from the tank. In the tank that you've been given (if you've downloaded my project)
on the very end of the turret, if I can find it for you...
let's just drop this down...
you'll see that I've got a little cube just there.
So I added a cube and I've rotated it and I've attached it to the tank at the position where I want the bullets
to come from.
Now you'll have to make sure you also turn off your Box Collider if you're doing this on a different
model
but what's important is that this box is attached to your NPC, so that when your NPC moves, this box does
as well because it's the relative position for the bullets to come out of.
So that's all ready to go there.
Let's go back to the Animator
and in here, we're going to add in our Attack script.
So 'right-click > Create State > Empty' and we'll call this 'Attack' and for our attack state we will add the
behavior of Attack and the code for our Attack
looks like this.
So I'm going to replace the commented-out code with the new code.
Again Attack will take its lead from our NPCBaseFSM class that we created, so that it gets hold of the NPC
data, then we start it up getting the information about the opponent and the NPC and the speed and all that
(although it's not needed in this particular case)
and we're going to call some methods that are inside the TankAI
from this one instead.
So we're calling back the other way this time
and now we've got our TankAI StartFiring
when we enter in the Attack state and then on the update, we will continue looking at the opponent.
I'm just using a really easy transform.LookAt
and then the opponent's position. You could put slerping and stuff in here if you want it to look a little
bit more smoother
but for our purposes, this works quite nicely.
Then when we exit, we're going to run a TankAI method called StopFiring. The StartFiring (as you'll
see in a minute) is an InvokeRepeating,
so it's just going to start firing all the time while it's in the Attack state
and then just before it leaves the Attack state, it will tell it to stop
that firing-invoked method. In the TankAI script, we'll add in those methods that the state is calling.
So just underneath GetPlayer, I'll add in these 3 things. The method doing all the work is this 'Fire'
one and it's going to instantiate a bullet object from a prefab (that we'll set up in a moment) from its
turret position
and that's the little cube that we created before and the turret rotation. So it'll make sure that it pushes
the bullet along in the same direction that the turret rotation is set to
and it does this by using a GetComponent Rigidbody on the bullet and then an AddForce of the turrets
forward direction multiplied by 500
in this case. So this is the strength of the force that the bullet is coming out at.
Now the 2 public methods here
(for start and stop firing)
are the ones that are called from back in the Attack script. StopFiring will
just cancel the Invoke on 'Fire', which happens when StartFiring is called.
So the InvokeRepeating will start the 'Fire' method (which is the one up here)
after half-a-second and then it will continue calling it every half-second after that.
Now you can make this faster by making this value here smaller
or you could have the interval longer as well.
Now the bullet itself needs to come through into the TankAI
so we'll make a public exposed variable at the top for that...to drop it into. The game object also needs
to know about its turret
so we'll put GameObject because that's what we're using to set up the alignment of the bullet itself.
So with those both public, we can save all of this code, switch back into Unity, find our tank (which is
up the top here) and you'll see you've got a Bullet and the Turret. Now the Turret
you have to get from the cube that's attached to the tank, so we grab that and drag it onto there
and then the Bullet...you'll find the Bullet in the Assets from the project that I've given you.
Now this is the same bullet that's in the tank project that Unity has but I've added some extra script
onto it, so just drag-and-drop that into the place for the bullet.
If you have a really quick look at the bullet and down the bottom there's a little bullet script here
and what it does is it instantiates an explosion when the bullet hits something and then makes the bullet
disappear essentially.
Right.
So that's already programmed for you in the resource file. Ok, so now we need to set up the attack so that
the attack happens. If we're in chase mode,
let's say we want to transition to Attack. Make a transition to Attack,
when we are...let's say a distance of less than...10.
So we've got a 'chase distance' of 20 and now we'll have an 'attack distance' less than 10.
Now if it becomes greater than 10, then we'll go back to chasing.
So Make Transition back the other way,
select that transition
and then we're going to add the condition of 'distance greater than 10'.
So if we now get out of a range of 10, it will go back to chase.
So 'less than' to Attack and 'greater than' to go back to the Chase mode.
Let's have a look at the behavior of our tank now.
So we're patrolling...the patrol itself brings us close enough to trigger the chase, so it'll go off and start
chasing us.
Once it gets close enough, it will stop and start firing,
as you can see there. Now it doesn't keep moving towards us because that attack behavior has no movement
code in it whatsoever.
It only has turning code, so if I just move, it will keep trying to fire in my direction.
Now if I get far enough away, it will go back into Chase and keep chasing me
and then if I get really far enough away, it will forget about me entirely and go back into Patrol.
Now what I don't have here is a transition that goes from Attack back to Patrol
or the other way because this logic will fire straight through these states, depending on how close you
are. If I suddenly got within 10, it would go via the Chase state into Attack and I wouldn't even have time
to execute anything in the Chase.
So that's fine.
I don't need an extra one here, though
you could put one in there if you wanted
but in this case, it's absolutely not necessary.
Obviously it's now attacking me off-screen because it's waypoint took me to close enough to my tank
and you can see that over here in the Scene view, which is a lot of fun.
Ok, so if I just move again and get out of the road,
it should forget about me and it'll reset.
It's entering of the patrol and go back to waypoint 1 and continue on its little route.
All right, so that is Finite State Machines
in a nutshell and how to employ the state machines that are inside of the mecanim system
to do that.
Now you can see how easy it is and how logical the Finite State Machine is
and what it does over the old way of doing it, is there's a lot of if-statements
left out of distances and things like that, that you would normally have to program into your
MonoBehaviour to change the state of your character,
whereas this is going to do it automatically for you in separate scripts that you can use on other characters
and of course, define different types of behavior for those characters even if they're in the same state.