-
In this assignment we will write code
-
to control the movement of the enemy.
-
The enemy will be driven by the animator,
-
taking movement from the root motion of
-
the animation clips that we provided previously.
-
Animator parameters control which animation
-
states the animator should play
-
and therefore how the enemies move.
-
We will set these parameters using velocities
-
from the nav mesh agent
-
and we will modify the values with code
-
to correct certain behaviours.
-
When using the nav mesh the system gives
-
the agent a desired velocity.
-
This is not the actually velocity that the agent
-
is moving, but one that it should head towards.
-
Nav mesh agents such as our enemy
-
have a waypoint path to a destination,
-
shown here by a red line.
-
The agent turns to face the waypoint
-
whilst travelling towards it, and we say that
-
the velocity of the agent, shown by the black arrow
-
is heading towards the desired velocity,
-
shown with the green arrow.
-
This desired velocity will be the basis
-
of the values that we'll send to the enemy's
-
animator parameters Speed and Angular Speed.
-
Let's begin by creating the script to
-
control these parameters.
-
Add a new script to the Robot Guard called
-
EnemyAnimation.
-
Select char_robotGuard and choose
-
Add Component - New Script
-
and name it EnemyAnimation.
-
Launch the script for editing and prepare it.
-
First let's take a look at the class variables
-
that we will need. There is only one public variable
-
that we will need, which is a float
-
we will call deadZone.
-
The deadzone, similar to a deadzone in input terms,
-
is a value range which should be ignored
-
in the case of enemy navigation
-
we do not want the enemies to attempt to turn
-
when the difference between their forward direction
-
and their desired velocity is very small.
-
And we define this with the deadzone.
-
Without this we would see the enemies
-
oversteering and walking in wavering
-
lines towards each waypoint.
-
As shown in this example.
-
We will need 6 private variables.
-
The first of which is a reference
-
to the player's transform.
-
We will need this to set the direction that the enemy
-
faces to the player when they are in sight.
-
Next we will need a reference to the
-
EnemySight script so that we can change the
-
animation based on whether the player is in
-
sight or not.
-
Then we will need a reference to the nav mesh agent
-
component which will be guiding the enemies movement.
-
Next we will need a reference to the animator
-
component and to help reference it's
-
parameters we will need a reference to
-
the HashIDs script.
-
The last will be an instance of the helper class
-
that we made during the last assignment,
-
AnimatorSetup.
-
Next we will use the Awake function to
-
setup these references.
-
In addition to allocating the references
-
we will also need the awake function to perform
-
a number of other actions.
-
Firstly we must make sure that the rotation
-
of the enemy is set by the animator
-
and not by the nav mesh agent.
-
This will reduce the appearance of foot slipping
-
whilst turning corners.
-
We will also need to create an instance
-
of our helper function from the previous
-
assignment, and in doing so
-
call it's constructor.
-
We can parse in the animator component
-
and the HashIDs script that we already
-
have a reference to.
-
We need to set the layer weight of the different
-
layers of the animator.
-
The Weight value controls the balance of animation
-
from the layers on the animator.
-
We are going to make both the Shooting
-
and Gun layers have a weight of 1,
-
meaning that they will totally override the
-
layers beneath them, such as the base layer.
-
This means that the animation would be purely
-
based on those layers.
-
However we are using a mask for both the
-
Shooting and Gun layers so that the only
-
parts of the body that they can override
-
are the upper body and right hand respectively.
-
So we use the Set Layer Weight function
-
and use the integer of the layer.
-
For example the base layer should be referenced
-
with a 0, so because we're referencing Shooting and Gun
-
we will use 1 to represent Shooting
-
and 2 to represent Gun
-
and we set both of them to 1f.
-
The last thing that we need to do in the Awake function
-
is to convert the deadZone variable
-
from degrees to radians.
-
This is because the animator controller we made
-
earlier measures the parameter in radians.
-
Luckily the Mathf class has a constant
-
that we can use for this, deg2rad.
-
A constant is like a variable,
-
however once it has been set it cannot be changed.
-
In order to convert a number from degrees
-
to radians we simply multiply by this constant.
-
The majority of this script will be setting
-
up the animations based on the nav mesh agent.
-
To do this we will create a function that we
-
can call in the Update function.
-
We will call it NavAnimSetup.
-
The first thing we are going to do in this function
-
is to create two floats for the Speed
-
and Angle parameters that we will parse to
-
the helper class's Setup function.
-
Now we need to decide if the player is in sight.
-
If the player is in sight then we
-
want the enemy to stop, so we will set
-
the Speed parameter to 0.
-
Now we need to find the angle between the direction
-
the enemy should face and the direction
-
it is actually facing.
-
Negative to the left and positive to the right.
-
We will need to make another function in order
-
to do this for us.
-
It needs to return a float
-
and will need 3 vector3s as parameters.
-
A vector we are measuring from,
-
a vector we are measuring to,
-
and another vector to determine which
-
way is up. We will call this function
-
FindAngle.
-
The toVector we are going to parse in to
-
this is the nav mesh agent's desired velocity.
-
This will sometimes equal 0.
-
If this is the case then it might cause an error
-
so we will need to put in a way to check for this.
-
If the desired velocity is 0
-
then we want the direction to be 0.
-
If this is the case we can simply return
-
0 from the function, and if this isn't
-
the case then we will continue with
-
the rest of the code.
-
The next step is to find out the absolute
-
value of the angle.
-
We can do this very simply with the
-
vector3.angle function.
-
Now we need to determine whether this angle is
-
to the left or to the right of the
-
forward direction. To do this we can find the
-
cross product of the two vectors
-
and check the resultant normal vector.
-
Using the lefthand rule for the cross product
-
we know that if the toVector is to the right
-
of the fromVector their normal
-
will point upwards.
-
See the lesson on Vector Maths linked below
-
if you need reminding of this.
-
Now we can find the .product of this
-
Normal and the Up vector that we parsed in.
-
If the Normal and the Up vector are pointing
-
in the same direction the result will
-
be greater than 0, therefore we can multiply
-
the angle that we've already calculated
-
by the sign of the .product that we have found.
-
This will show whether the desired velocity
-
is to the left or the right of the Forward vector.
-
Remember that we need this angle to be in radians.
-
So we need to multiply it by the
-
Deg2Rad constant that we used earlier.
-
And finally we can return the angle.
-
Now that we have a function to find the angle
-
we can use it in our NavAnim
-
setup function.
-
The fromVector is the enemy's forward vector,
-
the toVector is the vector from the
-
enemy to the player and we will use the
-
enemy's upVector as the up vector.
-
Now we need to account for when the
-
player is not in sight.
-
Given that the player is not in sight
-
we want the speed to be based on the
-
nav mesh agent's desired velocity.
-
To achieve this we can use projection
-
in order to project the desired velocity
-
vector on to the enemy's forward vector.
-
Projection allows us to take 2 vectors
-
and find out how much of the first vector
-
is in the direction of the other vector.
-
A projection is found by drawing a line
-
from the tip of the first vector
-
towards the second vector.
-
It must be drawn so that this line is
-
perpendicular to the direction
-
of the second vector.
-
The point of the intersection of this line
-
defines the end of the projection vector.
-
Since we are projecting the nav mesh agent's
-
desired velocity on to the enemy's forward vector
-
the resultant vector will be small
-
if the enemy is not facing the same direction
-
as the desired velocity.
-
The speed variable will be set to the
-
magnitude of the projection vector.
-
Without using this projection the
-
enemy might have a high speed whilst
-
facing the wrong direction, and thus running a
-
wide arch in order to face the correct direction.
-
Next we need to determine the angle.
-
Again we will use our findAngle function
-
but this time the toVector is the
-
nav mesh agent's desired velocity.
-
Since we are using damping to set the animator
-
parameters when they are turning
-
they won't stop turning as soon as they face
-
their correct direction. Instead they will
-
continue to turn very slightly.
-
They will then need to turn back to compensate.
-
This will result in a snaking motion
-
as they finish turning corners.
-
In order to prevent this we need to put
-
a check in to our function.
-
This check will simply be whether the angle
-
is small, i.e. less than our deadZone variable.
-
If it is then we avoid using the animator controller
-
to set the direction that the enemy is facing
-
To do this we set the angle to 0
-
and set the enemy's transform to look at
-
the desired velocity from it's own position.
-
Now that we have calculated the speed and
-
angle we can parse them in to
-
the Setup function of our helper class
-
animator setup.
-
They can then have damping applied and be
-
parsed in to the animator controller.
-
Now that we have our completed NavAnimSetup function
-
we can call it in the Update function.
-
Note that whilst we have inserted this function
-
above ones that we have already written
-
this is not necessary.
-
We are doing this in order to maintain a flowing
-
structure to the script.
-
First come the class variables,
-
then the Awake function is called,
-
then every frame the Update function is called.
-
This in turn calls the NavAnimSetup function
-
which calls the FindAngle function.
-
The last thing we need to do with this script
-
is to effect the root motion of the enemy.
-
Normally we have the choice of either applying
-
root motion or not on the animator component.
-
However if we use the OnAnimatorMove
-
function, which is called after Update every frame,
-
then we can effect the root motion manually.
-
There are two things we need to control in this function.
-
The velocity of the enemy and it's rotation.
-
To control the velocity we must set the nav
-
mesh agent's velocity to the delta position
-
of the animation divided by the delta time.
-
This is the change in position per frame.
-
As for the rotation, we have already set the
-
nav mesh agent so that it does not control rotation.
-
This is so that we can effect the rotation
-
directly using the animation's root rotation.
-
And that's our script complete.
-
Now that the script is finished,
-
we can save it and return to the Editor.
-
Don't forget to tidy the script away by
-
putting it in to the Enemy folder.
-
Expand the Scripts folder and drop it from
-
the root of Assets in to the Enemy folder.
-
Now let's save the scene and save the project.
-
In the next assignment we will be making a
-
script to make the enemy shoot the player.