Hello, this is Lee Young-Hoon
Today we will create enemy characters and FSM for their AI control
In this chapter, we will create enemy actors
And to control their AI, we will use a finite-state machine
Or FSM, in order to distinguish each state
And we will also learn how to implement action for each state
First, let's create our enemy actors and apply their appearances
Creating Enemy Actor & FSM-Based States
Let's first create our enemy class
Go to Tools, 'New C Class', and let's go with 'Character'
Next, let's call it Enemy
We would need to first add things like appearance or collider
But since Enemy is a character
It should have all of these already
So let's just do some touch-up on our Mesh
Let's go back, and remember what we did for creating our Player
Let's look at our Player
Capsule, Mesh, and Movement Comp, Arrow
These are the four preset components
Here, at Mesh Component there is SKM_Quinn
Let's use the same Quinn
To do that, copy this
You can copy it here as well, so copy
And back to our Class
Here, with this string, we will load it to this address
Now, this will load the Mesh Asset
And apply it to our Mesh
Now let's write this out
To do so, remember how we used ConstructorHelpers? Like so
And let's use FObjectFinder
Make sure to add the angle brackets like so
Let's name it 'TempMesh'
And let's add TEXT
And we can add whatever we have down here
And this will load this part
What goes here is the information that we will be loading
So it will be SkeletalMesh
Make sure to start it with 'U'
Let's check if it has loaded successfully
If TempMesh, Succeeded
Then, GetMesh
It will be applied there
Like so, SkeletalMesh
For TempMesh, add Object
And we will not adjust its location
It is facing rightward, and it is centered at the bottom center
So let's move it downward and rotate it
It's the same procedure here
Bring in our GetMesh, and this function called SetRelativeLocationAndRotation
Let's have the value FVector
And then FRotator
This is 0, 0, 90
-90, excuse me
And for Rotator, see here
The order goes Pitch, Yaw, Roll
Here, Pitch is y, and Yaw is z
So the order is y, z, x
So here we need 0, -90, 0
Let's finish setting it like this
Now let's come back to Unreal, first compile
And let's create our Enemy at Bluepirnt to check
Like so
Right click on Enemy and Create Blueprint
At our folder Blueprint, name it 'BP_Enemy'
Now we have set it like so
Let's first compile
And let's place our Enemy at this Level
Let's rotate Enemy by 180 degrees, so that it faces our Player
Let's place it like so
Like so, we can approach closer
And when we try to shoot, our Enemy does not get hit
The bullet just passes through
Also notice how the the camera acts strangely
When we rotate, it suddenly moves like this
The reason for this is
Here's the concept
Two things; first, it does not get hit because
When we shoot a gun, the Line Trace shoots in a form called Visibility
So this will be that Trace Channel Response
We'll need to take a look at this Trace Response
And then, second, when we rotate the camera
It moves too quickly afterwards
Meaning, when Player is here Player Camera here
Then Enemy is approaching like so
An with the rotation, this camera
The camera moves like so, moving closer
This also has something to do with Trace Channel
And we will be setting two things for our solution
To do so, first double click on Enemy to open it
And at Enemy, at Capsule Component, we have this 'Collision Presets'
And here at Trace Responses
Visibility is at Ignore
And Camera at Block; this is the problem
So let's change these
First, let's make it Custom
For Visibility, Block
And Camera, Ignore; we're reversing the setting
Also because Mesh has its collider
We should set this here as well
Open it, let's go with Custom
And use Ignore for both
In fact, for Mesh, we could just go with NoCollision here
Compile, and let's run it
Now when we shoot, Enemy gets hit
And rotating the camera does not get in the way
So this will be out setup
We will place our Enemy here
And we will add a 'State' for our Enemy
To process it further
Let me first explain the concept
We usually abbreviate it as FSM
The full name is 'Finite State Machine'
That's the spelling of the term
If we add 'In', it will be 'Infinite'
But since it does not have 'In', it is 'Finite'
So it is literally something in a finite state
Or a machine with this state
We sometimes call it a State Machine
Anyhow, the short term for this is FSM
And whenever we work with animations at Unreal
We do it in an 'FSM-based' way
So now, we will be creating an AI
Specifically an FSM-based AI
And when we create this State Machine, we need three things
There things, first is State
We need a State
And we need a Condition
Finally we need a Transition
Transition simply means that something moves over
So we need a Condition, like so
And finally a Transition
It will be helpful to remember these terms
So now let's create this
We will use FSM, in a diagram format
Basically, the Enemy AI can wait
It can move, and it can also attack
And it can also be attacked, getting damage
And finally it can die
These are the five states we will create
To wait, we call this 'Idle'
To 'Move', to 'Attack'
When it gets attacked, let's call it 'Damage'
And it can also 'Die'
We use a rectangular box to visualize the state
So this here is a state
First, it should be 'Idle', right?
And from 'Idle', we can have transition to 'Move'
So from this to 'Move', we have the transition
And this will happen under some condition
When in 'Idle' state, Enemy is looking for Player
So this is the condition; if Enemy finds Player
Then it transitions to 'Move', for example
After 'Move', Enemy will be able to 'Attack'
Because it can only attack after approaching Player
Then it transitions again to 'Attack'
And while at 'Attack' state, it could happen that
Player is not in the attack range
Because, say, Player ran away
Then Enemy should chase Player down
So it can also transition to 'Move' from here
This will be the basic logic behind the movement
And now for 'Damage' or 'Die'
These are both external factors
Here in our example, Player shoots a gun
So with that gun, Player 'Damages' Enemy
Now it's the 'Damage' state
For this, a concept of HP or stamina will help
If there is some HP left, Enemy stays in 'Damage'
And once its HP hits 0
Then we can move to the 'Die' state
That's the basic logic
For example, for 'Any State'
'Damage' and 'Die' are 'Any State' because we can reach there from 'Idle'
Or from 'Move' or 'Attack'
Meaning, the transition can happen from 'Any State'
For example, we can have 'Attack'
And after 'Attack' Enemy can go to 'Die' state
It could just die like that
Let's implement it following this diagram
So let's start with this
This time, we made an Enemy Class for our Enemy
Let's add a component here
Let's add a component called Enemy FSM like so
So when we implement it at this component
Between the main Character class and its FSM
How can these two exchange information?
We will be covering that together
So let's create a component
At Tools, down here we see two types of component
We have Actor and Scene for components
Actor Component is for something that exists
And Scene Component is for something that transforms as well
And we do not need the transform
So let's create a class with this Actor Component
Next, let's name it 'EnemyFSM'
So here's our EnemyFSM, and it's time to create some state
The best way of creating state is using enumerated type
So let's use some enumeration
Unreal provides their own enumerated type
It's called UENUM, and here's the macro for that
Let's use that and write out 'enum class'
For enum, there is a scoped one and unscoped one
So let's create our scope in a class type using enum
Let's name it Enemy State
And if we want to see it at Blueprint as well
Just add 'BlueprintType'
And when we do this, we always use unit8, meaning unsigned
This is the rule when we implement this
And then, for your case, you can just change the name here
And for enumerated type, you add 'E' before the name
So that's what we have
And here, let's add Idle, Move, Attack, Damage and Die
Let's add these five
You can add a comma at the end, or you can skip it
And if you want these to be some different names
At Blueprint, go ahead and use UMETA
This macro will allow changing the display name
This is optional
You can do Ctrl Shift U to capitalize this
With these hotkeys, you can do that
So we're done with that part
And now this is the data type, enumerated type
And let's go down and create some variable
Type 'public'
And let's add 'State'
We will be branching with this State
How do we do that? Come back to this source
And we see 'Tick', here as well for component
So we will start writing here using Switch Statement
Swt, press Tab
And it automatically adds, it has IntelliSense
Or autofill, so here
Write 'State' and press Tab again
And Enter, and we have all these automatically
First, we don't need this default part
Let's start writing, this will be 'Idle'
And for us, since this is Tick
We'll name our variable TickIdle and implement it here
So what's going on here? Tick is when it gets updated
So based on the current state
This will take care of some mechanical control, as in it only does this action
So let's visualize this here
Here's the input
And based on the state
The signal branches
Since we have five, like 'Idle' and so on
And that input, if state is Idle
Then it goes to Idle like so
And if our State value changes to Move
If this is Move, then the input goes to Move
That's what we can do to that signal
And that's what Finite State Machine does
So we can assign a variable for each state
For us to control
We can write it out here, but it will be too long
So let's instead create variables
To control these
Implementing Action For FSM-Based State
Since we have them here, let's implement them
At our header, let's create some declaration
Write it out like so
And we have everything
Now we implement it
Write it out like this
And we can use this to process them
Once you do that, the variables below will be complete
And we just need to implement each
The advantage for FSM is that we can divide them
Into different parts to implement each
And we can, through control flow
The action could change among these
And we can process them by turning them into signals
The downside is that this part may get complex
And we may lose some readability
Meaning, we won't be able to see the flow at once
Which could be the problem, but this is the basic part
Let's control each
Starting with 'Idle'
At 'Idle', what will happen is
Time will pass
For example, a second passes
And Enemy looks for Player
It wants to find Player
And if it does
It wants to transition into Move
That's what this does
Let's just write out everything first
At 'Move', it moves
The destination
It wants to move towards it
And it has to calculate the distance left to the destination
Because it can't keep moving forever
It should stop when it's close enough
So get the distance from the destination
And if Enemy can attack in that range
But this is not something that can be implemented
Let's say, if this is in attack range
Then it wants transition into 'Attack'
That's how we can implement it
So here, it branches into another two things
First is to move, and second is to transition after checking for conditions
That's how it branches
Same thing here
It starts with waiting for that time to pass, for that second to pass
And then it transitions, looking for Player
Let's go with TickAttack now
We'll modify this part a lot later, but basically
Let's just say time passes and it attacks
First, time passes
And when the cooldown time passes
Meaning, if the time passed is bigger than the cooldown time
Let's say that, then
Then it attacks
For Attack, let's print some log
Attack works like so
And we need to now reset the time that has passed
That's one task
And second, at 'Attack', after the attack
It calculates the distance from the destination again
So get the distance
And if that is out of the attack range
No, when it is in the attack range
If we are to reuse this, it will say
Something like, bigger than the attack range
Then it will move
Or it will transition into 'Move'
So first, time passes
And we have things like cooldown time and attack range
Let's first implement these
And here, Enemy needs to find Player
And it should remember that it has found Player
That's how it will
Mark Player as the destination
I wrote 'Player' here but it's actually the destination
So let me correct that
So let's add some related variables at our header
First, UPROPERTY
class ATPlayer
That's our target
And to move, we need its owner
Since this is just a component, it can't move right away
What makes it move is not in this component
It's in the owner's Movement component
So in order for that movement, it should remember its owner
For that, we use UPROPERTY
And the owner of this component is Enemy
So class AEnemy, and
Because it's basically myself, let's say Me
Let's capitalize the first letter
And then we also have time, 'float CurrentTime'
and 'float AttackRange'
For attack range, let's say around 150cm or 1.5m
And it needs to wait for a second in the beginning
So we'll say FindTime for that
This will be a second, and
Things like FindTime is what the game designer may change later
We can do this so that the designer can change later
What else, other than FindTime?
AttackRange too, I guess, is something up to the designer to finalize
So we have FineTime
Did we have our cooldown time?
We have our cooldown time
Let's say something like 'Delay Time'
Here, AttackDelayTime
Let's set it as a second for now
For all information regarding the balance, let's add this property
So that the designer can finalize them later
I didn't write 0 but the default is 0
Now, let's fill these in
Let's do this part at BeginPlay
And this can be done at our Idle state
So let's move over here
At BeginPlay, let's start with Me
Me is for the owner
And we need the owner here
This component is what Enemy owns
So let's cast this and have it here
Like so
Whenever we do this, we always need a header
I will add mine automatically here
Add something like this
This is the basic part
Whenever you use a class anew
You need a header whenever you're implementing one
So that was on casting
That was Me, and now back to Idle
Let's implement this part
So time should pass, and it is CurrentTime
And =GetWorld, GetDeltaSeconds
We could do this
Or we can just use the DeltaTimeSeconds variable
See, like so, we can do that
And then after a second passes
Not a second, FindTime
If it is bigger than FindTime
Meaning, if CurrenTime is bigger than FindTime
Then it looks for the destination
And since this is a cycle, let's initialize with 0
And let's start looking
Here, UGameplayStatics is a convenient tool
At UGameplayStatics
Here, we have GetActorOfClass
We can use this, GetWorld
We can also have it look for Player pawn
Or our Player character, like so
So Player is ATPSPlayer
And we can add UClass
So we can add StaticClass
And also, to use this, we need a header like so
Let's add a header here as well
I've stressed on adding the header a few tiems
And here we have this part now
And that's how it's done
Now we found Player, and we need to add this part
We have our variable, Target
So we can just get Target like so
And for GetActorOfClass, it returns AActor
So we can't do this, we need to use casting
So that's how we do it
And now we have this part
And if Target is not null
Like so, if Target, not null, then State should be
Transitioned to 'Move' state
And 'transition' here means
That we are changing this value here
And for transition, we use time
When it moves, when it gets the distance, and when it attacks, right?
So we will be reusing this CurrentTime
So let's set it as 0 when we use it
But doing this every time will be a bit time-consuming
Because we'll have to write it out every time
So let's add something that will automate this
Over here
Let's create a function called SetState after void
This will get EEnemyState newState
Let's implement this here
Here it is, at our State
We will add newState
And initialize CurrentTime as 0
Let's do all these at once
So by calling setState, this will be done automatically
Let's now modify what we've done before
This now takes care of resetting the time
Now let's make it move towards the destination
We're at 'Move' now
So let's make it move
When something moves, it moves to a direction
And it moves towards a destination, a target
So for Target
GetActorLocation, let's use that
And this minus my, or Me GetActorLocation
That will give us the direction
And let's use FVector to call this 'direction'
And movement itself is implemented in the character
At Enemy, so using AddMovementInput from above
Will make it move
Let's add our 'direction' here
So we used this 'direction' here
And here, this will automatically
Normalize its size for use
Meaning, there will be a certain length in here
But to really make sure that it normalizes
You can use getSafeNormal to keep the original
And return a normalized copy
So that's how it works
And for this 'direction' it will have the actual distance
So the distance from the target
It's the Size of our 'direction'
And at this function Size, it looks like this
The equation for distance is, you multiply all parts
Multiply everything and square root over everything
This will give you the distance
And if this is less than our attack range
Or let's say this, dist or distance is less than attack range
And that's transition to Attack, using SetState
And for our Attack, just use Attack
And let's move on
For 'Attack', same thing, time passes
CurrentTime
From GetWorld, DeltaTimeSecond
So this CurrentTime now
It is larger than our AttackDelayTime
Then our Enemy will attack
For 'Attack', let's print some log
UE_LOG
My device froze; let's wait a bit
And now, here, let's write
"Attack!!!" for the text
And for UE_LOG, this semicolon is not mandatory
Because for this macro
I'm sure this automatically adds a semicolon in itself
So this is not mandatory
Meaning, adding it won't do anything either
Now, let's initialize CurrentTime
CurrentTime is 0, to make it a cycle
Because, this attack is a repeating action
So that was for 'Attack'
And aside from this, let's say Player ran away
Then Enemy should follow
So let's get the distance again here
For that, we can do this
float dist
And we'd like to get that length again
But see here, we already subtracted it here
And we don't have any here; so we have to
And, shall we do something different this time?
For FVector, there is a function called 'Dist'
You could use this, where you put two locations
You can use that, or there is a function
That uses information on how far the target is from me
So at 'Me'
Me, Distto...
Or, GetDistanceTo, that's the function
And you add 'Target' here
Or you can reverse it
Target can Me could exchange places
You can move it around
But I'll stick to this
This is asking, how far is Me from Target?
And it returns a value
And we compare this to our attack range
And if dist is larger than AttackRange
It means that Player is running away
So Enemy should follow
SetState, and let's use 'Move'
And now we would like some log printed
But not UE_LOG; we'd like to expose it on the screen
So here, let's try this
For our Tick component, let's write it under there
Here, to print the current state on the screen as a log
Then, the current state should become text; FString
strState
This is an enum
This enumerated type, this state
This function can change it into string
So here, write 'UEnum'
Careful with the capitalization; it's lower case after two letters
And then, 'asstring'...
See here
There is a function called GetValueAsString
And we can add our State here
Since this is a template, we can add angle bracket
And add our template here
Or we can just skip this part
This is how we turn State into string
Then what happens is, this sentence is printed
Let's print this
This function called DrawDebugString
This allows for some string to be
Printed on a particular spot on the screen
So let's use this, GetWorld
And second, this is the location
With vector, so we use GetActorLocation from above
And I'd like to print it a little to the top
Around the character's head, so let's use Me, no...
Let's add it as FVector
'To the top' means z value, so 0, 0, 0
And little to the top
Shall we make it 50cm to the top? Like so
And we print our strState here
You can just leave it like this here
But see here, there is this 'Duration' part
So this shouldn't be -1, it should be 0
So let's just do this part, like so
We can add 0, and then
We can assign a color; default is white
FColor, and here is a list of colors
Just add whatever color you like
I'll try cyan
Something bluish, and then here
Duration is 0
To add some shade, you can go with 'True'
And whether to enlarge font size, and 1 means 1x
And 1.nx will make it bigger by 1.n times
I'll just keep it as it is
And let's compile to see what happens
Looking good, let's run it
It's not moving, let's see... We forgot to add our component
Our Enemy component, our FSM component should be here at Enemy
So let's go to the header for Enemy
And let's say UPROPRETY, EditAnywhere
And then here
class, UEnemyFSM
And then, FSM
Let's now implement this
We need to create it, at the bottom of this constructor
FSM, the same thing
CreateDefaultSubobject
And then let's add our UEnemyFSM
The name, let's call it "FSM"
In fact, if this doesn't contain other language, we can skip this 'TEXT' part
But let's just keep it this way
And now, let's compile it again
For this Actor Component, we do not need to attach
Not that we can
And then, oh, there's an error
Oh, we forgot to add the header
Let's add our header like so
And run, now our enemy is approaching
'Move' and 'Attack', see how the state is changing
Enemy is doing great, and then
Now it is 'Damage'
All the basic movements are looking great
Now we need something to happen when Player attacks Enemy
So at EnemyFSM
Let's add a function to count that damage
void OnDamageProcess
Let's name it this way
We could go with AttackDamage, but let's try this
And then let's get the damage value as int
Damage, that's the implementation
For damage, this state 'Damage' can now change
And to change it after getting damage, we need something
We can't just change it right away
We need this concept called HP, so that when it is exhausted
Meaning, when HP hits 0, it goes to 'Die'
And if HP is above 0, it goes to 'Damage'
So now we need our HP part
int HP, let's start with two
Meaning, two hits and Enemy dies
Here, let's subtract 'damage' from 'HP'
If HP is bigger than 0, then setState, and it goes to 'Damage'
And if it is not, if else
Meaning, if it is below 0, it dies
Then, setState and
Actually, let me do this again
'Die' and it goes there
Now that this part is over, let's implement it
For damage, let's do this
First, time passes
And if the time now is our damage delay time
Then transition to 'Move', something like that
And for when Enemy dies, I want to
It should move down to the ground
So move towards the bottom
That's one thing we will implement
Another thing is, we measure time
So time passes
And if current time is our death delay time
Then we'd like to destroy
Destroy who? 'Me', that's how it will look like
Let's now start with 'Damage'
Starting with implementation
Here, CurrentTime GetWorld
DeltaTimeSeconds
And as it accumulates, time will pass
As I told you before, when it accumulates and becomes 1, it means a second has passed
So for current time... Then we need a variable here
We need variables for 'damage delay' and 'death delay'
Let's go to our header and add these two
Like so, copy and paste twice
This will be called 'DamageDelay'
And 'DieDelay', let's name them like so
Let's give our 'DamageDelay' two long seconds
And 'DieDelay' too, let's add two seconds
Should we do two seconds for 'Attack' too? It seems a little too fast
Let's do two for all of these
Now let's finish the other parts
Now that we have our delay time
CurrentTime is bigger
Bigger than? Than our DamageDelay time
Then transition to 'Move'
Here, we just do setState and Move
Then, when Enemy gets shot
It will stand still for two seconds for now
When we add animation later
Two seconds is when it does some reaction and comes back
Since we have no animation now
Let's just implement it this way
And let's finish our 'Die' state as well
I'd like to move the character downward
We can simply do so using P=P0 vt
Let's start with our P0, FVector
P0, this is GetActorLocation
No, it should start from Me, and then GetActorLocation
Now, let's create the 'vt' part
'vt' for FVector is, it is downward
And af FVector we have DownVector
DownVector, this is how it looks like
0, 0, -1
And then we add some movement
So let's give around 200
We could subtract a variable, but let's just keep it here
It looks like there are enough variables
And we multiply ti by our DeltaTimeSeconds
GetWorld, DeltaTimeSeconds
And that's how we create our 'vt' part, right?
This is 'v', direction multiplied by speed, for velocity
And 't' is delta second
Let's make it move now
Me, SetActorLocation
And then P0 vt, now the character moves downward
And we will count for time to destroy it
First, time should pass, so CurrentTime
GetWorld, we can reuse this, right?
And then
Now let's get the time
If CurrentTime is bigger than DieDelayTime
And, if you press Ctrl K D
It will align everything automatically
So you could use it like that
And then, this will be destroying myself
So Me, Destroy
You can implement it like so
Let's run it to see how it looks like
First, compile
In fact, I forgot to work on the gun part
For our Player, here
Here, at OnIAFire, after 'Hit'
If it hits our Enemy
So, 'AEnemy'
FSM of AEnemy
OnDamageProcess
It will call this function like so
Damage, one point
One point of damage for Enemy
It will give a point of damage
And this is how it works, basically, and for that
We need to first know whether it hit Enemy; and to do so
At hitInfo we have GetActor, which is the one that is hit, the other's actor
We can cast this to double check, so let's cast
AEnemy, let's cast like so
Also, add the header like so
Let me bring it up so that I can show you how I'm adding the header
I added this part
And now, Auto, enemy to get this
Since this is the pointer, this will be a better way to write it out
We could just say auto but
Yes, auto*enemy; and for reference, this is how it works
So let's use that
And if Enemy is not null
Then it is indeed our Enemy
So let it try casting like so
And at Enemy, we have FSM
We could approach it right away like this
But actually, this is not the best way to do this
For Object-Oriented Programming (OOP), approaching the other's attribute
And then from there, another attribute, and so on
This is not an optimal way of processing a value at OOP
So a better way is, we implement our v at this Enemy header
And then we could call it right here
So let's create the function, void TakeDamage
Let's implement that
So at Takedamage
We'll deliver this damage to OnDamageProcess at FSM
This is more Object-Oriented
And outside, we can call TakeDamage at Enemy
Let's implement it like that
But we may use TakeDamage elsewhere again
So let's add something like 'OnMy'
Let's modify the name like so
So for Player, we can call
OnMyTakeDamage from Enemy
That will be one point
Save everything
Compile, and let's see how it works
Moving well, and when it gets hit, it stops
And after two seconds, it moves again
And then another shot; it should die, it should move down
It is destroyed but it does not move down
This is because CapsuleComponent is hitting the ground
So let's finish that part
This is done here, now at EnemyFSM
'Die' part is over here
From OnDamageProcess to 'Die' here
At 'Me' there is this capsule, CapsuleComponent
And its SetCollisionEnabled
Let's change it into NoCollision like this
Because we won't be using it, since it's dead
To use this, we need this CapsuleComponent, right?
Then this needs a header
So let's add it here
CapsuleComponent, and add the header like so
And there is no error; let me show you as well
At Components, we have CapsuleComponent
That's how you do it
Then it will remove the error
Let's compile to see the result
Boom, boom, and it moves downward
And destroyed, like so
And let's actually remove the bullet here
We do not need our bullet actor just yet
And that's it
Let's summarize what we've learned this chapter
Creating Enemy Actor and Distinguishing FSM-Based State Creating Enemy Actor How to apply appearance of enemy actor: Create enemy using Quinn
Distinguishing FSM-Based State Finite State Machine: State, Condition, Transition How to branch the current state from Tick to process the enemy actor state
Implementing Actions for Each FSM-Based State Finite State Machine Ups: Each action can be implemented separately Downs: Complex, loses readability
Idle, Move, Attack, Damage, Die States Separate each state and implement each individually Allow for appropriate transition under certain conditions