Godot 4.5 Tutorial: Nextbot AI & Enemy Pathfinding (Backrooms Part 5)
865 segments
We built the maze. We built the
atmosphere. But a horror game is not a
horror game unless something is hunting
you. Welcome to the finale. You made it.
Today, we turn this walking simulator
into a survival horror. We're going to
build a nextbot style AI, which is an
enemy that knows exactly where you are
and just never stops coming. A nextbot
is popular term from the Garry's Mod
community that refers a specific type of
AI enemy. It originated as a system for
more complex NPC navigation but became a
massive meme mind men mind men mind men
mind men mind men mind men mind men mind
men mind men mind mena horror trope
first the enemy needs to know where it
can walk in GDAU we use a navigation
region to bake a map for the AI now back
in our project here in our world scene
we're going to level here which is the
parent of all our grid maps we're going
to right click on it and we're going to
change its type and we're going to
change its type to a navigation
region
3D.
Go ahead and change.
Now, it's important to note when we did
change that, go back to the change type.
You'll notice it is experimental. So,
this class may be changed or removed in
future versions. So, if you're watching
this in the future, this may be
different, but all we're really doing is
trying to get a nav mesh onto these grid
maps. So, how are they're doing that in
the future? That's what we're going to
do.
and change or cancel that. We already
changed it, so you can hover over it and
see what type it is. And then with our
level selected, our navigation mesh,
we're going to click on that, create a
new navigation mesh and go into the
resource there. And then we're going to
go into cells. And to get the cell size
or cell height, we want the cell height
to be as close to the floor as possible.
Since you're using a grid map, we don't
really have any width on our floor. So,
we want it really close to the floor,
which is going to be 0.05.
So, hug the floor. So, our enemy's feet
will look like they're touching the
ground. And then when we have that,
we're going to go up here in the
toolbar, click bake, navigation mesh.
Then, we see our floor as the navigation
mesh. You also see that our door blocks
the mesh itself as well. So, we click on
our door and we're going to temporarily
hide it for now. If you want your
enemies to be go through the doors,
which we do,
once our door is hidden, before we click
bake again, we're going to change the
size of our enemy so they can fit
through this door. And our enemies are
going to be the agents. So, with our
level map selected, agents, we're going
to change our radius of our enemies to
0.25.
That way, they can fit through the doors
after we click bake. So, click bake
again.
And now we have a line of nav mesh to
the door. Now, our enemies can follow us
through the doorways. Then we can bring
our door back right there. and the nav
mesh will stay just fine. Then save that
scene. Now, if that didn't work for you,
maybe try reducing your radius of your
agent a little further and see if that
helps. We also want to check how close
it is to our floor, which is good. It's
hovering a little bit, so you could
bring this down a little further, but
you don't want to get too close. You
might get some layering issues. That's
good for now.
Now, I'm sure you've noticed these
warnings here. Now, this is just saying
that
these settings in our project settings
do not match our settings that we've
customized over here. Now, to change
these throughout the project, instead of
just dealing with the warnings, we can
go into our project project settings and
our filter settings, we can search for
something called default
cell height.
Just like that. and it should be in the
navigation section 3D. Now, see our
default cell height is 0.25, but we
changed ours to 0.05.
So, we changed that to 0.05.
So, we're using grid maps and that's
where we want it to stay by default. So,
we can enter that and close.
Then we can clear these.
Let me try baking again. See if it fixes
it. It doesn't which means we need to
project reload current project. So that
setting takes effect. So once it loads
again after we bake after we click level
then bake navigation mesh you'll see
that error goes away.
The warning disappears because the map
now
agrees with with the mesh basically. So
that's all it was just a little
conflict. Now we need the monster. Now
we're going to keep it super simple.
Just a floating capsule of Doom. Now
we're say new scene.
New scene. It's going to be a other node
scene. Be character body 3D
right here. Create. And then we're going
to rename this to enemy.
Great. Now, we want to add a mesh
instance 3D
so we can see our enemy.
And one we're going to use in the mesh
is the capsule mesh there. Now, we'll
set the end into the resource here.
We'll set the radius to 0.25,
which is the same as our agent radius we
set in our nav mesh. So, now our nav
mesh and our enemy agree on the size it
is. Now, for the material, we can use
uh we could just use that tape material
we did last time.
Or we could give it a weird look, make
it look like the wallpaper.
Yeah,
a stretched out wallpaper. We can give
it kind of a weird glow. You really do
whatever you want with this kind of
stuff. Whatever feels creepy to you. Or
emission. Can make it like a red
emission.
Maybe a little darker though.
That gives a little pink look.
There you go. So, it kind of looks like
the wallpaper is chasing you. Spooky.
Great. Just something silly like that.
Whatever you want to do. And let's see
here. In the transform, we need to bring
it up by one. There we go. That way, our
enem is sitting on the floor. Then, as
you see from our node configuration
warning, we still need a collision
shape. Right click on the enemy,
collision shape 3D.
And we're also going to have it be the
shape of a capsule. Just like that. And
into our resource here, we change the
radius to match our capsule. We also
need to change the transform to match
the capsule itself. So, it hugs the
capsule as it is.
So, we need the brain of our enemy. So,
right click on the enemy, add child
bone. This going to be the navigation
agent redee.
This going to tell the enemy where it's
allowed to walk.
And that's all we need for that. So, you
can control S to save. We're going to
save our enemy in our scenes folder.
Save. So, the logic is going to be
simple. We're just going to find the
player when in range, get the next step
on the path, and move there. We're also
going to add a spooky glitch effect. So
when the enem is chasing us, it's kind
of glitching back to where it was at
some point on its path. So on our enemy
here, we're going to right click on our
enemy node. We're going to add a child
node. Audio stream player
3D right here. Now, this one's going to
make a sound when we're close to the
enemy. Not like the hum we did with the
lights. This one will do it everywhere,
but this one will do it when we're close
to the enemy with the 3D one. So, we
want that one. The sound I'm going to
use is going to be included in the back
rooms tutorial assets that I've linked
below in the description just like all
the other ones. But if you're
downloading them separately because
that's how you want to do things, that's
fine. I'll also include that link. And
it's from free sound. You'll see it's a
creative common. And you want to make
sure anything you use, especially sound
related or anything really, you want to
have a CCO license. That way you have
the right to use it unless you want to
pay for the rights. So we're just going
to use this static sound every time it
jumps. Just like that. So add a
atmospheric spooky effect every time it
glitches. So however you decided to
download that, I'm going to go back up
into my audio file here. Now I'm going
to drag in that sound and drop it in
there and import it. So now I have my
static burst sound. So, in my audio
stream player 3D, I am going to drag and
drop right into the stream here. Now, we
don't want to be able to hear this from
anywhere. If we keep the max distance at
zero, we'll be able to hear the enemy
moving from across the across the whole
level. We don't want to do that. So,
we'll change this max distance. We're
just going to use 10 meters. So, the
enemy gets real close. That sound's
going to get louder and louder. And if
the enemy decides to move once or twice
at the same time and the sound is still
playing, we don't want the sound to cut
off right away. So, we're gonna up this
max polifany, which as it says just lets
it nodes play at the same time playing
additional sounds. So, we're just going
to increase that to four. Be super safe.
And that's all we got to do for that.
Let's go and save that scene. And our
enemy here, we're going to add a script.
attach script. We don't want to save it
in our scenes folder though. We're going
to save it in our scripts folder enemy
GD or keep the default and create.
All right, I'll make this bigger for
you. I'm going to get rid of my output
and we're not going to keep any of this
stuff. So, let's get rid of the
boiler plate code. Now, we are going to
use a speed. So, our constant speed for
enemy, we're going to use it as four.
Good enough for now. And then we want a
distance to where the enemy can get us.
This is where you can add like a jump
scare, that kind of thing. We'll call it
jump scare distance. And we'll just make
it 1.5. So, when the enemy is 1.5 meters
away from us, it's going to game over
us. Then, we want a distance. And when
our enemy starts chasing us, we call it
the aggro distance.
So that could be anything you want.
We're going to set it to 20. So as 20
meters away, the enemy is going to start
chasing us. And then for the glitching,
this is going to be the glitch settings.
Now I'm going to use the
interval between the two. So export
bar minimum glitch interval.
Give it a kind of teleporting around
creepy look. Our float is going to equal
to 0.5 and that what is that just
minimum
I could type right minimum
seconds between the glitches
and then we need a match a match a max
max glitch interval
so that way it's not consistent it's
random and unpredictable and that's
going to be 1.5. So between 0.5 and 1.5,
that's how we're going to handle our
glitching back and forth,
maximum seconds between glitches.
There we go. There's that. So we need
some uh internal variables here. So we
need a timer to say when we can glitch
the glitch timer. We'll make that a
float and zero seconds to start. Then we
need a next glitch interval
interval there
float one. And that's the interval
between glitches that we're going to use
to say when you can glitch. And then we
need a safe position. We don't want our
enemy glitching into the wall or outside
it even because then it won't be able to
get back in. So, we need a safe
position, which is going to be a point
on the path that the enemy has already
walked. So, we know that's a safe
position to be in. Great. To get all
this stuff, we need to get access to our
enemy's hierarchy. So, I'm going to make
that smaller. What I'm going to bring in
here, I need the navigation agent 3D.
I'm going to drag controll drop that in
there so I know my path is correct. And
this is large. So I'm going to rename it
to nav agent.
There we go. What we also want is our
audio stream player 3D. So we can play
that sound when we glitch around. Drag
control and drop. And we have our audio
stream player 3D.
And make this a little smaller too. Just
call it audio player.
And save that. We also need to access
the mesh of our enemy so we can change
its transform and position when we do
glitch. So the mesh instance 3D control
drop control. There we go. All right.
Now we have all our variables. We're
going to make a ready function. So
function ready. And then we're going to
go into that. The first thing we need to
do is save the spawn point as the first
safe spot. So, if we glitch right away
and we haven't gone too far, we need to
be able to uh teleport back to the first
spot we were to our safe position.
Initially, it's going to be the global
position of where our enemy is. That's
it. And we need to set the first random
timer,
which is going to be the next glitch
interval
right there. And we're going to set that
to a random range, a random float range.
So rand f range, which is going to be
the minimum glitch interval
and
between the max glitch interval. So we
have our min and our max. We don't want
it to go above or below that. I'll make
this bigger so you can see that. So
basically this next glitch interval,
random float range between our two min
and maxes. Perfect. And that's all we
need in our ready function. Now we're
going to use our physics process. So
this could be our movement and tracking
our players and those kind of things. So
physics process.
All right. Now in this physics process,
we want to ensure that the mesh is
visible because we're going to turn it
off and on again of the enemy. First,
ensure mesh is visible
if it wasn't toggled back.
toggled back. So if not visual
mesh,
not visual mesh, mesh instance 3D
because that's our enemy. That's going
to be vvisible.
There we go. Just want to make sure that
that boolean returns false because if it
is false, we need to turn it back on to
our mesh instance 3D.
And we want to take visible
and we want to make it to true. There we
go. And that'll make our mesh back on if
it's off. And now we need to track the
distance to the player.
Now you've reached a checkpoint. Now we
need the enemy script to look for the
player. So we're going to add the player
to a group. But we've never added our
player to a group yet. Now, if you've
never heard of groups in GDAU, groups in
GDAU work like tags in other editors.
So, you can add a node to as many groups
as you want and then you can use in
scene tree, get the list of nodes in a
group, call methods on that group, set a
notification to all nodes in a group.
Now, how do you add nodes to groups? On
the node in the inspector node section,
you're going to have our group section
here. Now, this is where we add here.
And now we can add it like our player to
a player group. So, back in our project
here, so we can get our enemy to see our
player. Well, we can go to our world or
we'll go to our player because that'll
affect our player in our world as well.
So, our player with our player selected,
we're going to go to this node section
just like we saw. Then, we're going to
go groups. And then when we add this
plus button here, we're going to give
our player the player group. The name is
going to be the player. You can get add
descriptions and stuff, but we're not
going to do that with this. It's pretty
self-explanatory. We're also going to
make it global. So way this can be
accessed from anywhere.
Okay. And now we have the player and the
player group. And you can tell it's in a
group. It's a little square box with a
circle. The node is in this group.
player.
Now, back in our enemy script here,
we're go I'll make this a little bigger
for you. I'm going go ahead and save
this.
So, our player is saved in the group or
the asterisk there. So, once that
asterisk is gone in our enemy script
here, we're going to go back up to our
onreddies and we're going to get access
to that player group. So, we're going to
add another ready here
bar. We're going to call it player. Now
we use something called the get tree
here. Now the tree get tree gets the
scene that we're currently in. So our
enemy is going to be in the world scene.
So it's going to get that scene and then
from this scene it's going to make this
smaller for you. It has access to all
these hierarchies now. So we need to
access the player node in the world
scene. And we get the world scene by
saying get tree open up. Now I want to
get the first instance, the first node,
first node in group right here. I get
that group. The group I want to get well
is the one we just made called player.
And now we have our player in our
variable here. So, back down in our
physics process here, what we can do is
right when we enter this, well, first of
all, we want to check if it's not the
player, well, we don't want to do
anything. So, let's return. So, if we
don't have a player in our onready, we
don't want our enemy to do anything or
else the code be right down here is
going to break. So, after this,
make sure we're actually visible as
well. We want to check the distance to
the player. So distance to player
is going to be the global position.
And then we're use a function called
distance to
a built-in function. So we track how far
away our player is. But we need our
player's global position because the
player is going to be moving around
constantly. So we need to update this
physics process and the distance to
player every frame. All right. Now that
we have the player position and the
distance to it, we need to go down and
we need to check if the distance to
player is close enough to attack. So
it's less than our aggro distance.
Right? So then we go into that if
statement and then we want to have the
what's going to be the chase logic.
Okay. So, anything you want to have
happen when the chase begins, this is
where you would put that.
So, in this we want to first thing we
want to do is move towards the player.
Move towards player.
And we do that by getting the navigation
agent
which we have on our ready. And then we
want to set the target position
in that
or on that agent.
And the target position well is going to
be the player. So we have the player's
global position.
There we go.
And then we want to save our next
location.
Have our nav agent.
Nav agent
get next path position.
Now we have a nav agent and our nav
agent has a path control and that's
going to handle all of our pathing for
us. So they have path positions. If you
hold control and click on this, you can
dive deeper into this. But returns the
next position of where this enemy is
going to be allowed to move to. So all
that blue baking nav mesh we got before,
that's what this is calling. So it can
actually know it can get there.
But back at our enemy GD script, after
we have that next location, we want to
save our current location for our
glitching effect. Go back and forth. Our
current location, well, it's going to be
our global position at this time. And
then we need our how far or how fast we
want to go. So, our velocity, a new
velocity, we're going to use that. And
then it's going to track where we are
and where we want to be. So we need to
minus that. So our next location
and then our current location,
not comma, minus our current location.
So notice how far we need to be and how
fast we need to get there. We want to
normalize it. Normalized. What is
normalized? If we controlclick into
that,
normalizing is a consistent function. So
it it returns movement so it's always
consistent. Like if you don't moving
diagonal, those kind of things, you
don't want those different than if
you're moving horizontally or
vertically. You want everything to be
and look the same. Now, it's pretty
crucial for converting raw vectors into
movement or also look stitchy and faster
than you want it to or just not as good
as you need it to. Great. Now that our
movement is consistent
back at our enemy GDScript
after we normalize our movement so it's
all the same no matter which direction
we're going. We want to know how fast we
want to get there. So we're going to
multiply that by well the speed that we
want our enemy to go.
Go and save that. And last but
definitely not least, well we need to
say well move. So, our velocity that our
current enemy is going needs to equal
our new velocity.
Looks like my tabs are messed up.
Backspace to the wall. Tab forward
twice. These tab indentations are
finicky. You want to make sure they're
perfect.
And now after we know how fast and where
we're going, well, we need to have our
glitch timer
so we can have our glitching go back and
forth at random intervals. So our glitch
timer is going to equal the delta. So
it's going to count by proper seconds
and not dependent upon your frame rate.
So if the glitch timer
is greater than or equal to the next
glitch interval, so that random interval
that we set up earlier, well, we're
going to write a function called perform
glitch. We'll write this function in a
second. Enter down.
And after we've performed our glitch, we
want to reset the timer.
So our glitch timer back to zero.
And then we want to set a new random
time.
New random time.
And we're going to do that by saying the
next glitch interval
interval. There it is. And we want to
set it to another random F range. Random
float range. And we want to set that
between our just like we did before
minimum and maximum glitch intervals.
Great.
Then save that and enter down backspace
couple times. We're going to get into
our else
now in our if statement. And our else,
well, if we're not chasing, well, we
want to idle. So, I'm going to make sure
the velocity
is zero.
Vector 3
zero. Now, this will make our enemy stop
moving at all. So, save that. And after
that if statement, regardless of what's
happening, you want to use the GDAU's
move and slide.
You don't use move and slide. None of
this will happen at all. And the last
thing we want to check after our move
and slide in this physics process is if
we are close enough to the player, well,
we want to jump scare them or kill them,
however you want to handle that. We're
do something called the jump scare. So
you can add a jump scare later on, but
what ours is going to do is just reset
the player back to the beginning. So the
distance to player is less than the jump
scare distance that we set above. Well,
we're going to call another function
that we're going to write in a second
called jump scare. There we go. We're
going to save that. Now we have a couple
functions we need to write. The first
one we're going to write is the
perform glitch. So function
perform. Now I type perform glitch.
There we go. All right. Now, the first
thing we want to handle in the perform
glitch is the visual. So, the visual
flicker.
And then we need to get our mesh
instance 3D. And we're going to make
that false. This is where we're going to
hide that mesh. That's why we're going
to make it unhidden in the physics one
just in case it didn't get unhidden. Now
after we hide the mesh, well, we're
going to teleport. So the rubber band.
So we're going to rubber band teleport.
So we know it's a spot it was safe
because we saved it because it's a place
the enemy has already been. So first
we're going to do is save the current
spot,
current valid spot.
as the next
safe spot.
There we go. Says current spot. Well, we
just you get a variable. Our current
spot is pretty simple to get our global
position.
That's where we are. So, now that we
have another safe spot, well, now we can
teleport
back to the old
safe spot.
And we just need to change our global
position we're at now. And we're gonna
change it back to the safe position that
we set.
No, no, it's a dis dist player. Not we
don't want to disc the player. We want
to be nice to him. We just want the
distance to player. Okay, perfect. Our
current spot global position and
teleporting to the safe position. And
then we update the safe position. Update
the safe spot for next time.
All right. Now we got safe position is
now our current spot.
And now that we have all our
positioning, well, we're going to set
the audio now. So it's going to be the
audio static. How we do that? Well,
we're going to change this the pitch.
It's a pretty common thing for in game
development to have the same sound.
Instead of having multiple sounds, we
just change the pitch of the sound so it
sounds different every time, at least a
little bit. So, we use a random pitch
and a new variable. And we're going to
get the random float range and we're
going to set the pitch between 0.8,
so lower, and 1.2 higher.
So, not too much difference, but enough
to be noticeable
to the ears. Then we get the audio
player that we set and we're going to
change the pitch scale is what we're
looking for. So, if we go back to our
let's make this smaller and our enemy
here. If we go to our audio stream
player here, change the inspector back
on. What we're accessing here in the
inspector is the pitch scale. Now, if
you hover over it, it'll say the pitch
and tempo of the audio as a multiplier
of the audio's sample rate. So, the
lower
slower it's going to play. The more deep
it's going to sound. The higher faster,
the higher it's going to sound. So, we
don't want it too far up and down. You
want to sound like a chipmunk. You don't
want to sound like
just slowmo demon or something. So,
unless you want that. So, up to you, but
we're going to keep it pretty subtle,
but enough to not be annoying. So, make
this bigger again. Now, we have our
pitch scale. We're going to change that
to the random pitch.
Random pitch. Perfect.
We also need Well, we need to play it.
We don't play it. Well, it won't
actually play. So, we need to play.
There it is. That's all that is. And
save that. The last function we need to
write after this one. Enter down a
couple times. function jump scare.
That's going to handle all the resetting
of our player. You could also do a game
over screen here, but we're not going to
do that in this tutorial. We're just
going to do print
you died. There you go. And then we need
to set the physics process.
That we're going to set that to false.
What does that do? Now, this is simply
saying stop physics processing. So, we
want our enemy to stop. So, if true, it
enables, it's always true by default,
but we can set it to false and stop our
enemy from running into us after we're
already gone. All right. Now, back in
our enemy GD script, what we can do
after that, we need to set if the player
if player
to make sure we have the player, we also
want to stop our player. So the player
set physics process,
we're also set that to false. So now our
player can't move and our enemy stops as
well. We also want to uh change the
mouse. So if we are dead, we want to be
able to reset or if we have a menu pop
up, we want our mouse to come back. We
don't want our mouse to stay hidden.
So, our mouse mode like we did earlier
input or a couple tutorials ago, mouse
mode
visible
right there. So, it just pops up our
mouse again. And then after that, well,
all we're going to do here, this is
where you do the game over screen, but
we're going to wait for a couple seconds
after we get hit by the enemy. We're
going to get the whole scene tree that
we're in, which would be the world here.
So after we have the whole world scene,
oops, we're going to reset the whole
level. Let me just do that not right
right away. Create a little timer. So
after 1 second. So after 1 second, what
we're going to do, we're going to call
the timeout function on the timer that
we just created. And it's going to say,
hey, I'm done. So now get the tree. And
then we're going to reload the current
scene. This going to reload back to
where our starting point is. Easy way to
set, you know, checkpoints, too, if you
want to set checkpoints.
Perfect. Now, let's save that scene.
We're going to make this smaller.
Now, the big moment. Let's go back to
our world scene, back to our 3D tab, and
we're going to grab our enemy from our
scenes folder here.
right there and then drag and drop into
our world scene here. Now, you can set
the enemy wherever you like. I'm going
to go ahead and set the enemy over here,
right past this tape section here,
right there. So, when the player
theoretically gets right here into these
two quarters or around this enemy is
going to see. Now, our pathing isn't
going to be perfect. It's just going to
move towards the player. There's more
advanced pathing materials you can do to
have them further away from walls and
those kind of things, but this should
work for now. Let's go and save that.
Now, let's run our scene.
Oh. Oh, that's silly. Let's stop that
for now. My spelling has destroyed me
again.
Let's check. Let's change our visible
spellch checking is better. Spelled it
right in the comment but not where it
actually matters. So actually here I'm
going to hold control F. I'm going to
search for visible.
I'll also search for visibil how I
spelled it last time. It's not visible.
It's visible. Perfect. All right. Let's
exit that. Let's go back to the Make
sure that's saved. back to our world
scene once again. Let's give this a try.
It's always something. All right, we are
in our room here. Me go and turn off my
mouse pointer.
That's distracting.
There we go. Now we are in here. Let's
go into our door.
Let's make our way to our tape.
See if our enemy starts chasing us. Oh,
I can hear it.
Oh. Oh. Oh. Oh.
That's pretty spooky.
Oh jeez.
Ah.
And it got me. Now we reset.
Look at that.
Now I like how it goes through the walls
like that. That makes it super
unpredictable. Obviously, we are
probably going to be
really hard to get away from this thing.
So, this is where the polish would come
in where you'd edit out the enemies and
then
make it so it's more survivable. Maybe a
way to get away from the enemy. The goal
right now is obviously to go pick up the
tape, but I keep getting caught. But
there you go.
In five videos, we went from empty void
to kind of terrifying. That was a scary
enemy in the end there. Atmospheric
horror game. Now, we covered FPS
movement, modular level design,
post-processing, shaders, raycast
interactions, and AI pathfinding. Now,
this project is finished. It's not
perfect. The monster is a pill. The maze
is simple, but you made it, and that's
what really matters. Now, this is where
you go off and make larger levels,
scarier enemies, mid or better AI,
and an inventory system to make picking
up the tapes matter. Now, if you found
this helpful at all or fun, leave a
comment on what your GDO game dev goals
are. Please like, subscribe, and hit
that notification bell. It really helps
out the channel. And thank you to all
our current and past Patreon and coffee
members. Your generous support keeps the
channel moving. Now, if you want early
access to tutorials, source code, or
suggesting future tutorials, please
consider becoming a member yourself. The
links are in the description. I'm
Spaghetti Syntax, and remember, always
stay curious, and I'll see you at the
next checkpoint.
Ask follow-up questions or revisit key timestamps.
This video, the finale of a GDAU horror game tutorial series, demonstrates how to transform a walking simulator into a survival horror by implementing a Nextbot-style AI enemy. The process involves setting up a navigation mesh for AI pathfinding, creating the enemy character with a mesh and collision shape, and scripting its behavior to chase the player, perform a "glitching" teleportation effect with synchronized static audio, and trigger a jump scare or game over mechanic. The video concludes with a live demonstration of the enemy in action and a recap of the series' covered features, encouraging further development and polish.
Videos recently processed by our community