Game Advanced Prototype version 4
Hi, everyone
I am Kim Hyun jin, in charge of the creation of FPS game
In this lesson, we will implement
the Damaged and Die states for enemy AI
and use a navigation system
to avoid obstacles
and move towards the destination
Implementing the damaged and die state functions
Damaged and Die states
This time, let's implement
the Damaged and Die states
First, let's create the Damaged state
he player will attack the enemy
with the left mouse button
Then, you'll be able to see
the debris effect like this
If the enemy
is chasing the player
and when it takes damage
the enemy will briefly stop
then wait for a certain amount of time
before deciding whether to continue chasing the player
stay still,
or attack,
and then change its state accordingly
The player has attacked the enemy
Then, regardless of the enemy's current state
it will briefly stop and then decide
whether to attack, move, or idle
based on the distance to the player
After the Damaged state ends,
It will decide whether to
transition to the Attack, Move, or Idle state
In the EnemyFSM script,
you will create a new function below the AttackDelay
Since the function will be called
from a different class and not within EnemyFSM,
add the public keyword in front of the function
Next, in the void HitEnemy function,
it will handle transitioning
to the damaged state
Later, when implementing the Die state,
the HP concept will be involved
At that time, the code
in the HitEnemy function will be expanded
For now, let's just transition to the damaged state
ChangeState
It transitions to the Damaged state
within EnemyState
Next, let's create a function that will be called
continuously when in the Damaged state
under, void Damaged
This will make the time flow
If the time exceeds the damage delay time,
calculate the distance to the player
If the distance is smaller than the attack range,
transition to the Attack state
If not, and the distance is smaller than the detection range,
transition to the Move state
Otherwise,
transition to the Idle state
The comment we wrote
is exactly the same as the content in the AttackDelay function
The difference is
whether the waiting time is the attack delay time
or the damage delay time,
that's all the difference there is
create a function that takes
the waiting time as a parameter
to handle this part
Below the Damaged function,
we will create a function
named void DecideState
add a parameter
named float delayTime
Then, cut all the code
from AttackDelay
and move it to the DecideState function
Paste
And we will replace the AttackDelay variable
with the delayTime parameter
that is passed in
Let's also modify the comments
if the time
becomes greater than the waiting time, we will modify
Once the writing is complete, go to the AttackDelay function
and execute the DecideState function you just created
DecideState
Open the parentheses and pass
the desired wait time as an argument
This should wait for the AttackDelay time
And the Damaged function should
also call DecideState
For the parameter, we should pass
the time for how long the enemy
should pause when taking damage
Since the variable hasn't been created yet,
let's go up and create a variable for it
The damage delay time
public float
The damage delay time is set to 1.5 seconds
Then, scroll down and pass
the damageDelay variable
inside the parentheses of DecideState
Then, the following content will be executed all at once
If we don't create and use
the DecideState function as we did,
we would have to write
such long code under
both the Damaged function
and inside the AttackDelay function
Then, the same code will be duplicated like this
As this increases,
it will become very difficult to manage later on
If this same code is in 10 places,
we would have to change
all 10 places just to modify one
But if you implement it
as a function like this,
by only changing the function, the parts that use this function
will automatically apply the updated content
So, when you're coding and
you notice that the same functionality
is repeated in multiple places
I hope you'll practice creating functions
and calling them instead
Now, we need to continuously
call the Damaged function
Go up and under UpdateState,
execute the Damaged function when in the damaged state
Now, when the player attacks the enemy with the left mouse button,
the hitEnemy function will be executed
to transition to the damaged state
Go to the PlayerFire script,
and in the FireRay function, if the collided game object
is the Enemy game object,
get the EnemyFSM script
from that game object
and then execute the hitEnemy function
that we just implemented
we will add the code below line 4 here
If the collided object is Enemy,
let's get the EnemyFSM component
Execute the hitEnemy function
from the retrieved component
if not,
Then, execute the existing functionality
this is how it works
If hitInfo,
The collision information is stored here
hitInfo, get the name
from the collided object's transform
Here, the name of the
collided game object is stored
You can compare the name
using the Contains function
It asks,
Does the name contain Enemy?
If the name of the game object
contains Enemy
it returns true
otherwise, it returns false
The enemy we implemented
has the name Enemy
If the name is Enemy100
it will still return true because the name contains Enemy
This code will return true
Change the name back to Enemy
If the collided object is checked as Enemy
then these two functionalities should be
implemented within these parentheses
Since we're getting the EnemyFSM component,
we will create a variable to store the retrieved component
I will create it with the name EnemyFSM
Then, hitInfo,
I will get the FSM component
through your transform
we retrieve it through the component function
which component to get is,
write that we are getting
the EnemyFSM component
If the collided object
contains EnemyFSM,
store it in the FSM variable
Next, hey FSM,
this would be the component we assume
Execute the hitEnemy function
from the functions you have
and then, else, if not,
Place the existing code
inside these parentheses
Save it and then return to the editor
to run it
Activate the console window
After aiming like this,
when I press the left mouse button,
In the console that it transitions from Idle to Damage,
waits briefly,
and then changes from Damage back to Idle
Then, when it chases and you shoot,
you'll see it briefly stop and then
transition back to Idle
This time, I'll make it keep moving
while shooting
Then, you'll see it briefly stop,
move again, stop,
and move again, all functioning properly
We are currently checking
the name of the game object
to determine if the collided object is the Enemy
Let's try doing this with layers instead
Select the Enemy game object,
then in the Inspector,
under the Layer section,
change it from Default to Add Layer
by choosing the dropdown menu
Next, write Enemy in Layer 6,
then select the Enemy game object again,
choose Default, and then
select the Enemy Layer
we just added
Now, let's check
using the layer value
instead of the game object's name
Return to the PlayerFire script,
and if the collided object is Enemy,
it is currently being searched by its name
Instead of this, ask the transform of the collided object,
What is your game object?
It will ask if the layer value
of the collided game object is the same
what is it the same with?
There is a struct called LayerMask
It has a function called NameToLayer
Open the parentheses, and inside,
enter the name of the layer we added
It's Enemy, and currently, there is only one
It checks if it matches the Enemy Layer
We set the Enemy game object's
layer to Enemy only
That's why only the Enemy game object will
have the Enemy Layer
You can also identify specific objects
through layers like this
This time, we will implement the Die state
In the EnemyFSM script, you need to distinguish
whether the HitEnemy function
will transition to the damaged state
or the Die state
To do this, the Enemy
must have an HP value
Let's create global variables related to HP
We will create two variables
one for the maximum HP
and one for the current HP
Write public float
and set maxHP as 100
Next, we will set the current HP
as public float currHP equals 0
At the start, let's set the current HP
to the maximum HP
By doing this, when the game starts
the current HP will be
set to 100
currtHP equals to maxHP
Come down to HitEnemy function
Now, the HitEnemy function will take
a parameter that specifies how much HP
should be reduced
So, let's create
a float parameter named HitPower
Since the HitEnemy function has been changed,
you will see an error like this
in the PlayerFire script
why so?
The HitEnemy function requires a float parameter,
which is why this error occurs
So, you will create a global variable
for the weapon damage in the PlayerFire script
You will define it as
public float weaponDamage
let's set it as 20
And when executing
the HitEnemy function in the Raycast,
pass the weaponDamage value as a parameter
Returning to EnemyFSM,
when the HitEnemy function is called,
the value 20 will be passed
After reducing the HP by that value,
if the HP is greater than 0
it will transition to the Damaged state
If the HP is less than
or equal to 0, it will transition
to the Die state
Reduce the current HP by the hitPower value
If the current HP is
greater than 0,
transition to the Damaged state
if not,
Transition to the die state
If not, it means the current HP is
less than or equal to 0
The current HP is stored
in the CurrHP variable
so CurrHP minus equals
Reduce the HP by the hitPower value
Now, we need to ask
If CurrHP is greater than 0,
transition to the Damaged state
If not, else
Transition to the Die state
using ChangeState
EnemyState and Die
To see the value of the HP reduction,
let's output it to the console
print
The current HP value is CurrHP
Let's play this
Mouse left click to fire
Current HP is 80, right?
As we fire additionally, 60, 40 ,20
This time if I shoot again,
It should transition to the Die state
instead of the Damaged state
if I shoot it
Like this, it transitions to Die state
let's add in a code that makes it
destroyed after 1 second in Die state
After going to ChangeState,
add another case statement
in the switch case statement
case EnemyState and Die
and write Destroy
Destroy myself
So, let's use the lowercase version of the game object
and add a semicolon
I'll have it destroyed it after 1 second
So, 1
Save it and then return to the editor
to run the game
After hitting five times, the enemy will die
five
we can see it gets destroyed after one second, right?
But there is one more issue here
We will shoot five times
Now, I'll shoot a bit faster
pewpewpewpew
It does disappear
Transition Idle state to Die state
because it disappears after 1 second,
It's transitioning from Die to Die, and from Die to Die
repeatedly, right?
And the current HP is
continuously decreasing by the negative value as well
So, this time, if the enemy is in the Die state,
we will prevent any more damage from being applied
Next, go to the EnemyFSM script,
and in the hitEnemy function, you will handle the exception
If the current state is Die,
let's exit the function
What is it same to?
If it's the same as EnemyState's Die,
I will end the function using the return statement
When the return type is void,
the return statement can be omitted
but it can still be used
So, when used,
it will end the function without
executing the code below
Then, if the state is Die, we wouldn't perform
the operation of reducing the current HP
Based on that, we wouldn't execute
the transition to the Damaged state
or the Die state
Save it and let's quickly test the damage
Now,
we see that the transition
to Die state is called only once
Pathfinding
This time, I let's learn about
pathfinding navigation systems
I will place the enemy behind this wall
When the player approaches the wall,
the enemy should avoid the wall and come around it
and that seems normal
but right now, it's going straight through the wall
Let's go ahead and check
As shown, it goes through the wall
If the enemy were a ghost, it would be fine movement
If that's not the case,
it should avoid obstacles
In Unity, they provide
a navigation system package
that can perform this functionality
Let's give it a try and apply it!
First, we need to add
the navigation system package
Select the Package Manager menu
under the Window tab in the editor's top menu
If you click the drop down just to the right of the plus button
under the Package Manager, you'll see several options
Select Unity Registry from the options here
If you type NAVI in the search bar at the top right,
the AI Navigation package will appear
Select the package and then click Install
Now, using this navigation system,
we will divide the terrain into areas
that are accessible and areas that are not
The terrain in this case refers to
the Wall game object and the Floor game object
So, click the plus button in the Hierarchy window
and add an Create Empty game object
and name it Env
Next, set the position to 0, 0, 0
Right click on the Transform name
and select the Reset menu,
and it will initialize like this
Then, select the Ground and Wall,
and place them under the Env game object
as child objects
Next, we'll add a component
to the Env game object
In the Inspector, click the Add Component button,
then search for Nav
and add the NavMeshSurface component
Next, click the Bake
button at the bottom
Then, the areas that are
highlighted in blue will appear like this
The blue areas represent the regions
that can be navigated
using this navigation system
And the areas that show their original color are
marked as inaccessible regions
There is also an inaccessible area
above the Wall
By setting it up like this,
the areas where the player and enemy are
located will also be marked as inaccessible
If the enemy moves like this,
and there’s nothing in the way but they still can’t pass through,
that wouldn’t be right
So, in the NavMeshSurface we just added,
expand Object Collection menu
and since the collect object here is
set to all game object,
this happens
Select All GameObjects, then open the dropdown
and choose the Current Object Hierarchy menu
After that, click Bake again
Then, only the Wall and Ground game objects
will have their areas set
This option is for setting the area
only for game objects
that have children
Now, select the Enemy game object
The game object that needs to find
the path and move is the enemy
The player is controlled by us, we don't need the game to find a path
In the Inspector, click the Add Component button,
and this time, add the NavMeshAgent component
Through the NavMeshAgent component,
when you set a target position within the blue areas,
the enemy will move to that location
However, if there is an obstacle in front,
the enemy will navigate around it to reach the destination
Now, go to the EnemyFSM script
and create a variable to hold the NavMeshAgent component
The variable for the navigation agent
I'll name the NavMeshAgent variable as agent
To use the NavMeshAgent class,
you need to add the line
using UnityEngine AI, at the top
It usually gets added automatically,
but sometimes it might not be
If you see a red line under NavMeshAgent,
make sure to add that line of code manually
At the start, we will get the NavMeshAgent
by setting, agent equals to, to retrieve it
Use GetComponent and NavMeshAgent inside parenthesis
Since the enemy needs to move,
let's go to the Move function
At this point, we made the enemy move towards the player
Instead of calculating the direction and moving in that direction,
we will command the enemy through
the agent to go to the player's position
So, we will comment out the code for calculating
the direction towards the player
and moving in that direction
Let's tell the agent the destination
The destination here would be the player's position
So, the agent has a function called
SetDestination among its features
Open the parentheses, and inside,
we can insert the position value of the destination
So, we will write the code
so that the player gets the position value
Let's go ahead and save it, then try playing the game
If the player gets closer
unlike before when it went through the wall,
this time the enemy is avoiding the wall
We can see that the enemy is successfully avoiding the wall
However, when the enemy attacks,
it seems to push the player slightly as it moves in
The reason this happens is that
when the enemy is in this state, it will chase the player
We set the destination
using the NavMeshAgent,
and that destination is the player's position
So, the enemy is
trying to reach that exact position
But since the player and enemy don't overlap,
the enemy ends up
gently pushing the player
while moving towards the destination
And then, after that, the enemy attacks
This shouldn't happen like this
If the enemy enters the attack range,
even if the destination was set to the player's position,
it should stop and perform the attack instead
Let's move to the ChangeState function
When the state changes,
we will stop the agent
Even if the enemy is heading towards the destination,
once the state changes, it will stop moving
Let's stop the pathfinding
Hey agent, among the properties you have,
there's one called isStoped
By setting isStopped to true,
it will stop the pathfinding
Save the changes, return to the editor,
and when you play, the enemy should move
based on the distance, right?
But it doesn't move
It moved before, but now it doesn't
The reason is that when it went to the Move state,
it simply stopped the pathfinding
Even though the destination was set,
it’s not moving because it was
set to stop, so it's not functioning
So, we will change isStopped to false
only in specific states
When?
When it's moving
So, you will add another case
to the switch statement
case Move State
Hey agent, among the properties you have
change the isStoped property to what?
to false
Save and come back to the editor
Go ahead and try playing again
Now, the enemy moves, stops, and then attacks
The enemy moves again, stops, and attacks
repeating the cycl
Being too far away, so it switches to the Idle state
While getting close to the enemy,
I'll try attacking by clicking the left mouse button
It stops and then comes back again
Since the enemy has transitioned to the damaged state,
it stops, comes back, and then attacks
Let's summarize the key points we've learned in this session
Summary Implementing Damaged and Die state functions Damaged Transition to Damaged ChangeState EnemyState Damaged
The void Damaged function is the same as the AttackDelay function Call the DecideState function and change the parameter attackDelay to delayTime
If it collides with something if Physics Taycast ray, out hitInfo If the collided object is an enemy, get the EnemyFSM component
Execute the HitEnemy function from the retrieved component if hitlnfo transform name Contains Enemy
EnemyFMS fam equals hitlnfo transform GetComponent EnemyFSM fsm HitEnemy If the collided object is not an enemy, execute the existing code
can also use the LayerMask.NameToLayer function to determine specific objects based on their layer
Implementing Damaged and Die state functions UDie Set the current HP to the max HP Decrease the current HP by the hit power
If HP is bigger than 0, call the ChangeState function with EnemyState Damaged as the parameter
If 0 is bigger than or equals to HP, call the ChangeState function with EnemyState.Die as the parameter
If the current state is the Die state, exit the function if currState equals to EnemyState Die
Pathfinding Navigation System AI navigation that avoids obstacles and finds a path is provided NavMeshSurface component divding the terrains
Enemy Object Add the NavMeshAgent component to the Enemy object Destination set by the NavMeshAgent is player position Retrieve the navigation agent
agent equals to GetComponent NavMeshAgent Tells the agent the destination agent SetDestination player position
Stop the pathfinding agent isStopped equals true To change the isStopped property to false only in specific states,
To add a case in the ChangeState function using a switch statement case EnemyState.Move agent isStopped equals to false break