Godot 4.5 Beginner Tutorial: Enemy Spawners & Horde Logic (Part 2)
567 segments
A hero is nothing without a villain, or
in our case, a few thousand of them.
Today, we build the Horde. Welcome back.
In part one, we built our survivor and
an infinite world. But right now, it's a
lonely walk. In this video, we're going
to create our first enemy, give it a
brain so it chases us, and then build a
spawning system to surround us with
monsters. Let's get building. Now, just
like our player, our enemy needs to be a
physical object in the world. We're
going to use a character body 2D again.
Why not an area 2D? Well, because
eventually we want them to bump into
each other so they don't stack into a
single pixel death ball. So, I'm going
to close the game here. And then we're
going to do a scene, new scene, and then
we're going to go to other node and then
get character body 2D again.
Great. and rename this to enemy.
There we go. Now, we're going to add a
sprite 2D. Right click, add child node,
sprite 2D.
Now, for my sprite, once again, I'm
going use some Kenny assets here. And
let me actually use the creature mixer
again that we used for our base
character. Now, one cool thing about
here is you can actually just randomize
and get creatures all day long. So,
whatever suits you, whatever creature
you think suits your game,
go ahead and uh stop there. And then you
do the same thing you do with the
player. Save the sprite sheet, download
it where you want, and then import it
into GDAU. So, once you have the sprite
you want, click your sprites folder.
Then, we're going to take your sprite
you downloaded, click and drag and drop
import into your game. Same thing here
with our sprite 2D selected. We're going
to click and drag and drop into our
texture area. Now we have the sprite you
selected in case mine little uh spider
looking dude. So we're going to do the
same thing we did with our player in our
animation section. Here we have one
frame high which is fine. For the
vertical we need four frames wide.
Change that to four. Then we have our
single first frame as our pixel guy. And
then we want to change the transform or
no the offset on the sprite 2D offset.
Our Y was -12. Should still be the same.
Yep. So our feet touch the Yaxis zero.
And we also want to get this guy moving.
So we can rightclick our enemy, add
child node, animation player there.
And with our animation player selected,
add a new animation. These guys do not
need an idle. So, we're just going to do
the run.
And we'll use the same specs here. We'll
change it to FPS. Just change it to 8
FPS. And it's a four-frame animation.
We'll zoom in here. And then we're going
to go to our sprite 2D after we select
our animation player. And this window
should still be up. And our frame zero,
our key action. We're going to add one
create. Then the rest. Two, three, and
four.
There. Oh, did that work? Yeah. Zero,
one, two, and three. Yep, that worked
just good. Then we can play to test it.
And now, oh, we need to loop it as well.
Don't forget that. Play. Oh, and now
he's walking. Perfect. We also want to
collide with the player. So enemy, right
click, add child node, collision 2D,
collision shape 2D, create. And just
like we did the player, we'll just do a
circle in the shape section. Circle.
Make them a little smaller. Drag it up a
bit. And that's our collision shape for
our player. Not a player, our enemy. All
right. Now, we need to get into some
layering. So we're Let's pause this.
Now, instead of do it individually on
the enemy, let's go into our project.
project settings and then general here
and get rid of our search we did before
and we're going to go down to layer
layer names right here. Now, we want to
go in the 2D physics section of the
layer names. Now, layers we want to deal
with is layer one. We're going to call
layer one player.
And then we're going to call layer two
enemy.
Perfect. And layer three finally is
going to be loot. So, experience, gems,
or whatever else you want to drop or
have the enemies drop when they die.
Let's go and close that. And now with
our enemy selected in our collision
section here, when we highlight or hover
over, we can see it's player, enemy, and
loot. And player, enemy, and loot. Now,
for our enemy here, we want to deselect
the player and select the layer as
enemy. And in our mask, well, this is
what they collide with or what it's
detecting. So, we want to keep layer one
checked so it seats the player. and
walls. By default, walls are going to be
one as well. We also want to select the
enemy so they don't stack up. Great.
Now, we can save this scene. Crl S or
saying save in our scenes folder as
enemy.
Now, we're going to give our enemy a
brain. The logic is simple. Find the
player. Look at the player. Move toward
the player. We're going to keep it
pretty efficient. The first thing we're
going to do is on the enemy selected,
right click, and then we're going to
attach a script. and not in the scenes
folder. We're going to go under our path
here and go up to our root and go into
our scripts folder. That's where we want
to keep all our scripts. Enemy GD is
fine. Then boiler plate is fine for now.
Create. Now I will get rid of all this
extra here and maximize so you can see
better. Now we have all our boiler plate
again. Now we're not going to use jump
or any of this UI stuff. So we're going
to highlight and delete all the guts of
this physics process. We're still going
to use the function, just not what's
inside of it. And then we'll get rid of
this delta caution. Perfect. And we're
going to get rid of these constants
here. Perfect.
Now we have an export variable we're
going to use just like we did for the
player. And we can export it so we can
edit it as per our preference. The bar
speed is going to be start with 100. And
then we also need to export a player
reference so it knows where the player
is after it can get a reference to it.
So player reference v player reference
and it's going to be a character body
2D. Now we have a reference to the
player. We go into our physics process
here. Make sure we tab indent over
inside. And then we need to check if we
have a player reference. So we don't
have a null reference error. We don't
have it assigned. So if player
reference. So now inside that now that
we know we have a player reference we
want to calculate the direction to the
player and we do that pretty much the
same we do the other one var direction
equals and here's where it gets
different we're going to get the global
position dot direction two here if you
control-click on direction two you can
look at the documentation just returns a
normalized vector pointing from this
vector to another one. So, back in our
enemy GD script now, direction two is
going to need a couple things. It's
going to need at least
where to go. So, wherever this script is
attached to, where does it need to go?
Well, we need to go to the player player
reference global position. There we go.
So, the script this is attached to is
global position direction to the player
reference global position. And what to
do after we have that direction? Well,
we're going to move towards the player.
Then move towards player.
And that's going to be just with the
velocity again, just like we did with
the player. Velocity. Do our direction
times the speed.
Perfect.
And the last thing we want to do is well
move the player. And we don't move at
all in if we don't have our player
reference. So, we're actually going to
put this move and slide inside of our
player reference function.
So, then we can move the enemy. Did I
say player? No. Move the enemy. Perfect.
Now, you've reached a checkpoint. Now,
if you drag this enemy into your main
scene right now and ran the game, well,
it wouldn't move. Now, in fact, if we
didn't actually use the if statement on
the player reference, the game would
crash. Well, why? Because the enemy
doesn't know who the player is yet. We
haven't told it. If you want to try it,
let's try it. Let's go ahead and take
out this if reference player reference
check here. Now, all this will run now
even if we don't have a player
reference. So, if you go down to we go
back to our main game scene, I minimize
this here. Go to our 2D section. I'm
just going to drag our play our enemy
into our scene here. If I drag and drop
on the main game, there we go. Now, we
have an enemy
on our scene.
Now the enemy is stacked on top of where
our player is. So use our move tool
here. We're going to move this way. Now
we have our player. We have our main
enemy. Now we have a reference here we
could assign manually to our player
reference. But when you have a thousand
enemies, you do not want to do that.
That would take forever. In fact, if we
play this now without the reference,
well, the game is going to crash right
away. as a key global position based on
type nil. So it just means it's a null.
So we stop that. Now if we add this
check back in here, we can save it, go
back to our 2D, and then run the game.
Well, it's not going to crash, but our
enemy, well, doesn't know where we are.
Now, once again, we could assign them
individually, but that's not efficient
at all. We're going to build a spawner
inside our player scene that handles
this introduction.
back in our game here and close that. In
your script, make sure you have
everything back how we had it before.
Back in our 2D, we want a spawner to
follow the player everywhere. So, we're
going to build it inside the player
scene. So, in our enemy, if you drag and
drop in there, we're going to delete
that out of the scene. We don't need him
in there. Then, we're going to save our
main game scene. Go back to our player
scene here. Now, on our main root node
here, player, right click, add child
node. It's going to be a node 2D. Enter.
And rename this to mob spawner.
Now, as a child of the mob spawner,
right click, add child timer.
Now, we're going to set the wait time on
this timer to 0.5.
Now, it spawns one enemy per half
second. Then, we need to check the auto
start function or it will not work
automatically. This way, we don't have
to start it through code. Now, we need a
spawn path. So, we're going to add as a
as a player node here. Root node. We're
going to right click. We're going to add
a child node called path
2D. There we go. Right there. We're
going to create that. Now, with path 2D
highlighted, I'm going to zoom out in
the screen here. And then, make sure you
can see all the purple lines. This is
the viewport of the camera we have. Now,
we want to select this select points
here. And then to the right of that, we
want to go to add point. So, click on
that. And we're just going to draw a
rectangle around our viewport outside
the bounds. So, we can't see where they
are. Click there. Click outside the
corners
here. There, outside there, and outside
here. Now if you zoom in we can see we
have these green arrows
and here we do not on this left hand
side. So what we need to do since we
have four points we can actually click
this up here called close curve. Click
that. There we go. Now we should have
arrows all the way around. Now within
this outside bounds here is where our
enemies are going to spawn. They'll
spawn here so we can't see them but then
they'll run into our viewport. If you
find you can see enemies spawning if
you're running fast. We'll just make
this wider. They'll spawn further out.
All right. The limb we need to do the
path 2D selected. Right click, add child
node. We need a path follow 2D. So
create that. And we need to make sure
our script can find this path follow 2D.
Super easy. And GDAU. That's not too
hard. Right click on path follow 2D. You
want to be able to access this as a
unique name.
So, make sure you see this little
percentage system here. Right click and
then make sure this box is checked here.
Now, this lets us access this node here
regardless of where it is in the
hierarchy. Now, we script the spawner.
The script has three jobs. Spawn an
enemy, pick a random location that we
just drew, and tell the enemy this is
the player.
So, with our mob spotter selected, we're
going to add a new script not in our
scenes folder. Click the path, go back
up to our root, double click on scripts,
mob spotter.gd is fine,
and create. Now, we get rid of this
boilerplate code here. Just delete this
empty script. Keep the extends. You need
that in GDO. The first thing we want to
do is connect the timer to our mob
spotter script. So we click on timer
over here and go to its node. We want to
collect the time out signal. Now signals
are things like events and calls to say
this thing happened at this time and
then scripts can access when these
things happen in a nutshell. So if you
double click on timeout, you don't want
to connect it to the player, you want to
connect it to the mob spawner on
timerout. So with mob spotter selected,
connect.
Now inside our script here, we also need
a couple extends or export one export.
We need the enemy scene. So what enemy
scene is this mob spotter going to
spawn? That's going to be a packed
scene. So we can in the inspector and
sign assign it. We also need an on ready
call which
if you don't know what on ready is it
really just says make the following
property when the node is ready. So when
this node's ready function is called
then it's ready. Then this node will be
initialized.
Back in our mob spawner here this is
going to be the spawn path. Now like we
did with the percentage sign. This is
how we're going to access our path
follow 2D.
So, it's going to show where to spawn
the enemy and it's going to spawn it
along that path. Right. Now that we have
those, we can go into our timer function
here. The first thing we need to do in
here, well, we need to create the enemy.
And we do that by in a variable, we'll
store in the enemy scene. So, we have a
new enemy. We're going to instantiate
that scene that we have in our export
variable up there and instantiate there.
And after we have our enemy scene, well,
we need to pick a random spot, if I know
how to spell random on the path. How do
we do that? Well, we take our spawn path
on ready variable we have. And then
we're going to get a thing called
progress
ratio. Now what is that? Control-click
on that. It just shows you that it's a
distance along the path and a range 0 to
one. So we just need to get a number
between that. So we do that. We want to
randomize it. So to get that number
randomized, we do a thing called rand f.
Now it's going to return a random float
between zero. If we highlight over that,
there you go. Between zero and one. And
our progress ratio needs between zero
and one for the vertex. That's perfect.
And after that, well, we need to put the
enemy.
The new enemy. Oh, yeah. This wasn't
supposed to be new enemy. It's supposed
to be new enemy.
It's not enemy scene. We're going to
overwrite our scene there. That would be
silly. So, new enemy
here. There we go. Now we're rolling.
So, new enemy. We want to change the
global position of that enemy.
And we want to make it the spawn point
we just got to spawn path. Global
position. Great. Now, the new random
spawn path position, we're going to set
that to our new enemy position.
So, each new enemy that spawns will be
at a random spot on the path. So after
that we need to make sure we actually
tell the enemy where the player is pass
the player reference
which is very important. So and remember
this script is on the player so the
player is going to be the parent. So
since this script is on the player
is well call is a child of the player
the get parent
is the player if that makes sense. So we
do that by saying well we take this new
enemy and then we're going to take the
player reference
player reference. Now, since this new
enemy here is a enemy scene, remember in
our enemy here script, we have the
player reference here. And through our
mob spotter, we can call that with our
new enemy since it knows what references
it has. So, new enemy.player reference.
We're going to use the get parent
because our parent is the player.
Perfect. Well, after that, what we need
to do is well, we need to add the enemy
to the world.
So not the player
or they will move with us. So how do we
differentiate between that? Well, we
want to get the tree. So it gets the
whole scene tree of the current scene
we're in.
So it's going to get the root node and
then add a child to that. We don't want
to add a child to our player. We want to
get the tree of the scene we're in. Get
that root node and then add the child.
and the child's going to be the new
enemy.
Perfect. So, save all that. I'm going
minimize my scripts here. The last but
definitely not least, I'm going to click
on our mob spawner section here and our
inspector. Go back to our inspector. And
we notice there's an enemy scene here.
Now, it's empty. We need to put which
enemy we want to spawn in here. So,
click and drag our enemy scene down here
in the file. Drag it up to our enemy
scene here and drop it in. Now, this is
our enemy scene. Now, you want more than
one enemy. Well, it gets a little more
complicated, not too much, but you have
to create an array and all those things
and then loop through them. Then you
randomize which ones spawn. We're not
going to do that today. We're just stick
with one enemy. We now we have that in
there. We'll be able to spawn it along
that path. Go and save that.
Now, before we get out of here and test,
let's do a quick optimization. So if we
run away fast, enemies might get left
thousands of pixels behind us. We should
definitely delete them to save memory.
So back in our project in our enemy
script here, we'll make this bigger. So
inside our player reference here, we
want to check if we are too far away
from the player. So if we are too far,
say there go 2,000 pixels or so. You can
change this depending on your game. We
can delete ourselves if we know we're
that far. And how do we do that? Well,
we just want to check our global
position of whatever this script is
attached to. And the distance two will
give us a distance to whatever we want.
In this case, we want the player. So,
the player reference global position.
So, now we know where the player is and
how far away we are from it. And we'll
check if it's over 2,000 pixels. If it
is, well, simply we just want to delete
ourselves. In GDO, that's pretty simple.
just Q free. And that frees us from
memory and existing in the game world.
And that optimizes pretty easily.
Great. Now we can minimize this. Make
sure we're in our main game here 2D. And
what we're going to do is go and give
this test. So if you click our run
current scene, we should have our player
be able to run around and then enemies
start spawning, chasing us. And it looks
like they are just fine there and there.
And it looks like Oh, did we forget to
flip the enemy sprite? Let's exit that.
I'm back in our enemy script here. Our
enemy script there. Let's maximize that.
We did. Okay. So what we can do here, so
when we're moving, we also want to just
like we did with the player here, we
want to actually handle the animation
and the sprite flipping as in our enemy
as well. So, we can do that simply since
we did the we don't have an idol, but we
can still take this code from the
player, copy that, and then go to our
enemy script. Since we have the
animation player on there, after we get
our velocity,
we want to paste that in. Make sure this
velocity is tab indented properly.
There we go. Tab these over.
Tab these over. Tab these over. And then
untab this one. Shift tab. We don't have
an idle, so we just want to run. So we
can delete that and handle sprite
flipping. Control save. And we do have
an animation player and a sprite 2D in
our enemy. I minimize this. Go to our
enemy scene. We have those two things.
So, this will still run just the same as
it does on the player. Great. Now, let's
test that again. Run as a main game
scene selected. Then we can run the
current scene. And then we will run
around and our enemies. Yep. Look,
they're running and running and flipping
and running. Perfect.
We now have the core loop. You move,
they chase, but we have no way to defend
ourselves. So, in the next video, we're
going to build an auto attacker. It's a
weapon system that's automatically going
to target the nearest enemy and fire it.
Now, if you found this helpful at all or
fun, leave a comment what your GDO game
dev goals are. I love seeing your stuff.
Now, 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, finished
not perfect, fail fast, fail often.
Always stay curious, and I'll see you at
the next checkpoint.
Ask follow-up questions or revisit key timestamps.
This video explains how to create an enemy character in a 2D game using Godot Engine. It covers setting up the enemy as a CharacterBody2D, adding a sprite and animation, defining collision shapes, and organizing physics layers. The tutorial then details how to give the enemy a basic AI to chase the player by calculating direction and applying velocity. Finally, it introduces a mob spawner system attached to the player to generate enemies at random locations around the screen, with an optimization to delete enemies that are too far from the player to save memory. The video concludes by showing how to flip the enemy sprite to face the direction of movement and teases the next video's topic: an auto-attacker weapon system.
Videos recently processed by our community