Transcripts
1. Intro: Combat and video games can seem daunting and a difficult
feature to implement. This course will provide you with all the tools and
knowledge you will need to create a scalable and dynamic combat system in Gado. We will break the
combat system down into individual mechanics
and combine them to create a robust
action combat system that can be customized
to suit your needs. You will learn how to blend combat and movement
animations together. Synchronize hit boxes
with attack animations, implement eye frames
into dodge animations, Lock on to enemies. Fire projectiles from
arranged weapon, and control enemies
with AI scripts. If you need any help, our
discord server is full of other students and
game developers who can answer any
questions you may have. Click the website link
in my profile to join. I'll be using a starter project
made in God version 4.3 that is available on my GitHub using free assets
from it dot IO. But you can follow along with your own project using
different assets. To get the most out
of this course, you will need a scene
in your project with a character the player can control and at least one enemy. You may also want to have an inventory and equipment system capable of equipping characters with a weapon and a shield. For more information
on these topics, see my previous course. Combat doesn't have to
be an uphill battle. Let's draw our weapons and smash through combat together. S.
2. Setup: Hello, friends. Before
we get started, you should have a project with a player character that can move around in an environment, and some enemies
for them to fight. You may also want to
have an inventory menu capable of equipping items, including at least
a mala weapon, a arranged weapon, and a shield. Throughout this course, we
will add controls, mechanics, and animations to our project
for locking onto a target, attacking, dodging, blocking,
and shooting projectiles.
3. Lock On: For our first lesson, we'll allow the player
to lock onto an enemy. Let's get started by adding an input mapping for
toggling the lock. Opening the project settings, switching to the input map tab. We can add an input
event named Toggle Lock. Scrolling down to find
the new input event, I'll bind the Q key on keyboard and the
right stick button on my controller to this event. In the player input
handling script, we'll need references
to the character, spring arm, and camera, all set using add on ready. During the input function, after checking for pause, opening or closing
the inventory menu, and whether the player's
control has been disabled. I've divided the rest
of the input into camera controls and
character controls for better organization. Currently, the only
camera control is rotating the spring arm holding the camera
using the mouse. Let's also check if the toggle
lock button was pressed. This will be a context
sensitive input with multiple different
possible functions. We'll need to know if the player is currently locked
onto a target. So let's declare a
variable to hold the currently locked
target as a node three D, so any three D object in our game world could
potentially being locked onto. We can first divide
the functions of the toggle lock button into
two possible categories, if the value of target
is null or not, which we can shorten
to just if target. Let's start in the s block where the player isn't currently
locked on to anything. Here, we will need to
assign the value of target to be the
nerest visible target, and we can delegate
responsibility for finding out which is the nearest
visible target to the camera. In the case where there is
no nearest visible target, the target will still be null. In many action adventure games, when pressing the
togo lock button, but there is nothing
to lock onto. The camera then resets to be
behind the player character, which will be a function
of the spring arm. If the player is
already locked onto a target and presses
the togo lock button, there are multiple cases
to consider as well. If there are multiple
visible targets, the player will lock on to the next nearrest
visible target, besides the one they are
currently locked onto. We can repurpose
the same function of the camera to find
the nearest target. But this time also provide the current target
as an argument, and we will write the algorithm
to ignore this target. In the case below,
we can still pass the current target since
its value is null, so we will have
nothing to ignore. If there are not multiple
visible targets, this function will also return null and the lock
will be toggled off. Regardless of whether or not
there are multiple targets, if the player is
also pressing down, most games will toggle the
lock off in that case. We can determine if the input
direction being given is similar to down using a dot
product of the two vectors, and checking if it
is larger than 0.75. This will cover any
directions close to down, but not reaching as
far as the diagonals. We want to have the
ability to toggle the lock from outside
of the script, so it would be a good idea to
make it a public function. Let's call it toggle
lock and give it an optional parameter to force lock off with a
default value false. The value of target
will be set to null if it is being forced off. Otherwise, it can be set to the nearest visible target
as determined by the camera, ignoring the current
target if there is one. All three of these
cases above can now call Toggle lock
with only needing to specify that the lock
is being forced off in the case of the down input
direction also being given, and other scripts can also force the player to lock on
or off for any reason. Switching to the
spring arm script. Let's assume that the player presses the toggle lock button, but no targets are visible. We want the spring
arm to position the camera behind the character. So we'll need a reference to the character the spring
arm is attached to, which in my case
is always going to be the parent node
of the spring arm. If you're using a
different node structure, you may want to
export this variable instead and set its value
in the inspector panel. To rotate the camera behind the character
gradually over time, we'll use a tween
variable of type tween. It would be a good
idea to export a variable for the
duration of the tween. I'll use one quarter
of a second. We might also want a value for the preferred x rotation to reset the spring
arms rotation two. I'll use negative 0.5 radians, positioning the camera
slightly above the character, looking forward and
slightly down at an angle. Let's also declare
a variable to hold the tweens target rotation
as a vector three and initialize it to
be a vector three with the reset x
rotation as its x value, but zero for the y
and z rotations. Giving the function
a definition, we can also accept an optional parameter for the duration of the tween using this as its default value so
it can be overwritten. To rotate the camera
behind the character, we can call a new
private function to tween the spring
arms rotation. Specifying that we want
the y rotation to be the character rigs rotation plus a half rotation
represented as Pi radians. Next, let's write
the private function which tweens the
spring arms rotation, accepting a target y
rotation as a float, as well as a duration
for the tween with a default value of the
exported duration. We'll start by setting
the y property of the target rotation
to be the target y rotation accepted
as a parameter. But to ensure that
the rotation always takes the shortest path
around the character, we should wrap its
value to always be between the current
rotation dot y minus a half rotation and the current rotation dot
y plus a half rotation. If a tween already
exists and is running, then we should kill it
before creating a new tween. We can then tween the
spring arms rotation to the target rotation
over the duration. Next, let's let the camera
detect visible targets. My camera doesn't have any
scripted behaviors yet, so let's add a new script and put it in the players
scripts folder. We will need a
public function to get the nearest visible target, accepting an optional
parameter of the current target with
a default value of null, so we can potentially ignore it. This will return a node three D, and the default case
will be to return null if we couldn't
find a visible target. In order to determine which of the visible
targets is nearest, we will need to maintain a list of all the visible targets. Let's declare another
variable to hold all the visible targets as
an array of node three Ds. Initialized to be empty. An easy way of knowing
which targets are visible is to attach an area three
D node to the camera. Let's call it target range. This area will be monitoring for anything on
collision layer 11, which I've chosen to
represent enemies. Make sure your enemies have
their collision layer set to the same layer as is being masked by the lock on targeting. I will also need
to reset the areas transformed to position it
at the camera's origin. The area three D node
needs a collision shape, and we can populate the shape resource
with a convex polygon. Using a convex polygon, we can define the
shape as a pyramid, which exactly copies the
camera's field of view, but with a limited range. Since the camera's
field of view, will take the shape of
a four sided pyramid, we can define it with
five points with the first point being the
same as the cameras position. The other four will be
positioned along the Z axis, the maximum distance
of our lock on range. I'll use a maximum
distance of 15 meters. Switching to top orthogonal view and hiding the
environment for a moment, I'll just quickly
adjust the positions of each point on the pyramid to approximately match the
cameras field of view, 11.4 meters to the
left and to the right. Likewise, in left
orthogonal view, adjusting the y values of
the points of the pyramid, 6.4 meters up and down. Until we have a pyramid shape, matching the cameras
field of view, which reaches out 15 meters. I'll then switch back
to perspective view and talk of the visibility
of the environment back on. Anytime a body enters this area, connecting the body entered
signal to the camera script, We can append the body to our
array of visible targets. Likewise, anytime a
body exits this area, the body can be erased
from the array. Any three D collision
body on layer 11, which exists inside this area will now be considered
a visible target. In my spring arm script, I'll need any tag to use
the get parent function. Running the game and switching
to the remote scene tree, we can select the camera and view its properties
in the inspector, including the array
of visible targets. As the camera moves
around the scene, the array of visible targets
updates automatically to add and remove each enemy as they enter or exit
the field of view. Pressing the toggle lock button won't actually
target anything yet, but we can see how
it repositions the camera behind the character. Before allowing
locking onto a target, we might also want to check
if the player's line of sight to the target is
obstructed by terrain. Let's also add a ray cast three D node to
the camera as well, and name it line of sight. Then grab a reference to
it in the camera script. To find the nearest visible
target with our camera, if the array is empty, its size is zero, then there are no
visible targets to consider, and we
should just return. If there are visible
targets to consider, we will need to compare
the distances of each one to the camera to find
which one is the nearrest. To do that, we'll need
a variable to hold the distance from
the current target to the camera as afloat. The nearest distance as afloat, and the index of the nearrest visible target as an integer. We'll set the
nearrest distance to infinite using the keyword I NF, and the nearrest
index to negative one to mean not a valid index. If after iterating through the entire array of
visible targets, the nearest index is
still negative one, then no visible target
was considered valid, and we should return null. Otherwise, we can return the visible target at
the nearest index. If the visible target at index
I is the current target, we can skip over it with
the continued statement. For any other visible target, we'll need to calculate its
distance from the camera. We can use distance squared since it's mathematically
more efficient. If the current distance is less than the
nearrest distance, then we'll need to
check if the line of sight to this
target is obstructed. First, we'll need to reset any rotation the line
of sight currently has. Then set the target position of the ray cast to the
global position of the target minus
the global position of the camera and
force it to update. But the position of characters
is usually on the ground, which is easily obstructed
by small bits of terrain and not always a good
indicator of line of sight. Let's also add 1 meter up. If the line of sight
is colliding with something and that something
is the visible target, then the line of sight is
not obstructed by anything, which makes this target the nearest visible target
we have found so far. We can set the nearrest distance
to the current distance and the nerest index to I before proceeding through
the rest of the array. This will now return either null or the nearrest visible
target to the camera. The line of sight will also
be colliding with layer 11, looking for enemies, but also layer one to be
obstructed by terrain. It would also be nice to
let the player know what they're locked onto with
the target indicator. Let's add a Sprite three
D node as a child of the player input handler and
rename it target indicator. For the texture, I'll use
scroll bar blank pointer. But change its input
settings to toggle on pre multiplied Alpha to make
it look a bit smoother. And scale it down to a
quarter of its size. Then expanding the flag section, I'll set its billboard
flag to enable, so it is always going
to face the camera. And the no depth test flag will always draw the sprite over everything else regardless of its position in three D space
relative to the camera. The target indicator
can be invisible by default since the player starts without a
target to indicate. Grabbing a reference to the target indicator
using add on ready. We'll then need to position
it over the locked on target. So after determining
what the player is locked onto, if
there is a target. An easy way to have the
target indicator follow the target is to repair
it to that target. We can then move it a few
meters up to position it over the target's head and set its
visible property to true. If there is no
target to indicate, we can repair the indicator
back to this node and set its visibility property
back to falls to hide it. Let's try it out. When we press the
toggle lock button, the nearest enemy is targeted, and the indicator positioned
over the target's head. Pressing the toggle
lock button again, switch is to the
next nearest enemy. Holding down and pressing the toggle lock button
forces the lock off. If there's only
one visible target that we are already locked onto, lock is also toggled off. We now have the player locking
onto enemies in our game. In the next lesson, we'll change the behavior of the camera and the character
while locked on. I'll see you in the next lesson.
4. Strafe: Hello, friends. In
the previous lesson, we added a context sensitive toggle lock
button to our game. In this lesson, we'll
change how the player nodes behave while
locking onto a target. Starting in the player script, once a target has
been locked onto, let's emit a signal so
that any other script can listen and react to the
value of target changing, specifying what was
targeted as a parameter. Using a setter, we
can emit the signal anytime the value of target
is changed for any reason. Now all three of the
character, spring arm, and camera can connect
to the signal, using it to set their
own private variables for what is being targeted. No matter what the
value of target is in the player input
handling script, all of the other nodes will also have the same information, including if it is set to null. Writing a simple function
for each that sets a local target variable so they could all
access it easily. Once our character has
locked onto a target, we would expect them
to face towards the target instead of facing in the direction
they are moving. In my script, rotating the character is being done
in the physics process, which is telling the
character to face towards the direction
of the movement input. Let's declare a new variable
at the top of the script, wants to face
direction as a three. And replace input direction with wants to face direction
in the function call. Then before this function call, we can check if the character
is locked on to a target. If they are locked
on to a target, the direction that
they will want to face will be towards the target, which we can easily determine by subtracting their
global positions. If they are not locked on,
then they will want to face the direction of movement
input, same as it was before. Now the character faces
the locked on target, but their local motion
animations don't look very good, since they are only
blending based on how fast the character is moving
and not the direction. In the character's animation
tree state machine, they are in the jump idle state because there is no
floor in the scene. The loco motion state is a
one dimensional blend space, blending the idle walk and run animations based on the
character's movement speed. Let's remove this state and replace it with
another state machine. Reconnecting the transitions
to the other states, starting with a
transition from start to locomotion to make
it the default state. I had the transition
to jump start enabled with a cross
weight of 0.3 seconds. And jump land to
locomotion switching at end with a cross
fate of 0.67 seconds. I'll omit the transition
from locomotion to jump idle temporarily while working in the locomotion state machine, so the character will remain in the locomotion state and I can see the results
in real time. Pressing the play button will
force the character into the locomotion
state and they will tpose because it's
currently empty. Inside the locomotion state, we'll need to have
two different sets of locomotion animations for
when the character is locked onto a target
and when they aren't and be able to
transition between them. The default will be the
same as it was before, a one dimensional blond space. Let's name it not locked on. Then we can add a two
dimensional blond space as well. Let's name this one locked on. Adding a transition
from start to not locked on makes
this one the default. We can then transition
between not locked on to locked on if the value
of target is not null, which we can shorten to just target and give it
a short cross fade. And return back
to not locked on, if not target with
the same cross fade. Inside the not locked
on blend space. We can recreate it
exactly as it was before, with a minimum value
of zero blending idle, walking and running animations based on the character's
movement speed. With that done, use the bread crumbs to
go up one level to the locomotion state machine
and press the play button on the locked on state machine to force the character
into that state. Inside the locked
on blend space, we'll blend together
the animations based on which direction the character is moving relative to their
forward direction, which is towards their target. I'll set the grid snapping to increments of one to
make things easier. If we position our view to
be behind the character and imagine that they are locked onto a target in front of them, then the y axis
represents how fast the character is moving towards
or away from the target. The x axis represents how fast the character is moving
to the left or right, strafing around the target. Let's start by putting the
idle animation in the middle. For the x axis,
we'll want to put a strafing left animation on the left side and a strafing right animation
on the right side. For the y axis, we can put a running animation at
the forward position, which will generate
triangles in the blend space showing how the
different animations will be blended together. And adding a walking
backwards animation in the negative y position, we'll also add more triangles. If we change the blend position, we can see how the
character will look while giving movement input
in any direction. Depending on the
assets you're using, you may also have diagonal
movement animations to add to this blend space. Sometimes this preview
does not accurately simulate how the character will behave while the
game is running. And you should always
test it out by playing the game to make sure it
looks good in the game. But before we do
that, let's return to the root level of the
state machine and add the transition from
locomotion to jump idle under the condition that the character
is not on the floor. I used to cross fade time of 0.3 seconds for this transition. We also need to update
the part of our script, which sets the blend
position of the blend space, which in my character script is happening in ground physics. Selecting the animation tree, we can copy the property
path of the not locked on blend position and updated
in the set function. But this only needs to be set if the character isn't
locked on to a target. If they are locked
on to a target, we will need to set
the blend position of the locked on
blend space instead. The locked on blend position is a vector two with
both an x and a y, which we will need to calculate. So let's declare a variable to hold it named locked on blend. To avoid repeating the same
calculations multiple times, I'll also declare
another variable to store the character's
relative velocity, which will be their x z velocity divided by their running speed. After calculating this value, the blend position
of the not locked on blend space can be set
to this vector's length. If the character is
locked onto a target, we can find the x value of
the blend position using the dot product of the rigs global basis vector x with their relative velocity. The dot product of two
vectors results in a number representing how
similar or different they are. So we are checking how similar the character's
relative velocity is to a vector pointing
directly to their right. Depending on how your
character is set up, which direction is
considered forward, you may need to
multiply this value by negative one to reverse it. The y blend position can also be determined using
the same method, but using the character's
forward z vector. Next, while locked
onto a target, the camera should look at the target, not the
player character. In the camera's
process function, ignoring Delta, If
there's a target, we can tell the camera
to look at the target and adding 1 meter up to
not look at their feet. When setting the
value of target, if the target is set to null, then we can reset the
camera's rotation to revert back to the
same as its parent node, the spring arm, setting
its rotation to vector 30. We might also want to
trigger the lock on function if the current target exits
our visible range limit. Ether locking off or automatically switching
to a closer target. So let's add a signal for when the current target
exits range and emit it if the body which exited the area is
the current target. We can then connect this signal to the player input
handling script. Calling the toggle
lock function. Depending on whether you
want the behavior to switch to the nearest
target or simply lock off, you may wish to bind
a true argument for the force of parameter. In the spring arm script, while locked onto a target, we'll want the spring arm to always point away
from the target to keep both the target and the player character in
view of the camera. Let's add a new variable to hold that direction
as a vector three. Then when the value
of target is being set, if it's not null, then we can set the value of target direction to
be the difference in our global position minus the
target's global position. This will result in
a vector pointing from the target back to
the player character. We can then re use the
tween rotation function, but it is expecting a float parameter
representing a y rotation. We can get the y rotation, which will point
in this direction using the trigonometry function, A t two, giving it the x and z values of
our target direction. Once the target has been set, the spring arm will spin
the camera around to be in the opposite direction from
the target behind the player. But we want it to stay there. In the process function, if the value of target has
been set and in parentheses, if either the tween does not
exist or is not running, meaning the tween has finished and the spring arm
is in position, then we can perform
the same calculations, but just set the
value of rotation y directly instead of
doing another tween. This will keep the spring arm pointing away from the target, while locked onto the target, keeping the target
and the player character in view of the camera. Lastly, in the player
input handling script, we want to prevent
the player from rotating the spring arm
while locked onto a target. This is happening in two
different places in my script, one for PC controls
using the mouse, and another for controller
support with the right stick. So I'll just wrap both in an if statement to not allow
these inputs while locked onto a target. Let's try it out. When we lock onto an enemy, the camera focuses
on the target, while the spring arm rotates to position the camera in
the opposite direction. Moving the character,
they rotate to face the enemy and animate fluid
movements in all directions. The spring arm maintains the camera's position of
being behind the character, pointing away from the target, while the camera continues
to look at the target. The player's normal
camera controls have been disabled
while locked on. If the locked on target
is out of range, then it will
automatically switch to the nearest target or lock off if there isn't
one to lock onto. If we force to lock off, then the camera refocuses
on the player character, normal control of the
spring arm resumes, and the characters locomotion animations return to normal. We now have our
player nodes adapting their behaviors to being
locked onto a target. In the next lesson, we'll have the player character
perform attacks. I'll see you in the next lesson.
5. Attack: Hello, friends. In
the previous lesson, we changed the behavior of the player nodes while
locked on to a target. In this lesson, we'll add attack animations so the characters can
attack each other. Let's start by opening
the project settings, the input map tab, and adding a new input
event for attacking. I'll use the left mouse button and the right shoulder
button on my controller. Then in the player
input handler script, we can check if the
attack button was pressed when checking for
character control inputs. If the attack button
was pressed this frame, then we should tell the
character to attack. But what if the
character is busy? They could be jumping or
performing some other action and we might not want them to be able to attack
right this moment. Requiring the player to
wait until attacking is possible before
pressing the button would be quite frustrating. Most modern games will
consider the button press to be valid for a short time
after the button is pressed, which is known as
input buffering. Let's add a time or node to
the player input handler. Name it input buffer, and set its one shot
property to true, so it will only count
down once at a time. Grabbing a reference to this input buffer timer
using add on ready, we can start the timer after telling the
character to attack, then await its timeout signal. After the timer has
counted down to zero, we will then tell
the character to cancel the instruction
that was given to attack. So at any moment while the
timer is counting down, the character will try to
attack when they are ready. The default wait time for
the timer is 1 second, which is a reasonable amount of time for the attack
input buffer. Another input we might want
to buffer is the jump input. Following the same pattern
as the attack input, we can start the timer, await its time out signal, then cancel the jump input. In the character script, let's start by creating a
new code region for combat. Then move the player targeted
function into this region. We'll need a public function
to tell the character to attack and another to tell them to cancel
the instruction. The jump function will also need a match and cancel
jump function two. The main purpose
of these functions is to set variables which will be checked by
the animation tree so it knows which
states to travel to. At the top of the script, we'll add more
variables to indicate the character's intentions of
what they want to do next, Buffered inputs as Booleans, whether they want to
jump or want to attack. Then the attack function
will set the wants to attack variable to true and cancel attack will
set it back to false. Since the input is
being buffered, we no longer need
to require that the character be able to
move or be on the floor, and we can delegate
that responsibility to the Animation
trees state machine. And we also don't need to tell the Animation tree
state machine playback to travel to the
jump start state. We will make this transition automatic if the
conditions are met. But we don't want both of these variables to be true
at the same time. The character will either want to jump or attack, not both. So anytime a buffered
input is set to true, all others will be set to false, overriding
the instruction. In the character
scene, selecting the animation tree and switching to the
animation tree panel, we can see the state machine. Let's change the
transition leading from locomotion to
jump start to be automatic under the
condition that the character is on the floor and can
move and wants to jump. Now the character will
automatically jump as soon as they are able within 1 second of
pressing the jump button. This is particularly useful for chaining jumps
one after another, since the player will
be able to press the jump button before
hitting the ground. But looking at the
jump state machine, let's consider how to incorporate
the attack animations. We could transition
from locomotion to attack and back again. But the animations
would look bad if we want the character to be able
to move while attacking. I wouldn't allow the character
to attack while jumping. Instead, it would be better
to separate animations that involve moving the character from those of
performing actions. If your state
machine hasn't been saved as a project
resource already, click on the drop down beside Tree Root and select Save As. I've already saved mine
in the character scenes folder and named it
character animations. But I'm going to rename it
to character movements. With the state machine saved, let's remove it from the
character's animation tree, and instead replace it with
an animation node blend tree. The animation tree panel
now displays a graph, which will hold our blend tree, allowing us to blend any
amount of animations together. Right clicking in empty
space, select load. Then navigate your
project resources to find your character
movement state machine. The state machine we were using previously is now a node
in the Blend tree graph, and the result of the
state machine will be output by the pin
on its right side. If we connect the output of the state machine to the
output of the blend tree, then the animation will
work just as it did before. But the purpose of
the blend tree is to blend multiple
animations together. Let's disconnect the pin. Then right click and
add a blend two node. The blend two node
will start with the animation
connected to its in input pin and blend in a percentage of the animation connected to its
blend input pin. The result of this blend will be output from the pin
on the right side, which we can connect to
the blend trees output. The animation we want to blend with the character's
movements will be the character's actions,
namely their attacks. We can add another
state machine to the blend tree and
name it action. Then connect its output to the blend two nodes
blend input pin. Next, we'll open the
action state machine and start the character
in the idle animation. Transitioning from start to idle to make it the
default action state. Next, we'll need a view of our character and to start
both the movement and action state machines
by clicking on the play button on their
respective start states. Then return to the root
of the blend tree. Our character still
appears to be in the jump idle
animation because the percentage of
the action animation being blended into it
is currently zero. If we drag the
handle over to one, we can see the animation gradually change from
jump idle to idle. Next, click on the
edit filters button, and turn on enable filtering. Let's move the window
out of the way so we can see our character as we
filter individual bones, deciding which bones
our action animations should be applied to
and which it shouldn't. Turn on all of the bones which comprise of the
character's upper body. Now the character's lower body, including their position, is being affected by
the movement state. Jump idle, while
their upper body is being controlled by
their action state idle. This allows us to blend these animations together
in three different ways. If the character is moving but
not performing any action, then the blend
amount will be zero, using only the output of
the movement state machine. If the character is performing
an action but not moving, then the blend
amount will be one, and filters turned off, using only the output of
the action state machine. If the character is both moving
and performing an action, then the filters
will be turned on. Using the output of the
movement state machine for the character's lower body and the output of the action state machine for
their upper body. Let's name this blend two
node lower plus upper, since it's blending the
upper body animations into the lower body animations. But if the blend
amount is zero or one, then the animations in one of the state machines will not be run to save on processing time, which can result in
some problems if we are relying on them to set
properties or call methods. It can be useful to always have both state machines
running at all times, even if the character is not performing any actions
or not moving. To ensure both state
machines are always active, we can set the
blend amount to 1%, and when performing an action, increase it to 99%. We can assume that it should start with a blend amount of 1% since the character won't be performing any actions
when first loaded. Back in the character script, since the animation tree is going to become
much more complex, it would be a good idea to abstract its behaviors
into its own script. We can remove the state
machine playback variable from the character script and add a new script to
the animation tree. I'll save it in the
character scripts folder. In the animation
trees new script, our main objective is to create the three animation
blends we just mentioned. In the process function, we'll need to set the
blend amount parameter like we did with
the blend spaces. Copying the property path and
specifying a blend amount. Let's make blend
amount a private float variable with a
default value of 1%, and pass this as the argument. We will then need to change
the value of blend amount based on whether or not the character is
performing an action. Let's store the
action state machines playback in a variable. This time, using
the self keyword, since this script is
attached to the tree and using the property path to the playback of the
action state machine. Then in the process function, we can check which node the action state playback
is currently in. And if it is in the idle state, then that means the character is not performing any actions, and we can set
blend amount to 1%. Otherwise, they are performing an action and we
can set it to 99%. But this will be an
instantaneous change which might look a bit jittery. So instead of setting the
variable directly to 1% or 99%, let's just move it toward that number by a small
amount over time. Using the move toward function, we can start from what the
variable currently is, moving it toward what we
want it to be by Delta. This will make the
transition take 1 second, which is a bit long. So let's multiply Delta by a new variable to
hold the blend speed, which I'll give a
default value of four. The transition will take
one quarter of a second. To turn the filters on or off, we will need access to the
blend two node directly. So declaring a
variable to hold it, which is of type
animation node blend two. We can access it starting from the animation trees tree root, using the get node function. This function accepts
a string name, which we can specify as
the same name we gave the node in the blend tree
graph, lower plus upper. Be careful to get the
spelling exactly the same. We aren't allowed to
turn the filters on or off while the animation
tree is processing. So Let's make a public function to let the character
script do this for us, telling the animation tree whether or not the
character is moving. If the character is moving, then the filters should
be enabled to blend the upper body actions into
the lower body movements. If the character is stationary, then filters can be disabled to only use the
action animations. While we're here, let's also add two more functions to
set the blend amounts of the locomotion
blend spaces to keep them abstracted from
the character script. Set locked on blend, accepting a vector two
parameter for the blend amount, setting the blend
amount parameter of the locked on blend space. And set not locked on blend, accepting a float parameter
for the blend amount, setting the blend
amount parameter of the not locked
on blend space. Back in the character script, we can see that
the property paths to the blend amounts
are no longer valid, since they don't refer to
the movement state machine. Abstracting these
functions, we will keep any changes to the
animation tree contained within its own script. In the normal physics process, We'll also need to tell
the animation tree whether or not the character
is currently moving. However, we are now assuming
that every character must have this script attached
to their animation tree, and that they are using
the same animation tree. So let's save our
new animation tree as a project resource. I'll put it in the
character scenes folder, along with the character
movement state machine. Then update each of the
other characters to use the new animation tree and
attach the script to it, so they will all function
in the same way. I'm only using the Barbarian and skeletons in this project, but the night mage and rogue should also be updated
if you're using them. However, this creates
a new problem. If every character shares the
same blend tree resource, then changing the enable
filters property for one character will change
it for all characters. We can get around this by giving each character their own
unique copy of the blend tree. To avoid having to replicate every change in every character's
blend tree every time, I'll use a simple shortcut. In the animation tree script, during the ready function, I'll set the tree
root property to be a duplicate of
the blend tree. Passing true as an argument
will also duplicate any sub resources this
resource is using. Since the blend tree, this
animation tree is using as its root is no longer the same one it was
originally assigned. We will need to
assign the values of the action state playback and the blend two node
after this duplication. Now every time a
character is loaded, they will make their
own unique copy of the blend tree and be able to change their
filtering behavior without affecting
other characters. Let's return to the characters
action state machine. Our character doesn't have
any equipped items yet, so let's transition
them into unarmed idol. For now, let's make
the condition for this transition be if they are targeting an enemy
or if they want to attack using a
short cross fade, and they can transition
back if they are not targeting an enemy and
don't want to attack. Then to keep things organized. Also add another state machine
named unarmed attacks, and transition into
the state machine if the character
wants to attack. They can then transition back to unarmed idol at the end
of the attack animation. Editing the unarmed
state machine. Let's start with unarmed
male attack punch A. This will transition
to either end or to unarmed male attack
punch B at the end of the animation based on whether or not the character
wants to attack again. We can repeat this pattern
transitioning to either end or unarmed male attack kick
at the end of the animation, based on whether or
not the character wants to attack yet again. And we can create an
endless cycling combo by transitioning back to the first punch as long as the player keeps
pressing the attack button. When they stop pressing
the attack button, the state machine will
transition to end, which will return
back to unarmed idle. We might also want to restrict how the player moves
while attacking. For example, it wouldn't
make any sense for the character to move at all
during the kick animation, since their feet should be busy. I'll zoom in on the
animation track display to better fit the length
of these animations. Let's add a public function to the character script
to restrict movement, accepting a Boolean parameter for whether or not the
character can move. We'll then set the value of the move variable
to be the opposite. In the character's
kick animation. We can add a call method track calling a method on the
character's root node. Calling the restrict
movement function at the start of the animation, passing true as the argument, restricting the character's
movement while kicking. And another key at the end
of the animation can restore the character's ability to move by changing the
argument to false. But for this to work,
we need to return to the blend tree and
add functions to the blend two nodes
filters so that if the character is both moving
and performing an action, the actions functions
will be called. L et's try it out.
Our animations appear to be working
as they did before. As soon as we tell the
character to attack, they transition into
the unarmed idle state before performing an attack. If we keep cicking, they will
keep attacking repeatedly, cycling through the different
unarmed attack animations. It is even possible to move
or jump while attacking, and the character will use the appropriate animations for their upper and lower body, but they are not allowed to
move or jump while kicking. If we lock onto a target, the character transitions
into the unarmed idle state and locking off transitions
back to the normal idol. We now have our character
performing unarmed attacks. In the next lesson, we'll add
weapon attack animations. I'll see you in the next lesson.
6. Weapon: Hello, friends. In
the previous lesson, we blended upper body actions
with lower body movements, allowing our character to
perform unarmed attacks. In this lesson, we'll add a variety of weapon attack
animations as well. Let's start in the characters
animation tree where we already have unarmed idle
and unarmed attacks. Following this
pattern, we'll add the two handed idle animation. In this asset pack, there are no one handed or dual
wheeled idle animations. But we can just
add two new copies of the idle animation
and rename them. From each of these
idle animations, we can then connect to attack state machines that work just
like the unarmed attacks. Connecting from each
of the idle animations to the attacks if the
character wants to attack, and returning at the
end of the animation. We only need a way of telling
the character which set of animations to use based
on their equipped weapon. In this project, we have
a script named enums, holding all of our enumerations, to which we will add
another weapon type. Starting with unarmed
as the default, then listing out each of the different weapon types that need different animations, one handed mala, two handed
mala and dual wheeld. It is important to know that the enumeration is
just a numbered list, so each of these values
are actually numbers, starting with
unarmed being zero, one handed is one,
two handed is two, and dual wheel is three. In the character
script, we'll add another variable named
attack animation with a type of our
new enumeration. We can export this variable
for testing purposes. L et's change the
character's attack animation to something other than unarmed. I'll use two handed melee. Back in the action
state machine, we can set the transition
condition to move from idle to unarmed idle to be if the character's attack
animation is zero, which is unarmed in
our enumeration, and also at least one of the other conditions by
putting them in parentheses. The return condition will
then also be the opposite. Either the attack
animation is not zero, or both of the other
conditions are met. Adding transitions to each of
the different idle states, we can set their conditions to be the same as unarmed idle, only using a different value
for the attack animation. I'll give each of them the
same cross fade time as well. Then transitioning back to idle under the logically
opposite conditions. Inside each of the
attack state machines, we can use the attack
animations the same way we did with
the unarmed attacks. There are four different
one handed attacks, creating a cycle of
different animations as long as the character
wants to keep attacking. Repeating the same process
for two handed attacks, which only has three
different attacks. And the dual wheeled
state machine also only has three attacks. We can test this
out in the game, since the characters attack animation is set to two handed, they use the two handed
attack animations. We can tell them to use one
handed attack animations or dual wheeled
animations as well. Now, we only need to change the variable when equipping
items to the character. In the custom resources, we will need a more
specific class to describe a weapon
inheriting from equipment Giving it the class name weapon, we can add an extra
exported variable to store the type
of weapon it is, and we can use the same
enumeration as the type. Then any of the item
resources in the project that are weapons can be
reclassified as weapons, giving them a value
for their weapon type, so the character will know which animations to use
when equipping them. For demonstration purposes, I'll update the x to be a
one handed weapon. Remember to repopulate
any fields that are not carried over when
changing the resource script. The grade as as a
two handed weapon, and the dagger as a
dual wheeled weapon. In the int or whichever script you're
using to equip items, we will need an exception for if the item being
equipped is a weapon. And if it is either
a two handed weapon or a dual wield weapon, then we will need
to remove anything the character has equipped
in their off hand slot. C hecking first if the file, progress equipment array has anything equipped in
the offhand slot. I'll then use that to
index the item buttons in the player's inventory and remove the E for equipped
from the button. Then also set the
equipment arrays value to negative one or empty. I'll then tell the
character to dof any equipment in their
off hand socket. In the character script, the character will need to know what they are holding
in their hands. Let's add variables of type node three D to hold their main hand and
off hand equipment. Our character donning a
weapon can then also have some extra logic added for
if the item is a weapon. The character's main hand item can then be assigned
to this weapon, and the attack animations
for the characters can be set to match that of
the weapon being equipped. If the item is a dual
wheel type weapon, I'll also add another
duplicate copy of the weapon in the
character's off hand socket. And assign it to the
off hand variable. When doffing a
piece of equipment, if that equipment is being removed from the
character's main hand, then we can set the
main hand variable to null and also check if they
are currently dual wielding. If dual wielding, I'll tell the character to do the item
in their off hand as well. And then I'll set the attack
animation to unarmed. Likewise, if the socket being
doffed is the off hand, then we should set the
offhand variable to null. We no longer need to export
the attack animation, since it will now be set
by the equipped weapon. Now the character starts
using unarmed attacks. But when equipped with
a one handed weapon switches to using
one handed attacks. We can equip them with a shield, then switch to a
two handed weapon, and the shield will be
automatically removed. The character now uses two
handed attack animations. Switching to the dual
wheeled daggers, the character has a
dagger in both hands and uses the dual wheeld
attack animations. Un equipping the daggers, the character returns to
using unarmed attacks. The last thing our attacks
need are hip boxes. Collision areas for detecting if the attack actually hits an
enemy and dealing damage. Opening up the weapon scenes, we can add hit boxes
to each of them. Using an area three D node, which won't be monitoring
or monitorable by default, nor have any collision layer or collision mask until
they are told to do so. Since these character
models have short limbs and small weapons, their attacks don't
reach very far. So I'm going to be very
generous with their hip boxes, making them larger than
the actual weapons. Creating a new script for the weapons,
inheriting from item. I'll replace the
script attached to the weapons root node
and open it for editing. We can tell the
weapon to grab or reference to its hit
box using add on ready. Then add functions to set the hit boxes collision
mask to a new integer. And another that will
activate or deactivate the hit box by setting
its monitoring property. The character holding
this weapon can now specify which
collision layer their enemies are on and activated hit box when
swinging the weapon. The character model
will also need a hip box for unarmed attacks. Just like the
weapons, it doesn't need to be monitoring,
monitorable, nor have a collision layer or collision mask and will have an oversized
collision shape. But we don't need to position
it right now and can delegate that responsibility
to the animations. In the character
script, we can grab a reference to the unarmed
hip box using add on ready. Remember to add a hip
box to every character or use the get node or null
function to avoid errors. Let's also export a
three D physics layer to hold the collision layer of
this character's enemies. This will add a grid of numbers in the
inspector just like the collision layers in the area three D notes properties. This way, we can specify
that this character will be trying to hit things on the
enemy Hurt collision layer, which I have said
to be layer 18. Meanwhile, enemies using
the same script can set their enemy collision layer to be the player's HRT
collision layer. During the ready function, we can set the collision mask of the unarmed hip box to be
the enemy's collision layer. Likewise, anytime a
weapon is equipped, we can set its collision mask to be the enemy's
collision layer, so it will know which layers to mask regardless of
who is holding it. We'll then need
some functions in the character script
to activate and deactivate the hip boxes of the weapons during the
attack animations, accepting a boolean parameter to specify if the hip box is being activated or deactivated and an integer for which hand? And I'll give it a default value of one to mean the main hand. Using a match statement, we can create three
separate cases, activating the hip box
for unarmed attacks, main hand attacks,
or off hand attacks. If unarmed, we can set the monitoring property of the
unarmed hip box to active. If main hand or offhand, then we can tell the weapon to activate or deactivate
its hit box. Our characters animations
can then have tracks added to them to activate and
deactivate the hit boxes. I'll just demonstrate with
one attack of each type, starting with one handed
male attack chop. Adding a call method
track to this animation, we would generally
expect the hit box to be activated just after
it starts swinging. Adding a key in this moment, we can change the Boolean
argument to true to activate the hit box of the
character's main hand weapon. Then deactivated at the end of the swing by changing
the argument to false. This will prevent enemies
from walking into the weapon when it isn't moving
from being harmed by it, and only deal damage when the character is actually
swinging the weapon with force. The process is the same when wielding a weapon into hands. Adding a track, then key framing function
calls to activate the hit box at the start of the weapon swing and deactivating it at
the end of the swing. When dual wielding,
we will need to specify which hip box
is being activated, whether it is the main
hand or the off hand, by also changing the
integer parameter to two, if the hip box
being activated or deactivated belongs to
the off hand weapon. And when unarmed, we will not only need to
activate the hip box, but also position it to be
in the correct location. Using a property track to key frame the hip boxes
position property. We'll then change the integer argument in the function calls to zero to indicate that we want to use the unarmed hip box. It's a good idea to make
sure the hip box is in position at least one frame
before activating it. It helps to use orthogonal
views to position the hip box exactly on the character's fist from at
least two different angles. Then click on the key icon to key frame this position
in the animation. We won't be able to test
these hip boxes until the next lesson when we add
hurt boxes for them to hit. The hip box will not only
activate with the animation, but then also follow
the character's fist. Remember that you'll
need to complete this process for every
attack animation. We now have our
character attacking with different animations
for each weapon type. In the next lesson, we'll allow characters to get
hurt and take damage. I'll see you in the next lesson.
7. Hit: Hello, friends. In
the previous lesson, we added hit boxes to our
weapons and attack animations. In this lesson we'll
add Hurt boxes to our characters so they
can get hit by attacks. A characters Hurt box is just
another area three D node. We'll be adding to
the character models. They will not be monitoring, but will be monitorable
by the hit boxes. They will exist on
either the players Hurt box layer or the enemy's heart box
layer appropriately. But don't need to mask anything since they aren't
monitoring anyway. For the Hert boxes
collision shape, another capsule is very common, but it is usually smaller than the character body
collision shape and entirely contained within
the character model. It wouldn't feel fair
to the player to get hit by something that doesn't
appear to make contact. Keep in mind that
the weapons are using oversized hip boxes, so the hert boxes being undersized would
also be reasonable, but I'll make mine fit the
character's head and body. I'll then copy and paste the same hert box into my
enemy character scenes, changing their collision
layer to the enemy HRT layer. Then in our weapon scenes, I'll use the axe as an example. We'll need to connect
the hit box's area entered signal to
the weapon script. If this weapons hit box enters
a character's Hurt box, then we will need this weapon to inflict damage to
that character. For now, let's add a public variable for
this weapons damage. When the collision happens, we'll need a reference to the character that the
Hurt box belongs to. In my project, that will always be the parent
node of the Hurt box. We can then tell the
character to take damage, passing the weapons
damage as an argument. It might also be nice to know the direction the
damage is coming from. So we can use the
difference between their global
positions normalized. To keep all of our weapons
information in one place, its damage should be added to the resource file as
an exported variable, and I'll give it a default
value of two damage. Each different weapon
resource file can then specify how much damage
that weapon will inflict. Remember to connect the area entered signal for each
weapon scene as well. Likewise, our character's
unarmed hip box also needs a signal connection to tell the character to
deal unarmed damage. Getting the Hurt Box's parent
and telling them to take one damage and providing
the normalized direction. Then in the character script, when we create the
weapon instance, we can set its damage to be the same number
from the resource. I. While we're here, let's also write the take damage function in
the combat region. Accepting a damage
amount to take, as well as the direction
the damage is coming from, giving it a default
value of vector 30. In order to take damage, our characters will need health. I'll add an export category
for the combat variables. Then declare one
for the characters maximum health as an integer, which I will set to five. And their current health
also as an integer, which we can set to their maximum health
using add on ready. Let's also declare a couple of boolean parameters for whether or not the character is dead, and if the damage being taken
was delivered from behind, and also grab a reference to the character's rt box
using add on ready. When taking damage, the
character's current health will be reduced by the
amount of damage received, but we don't want to allow
it to go below zero. So we can use the max
function to either set it to this calculation or
zero, whichever is higher. The from behind
Boolean can then be set to true if the
dot product of the damage direction with the character rigs forward basis vector is a
negative number. If the character's
current health after taking damage is zero,
then they will die. Otherwise, they will be hit. While the death animation
will be automatic, the hit animation can be handled by the
animation trees script, and we will tell it
whether or not the damage received was under or
over a certain threshold. This way, we can change the hit animation based on the
amount of damage received. We may also want to track a character's health
or when they die. It would be a good idea to broadcast both of those
events as signals. Declaring a signal for anytime the character's
health changes, we can use a percentage
of health remaining as an argument and another signal for when the character dies. Then emit the health
changed signal if they take damage and the die signal
if the character dies. To calculate their percentage
of health remaining, we just divide their current
health by their MX health. But since both of these
values are integers, the result will
also be an integer, so it will only ever
result in zero or one. We'll need to cast at
least one of them to afloat first before
performing the division, so the result will
also be afloat. If we no longer want
dead characters to be impacted by collisions, we can also set their
collision layer to zero, their collision mask to one, so they will only be
affected by terrain and set their H rtbach
monitorable property to false. It is also possible
that a character might be hit or killed while
performing an action. So we should also keep track
of which actions being interrupted may cause problems and correct them in
a private function. For example, we should
probably turn off all of their hip boxes if their
attacks were interrupted, which we can also do in
a separate function, checking if references to each exists before
turning them off. In the characters
animation tree, we will want the character
dying or being hit to interrupt and override
all other animations. Since this blend tree is
already saved as a resource, we can remove it from
the animation tree and replace it with
a state machine. At the very root level
of the state machine, we will start with our
characters death animations, since those will have the
absolute highest priority over all other animations. Then add another state machine
for the hit animations. We will transition from hit to death A if the
character is dead. With a short cross fade. Or alternatively
to death B if they were also hit from behind giving this priority
by setting it to zero. There may be no need for our character to transition
back from being dead, but it doesn't hurt
to add the transition with the condition being
that they are not dead. Inside the hit state machine, we'll do a similar thing with the hit animations and add our blend tree as the
default animation here. Enabling the transitions to the hit animations and returning to the blend tree at the end of their animations. With a cross fade both into and out of the
hit animations, With the animation tree
state machine updated, we will save it as
a project resource. I'll name it
character animations. And for the final
time, every character can be updated to use
the new state machine. In the Animation trees script, we will need a reference
to the hit state playback. Also, since we updated
the format of the tree, we will need to update all
of these property paths. We will now access
the blend node by getting the hit node, then the blend tree node, and finally, the lower
plus upper blend two node. Giving the get hit
function a definition, we can receive the
Boolean parameter as whether or not the character
is getting hit lightly. Then tell the hit state
to travel to either hit A if they were hit lightly or hit B if they were hit harder. Let's try it out. If
we punch the skeleton, they react to being hit lightly, equipping an axe and
attacking them again. They are hit harder. A
third hit and they die, falling backwards since they
were hit from the front. We can equip the great ax, which I've set to
have a damage of five and kill another
skeleton from behind, and they fall
forward as they die. It would also be
nice to be able to track the player character's
health on the UI. Let's add a control node
and call it health gauge. I'll put it behind
everything else on the UI. This will need a text
direct to draw the border, a color erect as the fill, and another text direct
if we want an icon. I'll use slim slider frame
as the border image. It's very large, so I'll scale it down to one
quarter of its size. And I'll use icon, small heart full for the icon. Resize it and position it where it looks nice
relative to the border. I'll then set the color for the color ect to match
the color of the heart, and set its position and
dimensions to fill the gauge. I'll reset its size property, anchor this to the
bottom left corner. Clicking and dragging on the
size x property of the fill, we can see how the gauge can easily be set to any percentage. Attaching a script
to the health gauge, we can just call it gauge to reuse it for any other
gauges we might want. We'll grab a reference to the fill color ret
using add on ready, and also declare a
variable to hold the size dot x of the fill when the gauge
is completely filled, which is what the
size dot x property is set to when first loaded. Then write a public
function named set value, accepting a percentage
float parameter. All we need to do is set
the size x property of the color t to be the percentage multiplied
by the maximum size. Connecting the player
character's health change signal to the gauges
set value function, it will now automatically
track the player's health. To test the health gauge. I'll just tell the
character to take two damage every time the
player presses the run button. Now we can see the character's
health gauge deplete, and the character dies
when it is empty. I'll then reset the take
damage line back to run. We now have our
character getting hit and taking
damage from attacks. And the next lesson, we'll allow characters to avoid
getting hit by dodging. I'll see you in the next lesson.
8. Dodge: Hello, friends. In
the previous lesson, we allowed our characters
to get hit by attacks, take damage, and die. In this lesson, we'll help
them avoid damage by dodging. Let's start in the
project settings, the input Map tab, adding an input
event for dodging. I'll use the space bar or the right face button
on my controller. In the player script, we can use the same methods as jump and attack to buffer
the dodge input. In the character script, we'll need a variable for whether or not the character
wants to dodge. But a dodge isn't
just a simple input. It also requires a direction
as a vector three. In the combat region, we'll need a dodge function
and a cancel dodge function, setting the Boolean
variable appropriately. And with any input buffered, all other buffered inputs
will be set to false. Since we're already storing the input direction in
the character script, we can check if that is vector 30 at the moment the
character was told to dodge. If not giving any input, I want the character
to dodge backwards. I'll use the character
rigs bases forward vector, multiplying it by negative
one to reverse it. If we are giving
directional input, I want the character to
dodge in that direction, but always with full force. I'll normalize the direction to remove magnitude
from the equation. In the characters animation
tree state machine, navigating through
the death and hit animations into the blend tree, and finally, the
movement state machine. We can add the dodge animations in a two dimensional
blend space. Transitioning from locomotion to dodge if the character
wants to dodge, and back to locomotion at
the end of the animation. Both with a cross fade. Since this is connecting
to locomotion, the dodge will only be performed while the character
is on the floor. You may also want to
have aerial dodges connected to the
jump idle state. Inside the dodge blend space, I'll change the grid
snapping to factors of one. Then add Dodge forward, Dodge backward, Dodge left and dodge right animations
that will be blended together
so the character can dodge in any direction. The animation tree script
will need to be able to set the blend amount of this
blend space as a vector two, the same way we set
the blend amount of the locked on
strafing animations. Back in the character script, when telling the
character to dodge, we can tell the animation
to set this blend amount. But the dodge direction is a vector three in global space, whereas the blend
amount for the dodge is a vector two in the
character rigs local space. We can calculate the
blend amount by comparing the dodge direction to the rig spaces vectors
using a dot product. I'll need to reverse
the x blend amount multiplying it by negative one. If we try this out,
the dodge animation plays three times and the character isn't actually moving. The animation repeats
because wants to dodge is true for
one full second, but the animation is
only 0.4 seconds long. So it will repeat as many times as it can
until the wants to dodge variable is set back to false before the 1 second is up. We will need to set
the variable to false once the dodge
has been performed successfully and also apply some velocity to
move the character. Adding another function, much like applying jump velocity, we will apply dodge velocity and call this function
from the animation. If this function is called, the character has
successfully dodged, and we can set the
variable to false to prevent more dodges from
occurring from a single input. Then we will set the
character's velocity to be the dodge direction
multiplied by Dodge force. This will create a sudden
impulse of velocity, moving the character rapidly in the direction
they are dodging. Creating another
exported variable for the character's dodge force. I'll set it to eight. May also want our dodge to
have eye frames, short for invincibility frames. This is a duration of time when the character can't
be harmed by attacks. Just like we have a
function being called by our attack animations to
turn the hip box on or off, we will also have a
function called by the dodge animations to turn
the Hurt box on and off. We should use set deferred to set the monitorable
property of the Hurt box to the
value of active in case this is happening
during a collision event. Since it's possible that we may want to call this
function as a result of a collision and we can't turn colliders on or off while
they are being processed. In the characters animations, we will need to add
function call tracks to each of our characters
dodge animations. I'll first turn off the
character's Hurt box. Then when the
character looks like they start to move
in the animation, I'll apply the dodge velocity. Once they've had some time for the Dodge velocity to move the character out of
the way of the attack, I'll turn the HRT box back on by changing the
argument to true. Then repeating the
same set of function calls for each of the
dodge animations. While it shouldn't happen with this code as it is currently, it might be possible
for the character to be interrupted while dodging, meaning the het box might
never be reactivated. Depending on how
the game functions, you might want to
include activating the character's box in
the interrupt actions. Now that the dodge
velocity is being applied, I don't really like how
the dodge animation actually moves the character
rigs root position, resulting in some
abrupt movements, both leading into and
coming out of the dodge. So opening up the animations, I'll remove the root key frames from the dodge animations. Keeping the character rig
rooted where the character should be as determined by
the character body's physics. And then I'll repeat this for the other dodge
animations as well. Running the game, we can
view the remote scene tree, find the character's rt box, and pay attention to the
monitorable property. Dodging sets the monitorable
property to false, then resets it back to true, and the character animates
the dodge more smoothly with the velocity thanks to the
removal of the root keyframes. With the dodge now animating
and functioning properly, let's now consider
if we want to allow the character to attack and
dodge at the same time. Also, should the
character be allowed to attack or dodge if
they are being hit? If we don't want to
allow this behavior, we will need to track
when the character is attacking, dodging
or being hit. Let's add some more
boolean variables to our character script
is attacking, is dodging and is hit. All of these will need
to be exported to be accessible to the
animation player node. Using property tracks, the
hit animations can set the is hit variable to true at the start
of the animation. And back to false at the end. Then in the animation tree, when transitioning from
locomotion to dodge, we can check to make sure
that the character wants to dodge and naught is hit, and naught is attacking. Before transitioning
into any attack states, we can check not only if the
character wants to attack, but also if they are not
hit and not dodging. We can then repeat the
process of setting the Boolean variables in
the dodge animations. Each one setting the
is dodging variable to true at the start
and false at the end. Again, with every
attack animation, setting the is attacking
variable to true at the start of the attack
animations and falls at the end. In case attacks or
dodges are interrupted, we should also set these
variables back to false. There is also the possibility of the character dying
interrupting the hit animation. So we'll set the is hit
variable to false here. In order for the is attacking variable to be set by the
action state machine, we will also need to
enable the filter for it and also the
hit box position. In order to test if the
dodge is actually working, we'll need the enemies
to start attacking. If you haven't set up the
enemy attack animations yet, an easy way to do it is to copy the characters animation player and paste it into
the enemy scene. We can then tell
the animation tree to use the new animation player. And as long as the enemy has the same bone structure
as the character, the animations will
all work the same. Otherwise, you will need to
set up the enemy to have at least one attack capable
of activating a hip box, masking the character's t box. To make the enemy's attack, I'll just attach a blank node
to them in the level scene. With a simple script,
telling them to attack. I'll make a new folder for enemy scripts and
call it attack. After grabbing a reference to the character as the
parent of this node, I'll tell the character to
attack in the ready function. Since the variable will
never be set back to false, this is all that's
required to have the enemies endlessly
perform attacks. Let's try running the game with visible
collision shapes on. And view the remote
scene tree where we can see the values of
all our new variables. The character will wait for
an attack to finish before performing a dodge
and vice versa. If we let the skeleton
hit the character, they take damage and are unable to attack or dodge until the
hit animation is finished. And we can dodge the
skeleton's attack with our new dodging action. We now have our character
dodging to evade attacks and restricting
when the character can perform certain actions. In the next lesson, we'll allow characters to block incoming
attacks with a shield. I'll see you in the next lesson.
9. Block: Hello, friends. In
the previous lesson, we helped our characters avoid taking damage by
dodging attacks. In this lesson, we'll allow them to block attacks
with a shield. Let's start in the
project settings, the input Map tab, and add an input
event for blocking. I'll use the right mouse button and the left shoulder
button on my controller. In the player script,
the block button doesn't need to be buffered, since it behaves more
like the run button, changing the
character's behavior while the button is held down. Sending a signal to the character
to either block or not, based on whether the button
is pressed or released. The character script can then store this value in a variable. Wants to block. But unlike
the other variables, there's no real need to
buffer the blocking input. Then add a function in the combat section that
sets this variable. In the characters animation
tree state machine navigating to the
action state machine. We can add the block
animation and transition to it if the character wants to block with a short cross fade. Then transition back to if
they don't want to block. Getting a view of the character and blending in
the action state, this animation causes the character to
raise their shield. Once this animation is complete, we can transition to
the blocking animation, which should be set
to loop to keep the shield up as long as the player holds down
the block button. This will also return to idle if the character
no longer wants to block with a short cross fade. We might also want to be
able to transition to the block animation from some
of our other idle states, such as the unarmed and
one handed idle states. To keep the number of
transitions lower, I'll first combine the idle
and one handed idle states by logically combining the
transition conditions to reach the one
handed attack state. If attack animation is one, they want to attack and are
not hit and not dodging. Then return to idle at the
end of the attack animation. Deleting the one
handed idle state, I'll then rearrange
the other states to be a little more organized. Now, either idle or unarmed
idol can transition to block, if the character wants to block, and transition directly
back to unarmed, if the character's attack
animation is zero, and they either have a
target or want to attack, and they don't want to block. With a priority of zero. Now, either idle or
unarmed idle can transition to block if the
character wants to block. The block and blocking states
can skip idle and return directly back to unarmed idle
with a priority of zero. This way, the character
doesn't have to go through two different idle states
between blocking and attacking. If we try it out, our character can transition to
the block animation when pressing the
block button and transition back to idle when the block
button is released. But it would make more sense
to restrict the character to only use this animation if they have a shield equipped. In order to do that, we will need another
resource script. Inheriting from equipment, giving it a class
name of shield. If you want your shields to have different variables, we
can define them here. Let's add a variable for the
amount of damage reduced by blocking with this shield
with a default value of one. Accessing the Barbarian round
shields custom resource. We can change it to be a shield and change the damage reduction
amount if we want to. Remember to repopulate
any properties which are reset as a result
of this class change, and update all shields to
use the new class script. In the shield scene, we'll need to be able to
store the variables from the item resource in the shield just like we did
with the weapon. Adding a new script, It'll just have a damage
reduction integer variable and a dummy activate
hip box function, which will ignore the Boolean
argument and do nothing. It will become clear why this
is necessary in a minute. We can then replace the script attached to our shield
with the new one. In the character script, let's add another variable to the equipment category
for whether or not the character has
a shield equipped. When donning a
piece of equipment, if the item is a shield, then we will set it to be
the off hand equipment, pass along the damage
reduction value from the item resource
to the shield, and set has shield
equipped to true. Since the off hand variable is now holding a shield
instead of a weapon, interrupting the
character's actions will deactivate all hip boxes. And since the off
hand is not null, we are telling it to
deactivate its hip box. But we have told the shield
to ignore this request. Then when doffing a
piece of equipment, if the socket is the offhand, then we can assume
that the character is no longer holding a shield. Then when telling the
character to block, we can add the condition that the character must also
have a shield equipped. The inventory script
will also need some more conditions when
equipping a shield as well to prevent the character
from equipping a shield when their off hand is already occupied with a two handed
or off hand weapon. This process will
vary depending on how your inventory and
equipment systems work. If the selected
item is a shield, and the character has something equipped in their main hand, and the character
is currently using either the two handed or
dual wheel animations, Then I'll unequip the
main hand socket and tell the character to dof the equipment in
their main hand. If we try it out.
The block button is now ignored if the character doesn't have a shield equip. We can equip a two
handed weapon, then equip the shield. The two handed weapon is
unequipped automatically, and then the shield is equipped. And now the character can
block with the shield. Next, let's consider how the blocking input works
with our other actions. If the player is blocking and decides to either
attack or dodge, then I would want
those actions to take priority interrupting the block. But if we just set the wants
to block variable to false, then the player will
have to release the block button and press it
again to continue blocking. Instead, we can declare
another Boolean variable. Let's call it interrupt block. If the character wants
to attack or dodge, then this variable
can be set true. And when the attack
or dodge is done, we can set it back
to false to allow the character to continue
blocking automatically. I'll simplify the add
dodge velocity function to just call cancel dodge. Then in the animation tree, we can add the conditions
to transition out of block or blocking to include if the
block is being interrupted. And not allow the
transitions back to block until the block is no
longer being interrupted. Now the character
will stop blocking while attacking
and automatically return to blocking as long as the block button is
still held down. And the same also
works for dodging. The last thing we need
to do is actually reduce the damage of incoming
attacks while blocking. For that, we'll need to know if the character is blocking. Since blocking isn't limited to a single animation and
the animation loops, there's no way we
could simply tell the animator to set a boolean
variable to true or false. Let's add the block
hit animation. Enabling the transition
to this animation from blocking and returning at
the end of the animation. What we contract to know
if the character is blocking is which state this state machine
is currently in. If it's either in the
blocking or block hit state, then the character's
shield is up. The block animation
is also quite long. It includes not only
raising the shield, but also a cycle of
blocking animation as well. I don't think that the character should
be considered to be blocking until after the
shield is fully raised, but I don't want to wait for the block animation
to finish either. So In the character's
animation player, I'll reduce the length of the
block animation to make it stop after the shield is raised
at one third of a second. In the animation trees script, let's add a public
function to check if the character is blocking,
returning a Boolean. First, storing the
name of the action states current node
in a string variable. We can return if it is either
blocking or block hit. And we'll also need
another function to play the block hit animation, telling the action state to travel to the block hit state. Then in the character script, at the top of the
take damage function. We can check if the
character's animation is currently blocking, and that the direction that the damage is coming from
is the character's front. Using a dot product of the damage direction with the character rigs
forward basis vector. If the value is a
positive number, then the damage is coming from somewhere in front of them. This would cover a
wide angle though, a full half sphere in
front of the character. Using something closer
to a quarter or a half, we can limit it to a cone shape. Under these
conditions, the attack should be successfully blocked, reducing the amount of damage by the damage reduction
of the shield. Using the max function to
cap it at a minimum of zero. We can then tell
the animation tree to play the block hit animation, and if the amount of damage has been completely reduced to zero, I'll just return since the character isn't
taking any damage. Let's try it out.
Equipping the shield and blocking the
skeleton's attack. The damage is reduced to zero and the block hit
animation plays. Taking damage from
the side or the back, the character still
takes damage. We now have our
character able to block attacks with a
shield from the front. In the next lesson,
we'll give them a ranged weapon that
can shoot projectiles. I'll see you in the next lesson.
10. Shoot: Hello, friends. In
the previous lesson, we allowed our character to
block attacks with a shield. In this lesson, we'll give them arranged weapon capable
of shooting projectiles. This asset pack does not contain
a bolt for the crossbow, so I'll start by making
one in the crossbow scene. Starting with a
rigid body three D node, renaming it bolt. I'll position it
where the base of the bolt will rest
inside the crossbow with its blue z basis vector pointing forward where
it will be shooting. Next, I'll add a mesh instance
three D node as a child, populating it with
the cylinder mesh. Reducing the radius and height, rotating it to point forward and positioning it to be
forward along the z axis. I'll just give it
a basic material and set the albedo
to be a brown color. Then add a basic three D
node as another child, so it will be placed with
the same position and rotation and rename
it amo socket. Then reparent amo socket to the crossbow retaining this
position and rotation. Reparenting the bolt
to the amo socket, so its position and rotation relative to its
parent is now zero. The bolt can now be made
into its own scene, which I'll put into a new
folder for projectiles, and then delete it from
the crossbow scene. The crossbow can now
be loaded simply by adding a bolt as a
child of this node. The bolt will need
a collision shape. I'll use a capsule with the same dimensions and
position as the cylinder. Expanding the solver section, I will need to have
contact monitor enabled and max contacts set to
something larger than zero. If we want the bolt to
collide with the terrain, it will need to mask
collision layer one. And I want my bolts to move slowly so I can
see what they're doing. But don't want them to
fall down due to gravity. So I'll set their
gravity scale to zero. Attaching a script to the bolt, we can name it projectile, so it can be used for other
types of projectiles in our game and save it in
the item scripts folder. As soon as a bolt
is instantiated, it will be loaded
into the crossbow. And we may want to
pass along information from the character or weapon to the ammunition at this time. Let's create a public
function to do that. But for now, we'll just set its freeze property
to true since the rigid body
doesn't need to do anything until after
the weapon is fired. To shoot the projectile, we will apply an impulse force sent by the weapon
as a vector three. At this moment, the
rigid body can start applying physics to move the
bolt through the game scene, setting its freeze
property to false. We no longer want the
projectile to follow the transform properties of
its parent, the crossbow. It should be re parented to the game scene where it
can move and act freely. In case it isn't
pointing directly toward the direction,
it's being fired, we can tell the bolt to look
at a point in space that is its own global position minus the direction
of the impulse force. Then apply the impulse force to the bolt to get it moving
in that direction. Connecting the body entered signal from the rigid
body to the script, this function will be called
if the bolt makes contact with anything on collision
layer one, the terrain. If that happens, the bolt can stay exactly where
it made contact, setting its freeze
property back to true, but deferred since this is
happening during a collision. It is very important that
anytime you implement a feature into a game that instantiates a large
number of nodes, that you also implement some
way of cleaning them up. Otherwise, you could end up
with very large amounts that cause frame rates to drop and
eventually crash your game. A common practice
with projectiles is just to give them
a maximum lifespan. Let's add a time node to our projectiles and set it
to count down one time. I'll set it to 10 seconds. Then grab a reference to the time or node
using add on ready. We will start the timer
when firing the projectile. If it doesn't end up
hitting anything, we still want to clean it up, so it doesn't just fly off
into the distance forever. Connecting the time up signal
to the projectile script. It will cue the projectile to be freed from the game scene. Back in the crossbow scene, we'll need a new script
for this weapon. We can name it ranged weapon and put it in the
item scripts folder. Replacing the script that is
attached to this weapon with ranged weapon and give it a reference to the custom
resource file for the crossbow. We can export the ammunition
as a packed scene, a prefab that we can use as a blueprint to instantiate
as many as we need. Then assign the bolt as the ammunition for the
crossbow in the inspector. We'll need a reference to
the ammo socket using at on ready and a reference to the ammo that is currently loaded into the ranged weapon. It might also be nice to
have a damage bonus that the weapon will add to the shots fired from
it as an integer. The amount of impulse
force the weapon applies to its ammo when it
is fired as afloat. I'll use a low number
like ten so we can see the bolts moving
through the air easily, and the collision mask of our
enemies so that information can be passed from the character th the weapon to the ammunition. When equipping the weapon, characters are
setting the hip box, collision mask of weapons, so we can easily reuse the same function to
set the mask variable. And just like we did
with the shield, we can add a function to
activate the hip box, ignoring this parameter,
and just return. Since the weapon has
no hip box of its own. The weapon will
need to be able to shoot the projectile
with a public function, and we'll let the
character specify the direction they
want the projectile to go as a parameter. All the weapon
needs to do is pass this function call
along to the amo, multiplying the direction
by the weapons force. Then set the loaded
amo reference to null since the weapon
is no longer loaded, and a reload function, which will instantiate
a new bolt, assigning it to the variable. Then add it as a child
of the amo socket, giving it the
correct position and rotation required to sit
nicely in the crossbow. We can then pass
along any information the ammunition will need to
know before it is fired, like the weapons damage
and the collision mask. So let's go ahead
and add these as parameters to the
onload function in the projectile script. Our character script
will need a variable to know if the weapon they are
holding is currently loaded. And two new functions,
reload and shoot. Reload, will just set
the variable to true, and then tell the main
hand weapon to reload. For now, shoot will just
set the variable to false, then tell the mainhand weapon
to shoot in a direction, passing the character rigs
forward basis vector. If the weapon is unequipped, since it is being completely
removed from the scene tree, the ammo will also be
removed along with it. So I'll just reset the weapon is loaded variable back to
false at this point. Our weapon type enumeration, we'll need two more entries for one handed range of attacks, which will be number four, and two handed range
attacks is number five. In the crossbows
custom resource, I'll change the script
from equipment to weapon. I'll change its weapon
type to one handed range, lower its damage, and
repopulate its icon. And likewise change the heavy crossbow
to two handed range. In the characters
animation tree, we will need more actions
for range attacks. This state machine is already getting quite large and messy, so I'll wrap the range attacks inside another state machine. Transitioning into
this state machine, if the characters attack animation is either
four or five, and they want to attack, and they are not hit
and not dodging. Then go back to idle at
the end of the animation. And I'll blend both transitions
with a bit of cross fade. Inside the range to
attack state machine, we can add animations for
shooting and reloading. Both for one handed and
two handed range attacks. We only need to play the
correct one once and then exit back to the
action state machine. So they will all
transition at end. Transitions to one handed will require that the attack
animation be set to four. And two handed set to f. Whether the shot
or reload animations play will be based on the
value of weapon is loaded. Now that the animations can be played by the animation tree, we need the animations to call functions in the
character script. During the shoot animation, we will add a method call track and scrub to the frame before the character recoils from
the projectile being fired. Then add a key frame to
call the shot function. Likewise, during the
reload animation, We will try to
select a frame when the character has finished
reloading the weapon. And add a key frame to the call method track to
actually reload the weapon. If we want these
animations to lock the character out of being
able to perform other actions, we'll need to set
a boolean variable like we did with the attacks. I'll just reuse the is
attacking variable. And repeat for the two
handed ranged animations as well. Let's try it out. We can equip the crossbow
to the character, and pressing the
attack button once, we'll have them load the
crossbow with a bolt. Pressing the attack
button a second time, they fire the crossbow forward, and the bolt shoots out until it hits something
in the environment, where it stops and destroys
itself after 10 seconds. Now we need the bolts to
deal damage to enemies. Back in the bolt scene, we will need to add a hit
box to the bolt. The hit box needs
a collision shape. I'll use the sphere. I'll position it at
the other end of the bolt and give it
a smaller radius. Just like our other hip boxes, it doesn't need to be monitoring or have a collision mask yet. Grabbing a reference to the
hip box using at on ready, let's also export a variable
for the bolts damage. I'll use a default value of one. When the bolt is loaded into the crossbow by the character, that would be a good opportunity to pass along information, such as any damage bonuses
and the collision mask. The weapons damage bonus can be added to
the bolts damage, and the hip box's
collision layer can be set to the character's
enemy collision layer. When the projectile is shot, we can tell the hip box to start monitoring for enemy herd boxes. Connecting the hip box area entered signal to the
projectile script. We can tell the Hurt box
that this projectile hit to tell its parent
character to take damage. From the direction of the
Hurt boxes global position minus the Bolt's global
position normalized. The bolt can then be to be
removed from the scene. And if the bolt hits terrain, it should no longer be
monitoring to deal damage, so we'll set it to
false deferred. In the character script,
I would like to make absolutely sure that when the character is
locked onto a target, that the projectile is fired
directly at that target. So the shoot function
will split into two different behaviors based on whether or not the character
is locked onto a target, calling a separate function
to shoot at a target, passing the target
as an argument. But the weapon
should only fire at the lockdown target if the
character is facing them. So we can check if
the dot product of the character rigs
forward basis vector and the normalized difference in their global positions
is a positive number. The closer to
positive one this is, the more similar they are, the more the character is
facing towards them. I'll say something larger than 0.75 is close enough to shoot
at the lockdown target. In the ranged weapon script, giving shoot at
target a definition, accepting a target
as a parameter. We can now calculate the
direction as the difference of gulple positions normalized
and multiplied by force. Since this will use the
weapons gubal position, not the character's
gubal position, the direction will
be more accurate. And I'll add 1 meter up to the target's position to
not aim at their feet. Let's try it out.
Equipping a ranged weapon and locking onto a target, I'll first just face
off to the side, and the projectile
still shoots forward, since the character
isn't facing the target. Moving to face the target
and firing a second bolt, this time it shoots directly at the target and deals damage. We now have projectiles being
fired from ranged weapons, hitting enemies or
the environment. In the next lesson, we'll allow enemies to detect
players and fight back. I'll see you in the next lesson.
11. Enemy: Hello, friends. In
the previous lesson, we added range attacks
that shoot projectiles. In this lesson, we'll
allow enemies to detect, chase, and attack the
player character. In the graveyard scene, we added a simple
node that tells the enemy to attack constantly. Let's remove that
and start building a more complex version to only attack when the player character
is in range to be hit. In the skeleton minions scene, we'll add a child node to
it of type node three D, since it will need to access three D positions and rotations. I'll name it aggression. Then add an area three D node, which will represent the
enemy's attack range. I'll give it a collision
shape and use a sphere. Adjusting the size of the
sphere to be a little bigger, I'll give it a
radius of 1 meter, then position it to be 1 meter up from the ground
and 1 meter forward. The attack range area
will be monitoring for collisions masking
the player's herd box. L et's attach a script to this node and put it in
the enemy scripts folder. Since the purpose of this
script is to control a character just like the
player input handler node, we'll need a reference
to the character being controlled by this node. With how I am structuring
my scene tree, this node will always be the direct child of
the character node, so I can access the character
by calling get parent. Connecting the area entered and area exited signals
to the script, I'll rename the parameters and give them types for clarity. When the player characters Hurt box enters
the attack range, we want to tell the
enemy to attack. And when the Hert box
exits the attack range, we can tell the
enemy not to attack. We can save this branch as its own scene and save it
in the enemy scenes folder. Then delete it from
this enemy scene. E Back in the graveyard scene, we can attach our
enemy aggression node to any or all
of our enemies to make them
automatically attack the player character if they
enter the attack range. Let's try it out. The enemies
are not attacking yet, but we'll attack if we enter their attack range and
stop when we leave it. Next, we'll allow
the enemies to see the player character
from a further distance. Opening up the
aggression node scene, we can add another area three D node to represent
an enemy's vision. Adding a collision
shape to this scene, I'll use another sphere
with a much larger radius. I'll use 5 meters and position it to be ahead of the enemy and a little
bit up off of the floor. This area can be monitoring for the player character's
collision layer. I'll use layer nine. Then add a ray cast three
D node to be their line of sight and also move this
up off of the floor. This will be looking for the same layer as
the field of vision, but also be obstructed
by terrain. Since I used layer nine, which is the character
body and not the t box, I'll connect the on body entered and exited signals to
the aggression script. When a body enters
the field of vision, I'll store it in a variable, Let's name it target of
type character body three D. But we'll also need to confirm that they
have a clear line of sight too before
becoming aggressive. So we'll need a
reference to the cast. And we can store whether
or not the enemy has seen the player in
a Boolean variable. Let's also grab a reference to the enemy's field of
division now too. A. Riding the process function, we can start by checking if the value of target
has been set. If it's null, then the enemy can't see
the player character. We should just
return. If the enemy has not yet seen the player, then we can check their line
of sight to the target. Setting the target position of the raycast to be the difference in their
global positions, adding a meter up
off of the ground. After forcing the raycast to
update if it is colliding with something and
that something it is colliding
with is the target, then the line of sight is
not obstructed by terrain. We can set the has seen
player variable to true. If the value of this
variable is true, then the enemy's
behavior will change, and we will have them chase
the player character. When the player character exits the enemy's field of vision, if the body is the target, and the enemy has not
yet seen the player, then we no longer need to
check their line of sight, so we can set target to null. This aggression node is a
child of the character node, which is not rotating. So the field of vision and attack range will not rotate
with the character rig. We can use the
character reference to grab the rig rotation, accessing the y property, and assigning it to
this nodes y rotation. So it will follow
the same rotation. We might also want a way to tell this enemy to stop pursuing
the player character. In which case, we can
set the target to null, has seen target to false and tell this character to
move with no direction. Good time to call this function would be when the enemy dies. So overriding the
ready function. We can connect this character's died signal to the
stop function. When the enemy first sees
the player character, we can also connect the
targets died signal to the stop function to
stop the enemy from trying to kill the
player character after they've already died. I'll also use this same function to turn off this enemy's vision, setting its monitoring
property to false. In order for the enemy to
move toward the player, we first need to make some
changes to our level scene. Let's add a navigation region
three D node to the level. Then re parent any nodes
that will be used to generate our navigation map
as children of this node, including the ground
and obstacles. With that done, select the
navigation region node and populate the navigation mesh with a new navigation mesh. Then click on the Bake
navigation mesh button. The navigation mesh will cover
the terrain with triangles that form an area where the characters will be able to walk. Expanding the navigation
mesh resource, we can see its properties. The only section
of concern right now is the agents section. Agents are our
characters who will be using this navigation mesh
to walk around the scene. Taking a quick look at our
characters collision capsules, I have given them a
height of 2.6 meters and a radius of 0.75 meters. I'll copy those values into the agents height and radius in the navigation
measure resource, then rebake the navigation mesh. The mesh now leaves more space
from walls and obstacles. In order to use the
navigation region, our characters will all need another node attached to them, a navigation agent three D node. Make sure you attach
one to every character. Grabbing a reference to this
node using add on ready. We can then write a function
in the character script, which can be used to tell
any character to navigate to any location in the
navigation mesh for any reason. This is also very useful for many things outside of combat. Accepting a target position
as a vector three, we will set the navigation
agent's target position to be the same location. The navigation agent will automatically use
the navigation mesh, assigned the same
navigation layer to produce a path to get as close to the target
location as it can. Calling the agents get
next path position, we'll return the first way point along this path as
a vector three. We can then subtract
the character's current global position to get a vector pointing
from where we are to the first
position along the path. If we multiply this by 101 to remove the
vertical component, then normalize this vector. It is now functionally equivalent to an input
vector produced by our player input handling
script when the player tilts the analog stick or
uses the WASD keys. So we can assign this to the
input direction variable. And the physics process
will handle the rest, telling the character to
walk along this path, which will navigate
them to their target. It would be a good idea to move everything that happens
to a character when they die into a public
function in case anything other than taking
damage can cause them to die. When a character dies, we should tell them
to stop moving. And when they are being told
to move or take any action, we should ignore those requests if the character is
dead by returning. Back in the aggression script, we only need to
tell the enemy to navigate toward their target
once they've seen it. Before trying it out,
let's choose one enemy. I'll use the skeleton warrior and select their navigation
Agent three D node. In the D Bug section, click the enable Toggle to be able to see the path
rendered in game. Approaching the first skeleton, as soon as we enter
their field of vision, the line of sight immediately confirms that they can see us, and the navigation agent finds a path for the enemy
to walk toward us. Then attack us.
Defeating this enemy, this time, let's attract
the attention of the rogue. Since they're behind
the gravestone, the line of sight
is not confirmed and the enemy remains in hiding. And if we approach the warrior, we can run around
the open grave, seeing how the
navigation agent will automatically update
the path in real time, to go around the shortest possible path to
reach the player. We might also want our enemies to be able to wield weapons. Back in the graveyard scene, let's attach another basic
node to one of our enemies, and name it equip. Attaching a script to this node, I'll put it in the
enemy's scripts folder. All we need to do is grab a
reference to the parent node, the character, and export a weapon to equip
to this character. Then in the ready function, tell the character
to do the weapon. I'll tell my skeleton warrior
to equip a great axe. Then copy and paste
this node onto another enemy and change the
weapon to something else. I'll give the rogue some knives. Each of our enemies
scenes will need to have bone attachment
nodes added to them. Following their left hand
slot and right hand slot. We can then populate the characters equipment
socket array with these nodes to be used
when equipping weapons. Lastly, it would be
nice to not have to reload the game when we die. So let's connect the
player characters died signal to the
game manager script. After fading to black, I'll just tell the scene tree to reload the current scene, resetting everything back to how it was when the scene
was first loaded. Let's try it out.
The skeleton warrior is equipped with
the great a and is using the two handed
attack animations. When we die, the entire
scene gets reloaded. And going up against
the rogue this time, they are equipped
with the knives and use dual wheeled animations. We now have a complex combat
system in our game with a variety of
mechanics that can be adjusted to fit the
needs of your game.