HomeVideos

Godot 4.5 Beginner Tutorial: Enemy Spawners & Horde Logic (Part 2)

Now Playing

Godot 4.5 Beginner Tutorial: Enemy Spawners & Horde Logic (Part 2)

Transcript

567 segments

0:01

A hero is nothing without a villain, or

0:04

in our case, a few thousand of them.

0:06

Today, we build the Horde. Welcome back.

0:09

In part one, we built our survivor and

0:12

an infinite world. But right now, it's a

0:14

lonely walk. In this video, we're going

0:16

to create our first enemy, give it a

0:18

brain so it chases us, and then build a

0:20

spawning system to surround us with

0:22

monsters. Let's get building. Now, just

0:26

like our player, our enemy needs to be a

0:28

physical object in the world. We're

0:30

going to use a character body 2D again.

0:33

Why not an area 2D? Well, because

0:35

eventually we want them to bump into

0:36

each other so they don't stack into a

0:38

single pixel death ball. So, I'm going

0:41

to close the game here. And then we're

0:43

going to do a scene, new scene, and then

0:46

we're going to go to other node and then

0:48

get character body 2D again.

0:52

Great. and rename this to enemy.

0:56

There we go. Now, we're going to add a

0:58

sprite 2D. Right click, add child node,

1:00

sprite 2D.

1:03

Now, for my sprite, once again, I'm

1:04

going use some Kenny assets here. And

1:06

let me actually use the creature mixer

1:08

again that we used for our base

1:10

character. Now, one cool thing about

1:12

here is you can actually just randomize

1:15

and get creatures all day long. So,

1:17

whatever suits you, whatever creature

1:19

you think suits your game,

1:22

go ahead and uh stop there. And then you

1:24

do the same thing you do with the

1:25

player. Save the sprite sheet, download

1:27

it where you want, and then import it

1:29

into GDAU. So, once you have the sprite

1:31

you want, click your sprites folder.

1:34

Then, we're going to take your sprite

1:35

you downloaded, click and drag and drop

1:38

import into your game. Same thing here

1:42

with our sprite 2D selected. We're going

1:44

to click and drag and drop into our

1:46

texture area. Now we have the sprite you

1:50

selected in case mine little uh spider

1:54

looking dude. So we're going to do the

1:56

same thing we did with our player in our

1:58

animation section. Here we have one

2:00

frame high which is fine. For the

2:02

vertical we need four frames wide.

2:05

Change that to four. Then we have our

2:07

single first frame as our pixel guy. And

2:11

then we want to change the transform or

2:13

no the offset on the sprite 2D offset.

2:17

Our Y was -12. Should still be the same.

2:20

Yep. So our feet touch the Yaxis zero.

2:24

And we also want to get this guy moving.

2:26

So we can rightclick our enemy, add

2:28

child node, animation player there.

2:33

And with our animation player selected,

2:35

add a new animation. These guys do not

2:37

need an idle. So, we're just going to do

2:39

the run.

2:41

And we'll use the same specs here. We'll

2:43

change it to FPS. Just change it to 8

2:46

FPS. And it's a four-frame animation.

2:50

We'll zoom in here. And then we're going

2:52

to go to our sprite 2D after we select

2:55

our animation player. And this window

2:57

should still be up. And our frame zero,

3:00

our key action. We're going to add one

3:03

create. Then the rest. Two, three, and

3:06

four.

3:08

There. Oh, did that work? Yeah. Zero,

3:12

one, two, and three. Yep, that worked

3:16

just good. Then we can play to test it.

3:19

And now, oh, we need to loop it as well.

3:21

Don't forget that. Play. Oh, and now

3:24

he's walking. Perfect. We also want to

3:27

collide with the player. So enemy, right

3:30

click, add child node, collision 2D,

3:33

collision shape 2D, create. And just

3:36

like we did the player, we'll just do a

3:38

circle in the shape section. Circle.

3:41

Make them a little smaller. Drag it up a

3:44

bit. And that's our collision shape for

3:46

our player. Not a player, our enemy. All

3:49

right. Now, we need to get into some

3:50

layering. So we're Let's pause this.

3:54

Now, instead of do it individually on

3:55

the enemy, let's go into our project.

3:59

project settings and then general here

4:03

and get rid of our search we did before

4:05

and we're going to go down to layer

4:09

layer names right here. Now, we want to

4:11

go in the 2D physics section of the

4:14

layer names. Now, layers we want to deal

4:16

with is layer one. We're going to call

4:18

layer one player.

4:21

And then we're going to call layer two

4:23

enemy.

4:24

Perfect. And layer three finally is

4:28

going to be loot. So, experience, gems,

4:30

or whatever else you want to drop or

4:32

have the enemies drop when they die.

4:34

Let's go and close that. And now with

4:37

our enemy selected in our collision

4:39

section here, when we highlight or hover

4:42

over, we can see it's player, enemy, and

4:45

loot. And player, enemy, and loot. Now,

4:50

for our enemy here, we want to deselect

4:53

the player and select the layer as

4:55

enemy. And in our mask, well, this is

4:58

what they collide with or what it's

5:00

detecting. So, we want to keep layer one

5:02

checked so it seats the player. and

5:04

walls. By default, walls are going to be

5:06

one as well. We also want to select the

5:08

enemy so they don't stack up. Great.

5:11

Now, we can save this scene. Crl S or

5:14

saying save in our scenes folder as

5:17

enemy.

5:19

Now, we're going to give our enemy a

5:20

brain. The logic is simple. Find the

5:22

player. Look at the player. Move toward

5:25

the player. We're going to keep it

5:26

pretty efficient. The first thing we're

5:27

going to do is on the enemy selected,

5:29

right click, and then we're going to

5:31

attach a script. and not in the scenes

5:34

folder. We're going to go under our path

5:36

here and go up to our root and go into

5:39

our scripts folder. That's where we want

5:41

to keep all our scripts. Enemy GD is

5:43

fine. Then boiler plate is fine for now.

5:46

Create. Now I will get rid of all this

5:50

extra here and maximize so you can see

5:52

better. Now we have all our boiler plate

5:54

again. Now we're not going to use jump

5:56

or any of this UI stuff. So we're going

5:59

to highlight and delete all the guts of

6:01

this physics process. We're still going

6:03

to use the function, just not what's

6:04

inside of it. And then we'll get rid of

6:06

this delta caution. Perfect. And we're

6:09

going to get rid of these constants

6:10

here. Perfect.

6:13

Now we have an export variable we're

6:15

going to use just like we did for the

6:17

player. And we can export it so we can

6:19

edit it as per our preference. The bar

6:23

speed is going to be start with 100. And

6:26

then we also need to export a player

6:28

reference so it knows where the player

6:30

is after it can get a reference to it.

6:33

So player reference v player reference

6:36

and it's going to be a character body

6:39

2D. Now we have a reference to the

6:41

player. We go into our physics process

6:43

here. Make sure we tab indent over

6:46

inside. And then we need to check if we

6:48

have a player reference. So we don't

6:50

have a null reference error. We don't

6:52

have it assigned. So if player

6:54

reference. So now inside that now that

6:56

we know we have a player reference we

6:59

want to calculate the direction to the

7:02

player and we do that pretty much the

7:04

same we do the other one var direction

7:08

equals and here's where it gets

7:11

different we're going to get the global

7:13

position dot direction two here if you

7:18

control-click on direction two you can

7:20

look at the documentation just returns a

7:23

normalized vector pointing from this

7:25

vector to another one. So, back in our

7:28

enemy GD script now, direction two is

7:31

going to need a couple things. It's

7:32

going to need at least

7:35

where to go. So, wherever this script is

7:38

attached to, where does it need to go?

7:40

Well, we need to go to the player player

7:42

reference global position. There we go.

7:46

So, the script this is attached to is

7:48

global position direction to the player

7:50

reference global position. And what to

7:53

do after we have that direction? Well,

7:55

we're going to move towards the player.

7:57

Then move towards player.

8:00

And that's going to be just with the

8:02

velocity again, just like we did with

8:03

the player. Velocity. Do our direction

8:06

times the speed.

8:09

Perfect.

8:11

And the last thing we want to do is well

8:13

move the player. And we don't move at

8:16

all in if we don't have our player

8:18

reference. So, we're actually going to

8:19

put this move and slide inside of our

8:22

player reference function.

8:25

So, then we can move the enemy. Did I

8:27

say player? No. Move the enemy. Perfect.

8:30

Now, you've reached a checkpoint. Now,

8:32

if you drag this enemy into your main

8:34

scene right now and ran the game, well,

8:36

it wouldn't move. Now, in fact, if we

8:38

didn't actually use the if statement on

8:40

the player reference, the game would

8:42

crash. Well, why? Because the enemy

8:45

doesn't know who the player is yet. We

8:47

haven't told it. If you want to try it,

8:49

let's try it. Let's go ahead and take

8:51

out this if reference player reference

8:53

check here. Now, all this will run now

8:57

even if we don't have a player

8:58

reference. So, if you go down to we go

9:00

back to our main game scene, I minimize

9:03

this here. Go to our 2D section. I'm

9:05

just going to drag our play our enemy

9:07

into our scene here. If I drag and drop

9:11

on the main game, there we go. Now, we

9:12

have an enemy

9:15

on our scene.

9:17

Now the enemy is stacked on top of where

9:19

our player is. So use our move tool

9:22

here. We're going to move this way. Now

9:24

we have our player. We have our main

9:25

enemy. Now we have a reference here we

9:28

could assign manually to our player

9:31

reference. But when you have a thousand

9:33

enemies, you do not want to do that.

9:35

That would take forever. In fact, if we

9:38

play this now without the reference,

9:42

well, the game is going to crash right

9:44

away. as a key global position based on

9:46

type nil. So it just means it's a null.

9:49

So we stop that. Now if we add this

9:51

check back in here, we can save it, go

9:54

back to our 2D, and then run the game.

9:58

Well, it's not going to crash, but our

10:00

enemy, well, doesn't know where we are.

10:03

Now, once again, we could assign them

10:05

individually, but that's not efficient

10:07

at all. We're going to build a spawner

10:09

inside our player scene that handles

10:11

this introduction.

10:15

back in our game here and close that. In

10:17

your script, make sure you have

10:18

everything back how we had it before.

10:21

Back in our 2D, we want a spawner to

10:23

follow the player everywhere. So, we're

10:25

going to build it inside the player

10:27

scene. So, in our enemy, if you drag and

10:29

drop in there, we're going to delete

10:30

that out of the scene. We don't need him

10:32

in there. Then, we're going to save our

10:34

main game scene. Go back to our player

10:36

scene here. Now, on our main root node

10:39

here, player, right click, add child

10:41

node. It's going to be a node 2D. Enter.

10:46

And rename this to mob spawner.

10:50

Now, as a child of the mob spawner,

10:52

right click, add child timer.

10:56

Now, we're going to set the wait time on

10:57

this timer to 0.5.

11:01

Now, it spawns one enemy per half

11:04

second. Then, we need to check the auto

11:06

start function or it will not work

11:08

automatically. This way, we don't have

11:10

to start it through code. Now, we need a

11:12

spawn path. So, we're going to add as a

11:15

as a player node here. Root node. We're

11:17

going to right click. We're going to add

11:19

a child node called path

11:23

2D. There we go. Right there. We're

11:26

going to create that. Now, with path 2D

11:29

highlighted, I'm going to zoom out in

11:30

the screen here. And then, make sure you

11:32

can see all the purple lines. This is

11:35

the viewport of the camera we have. Now,

11:38

we want to select this select points

11:40

here. And then to the right of that, we

11:42

want to go to add point. So, click on

11:45

that. And we're just going to draw a

11:48

rectangle around our viewport outside

11:51

the bounds. So, we can't see where they

11:55

are. Click there. Click outside the

11:57

corners

11:59

here. There, outside there, and outside

12:04

here. Now if you zoom in we can see we

12:07

have these green arrows

12:10

and here we do not on this left hand

12:12

side. So what we need to do since we

12:14

have four points we can actually click

12:17

this up here called close curve. Click

12:19

that. There we go. Now we should have

12:21

arrows all the way around. Now within

12:24

this outside bounds here is where our

12:26

enemies are going to spawn. They'll

12:28

spawn here so we can't see them but then

12:30

they'll run into our viewport. If you

12:32

find you can see enemies spawning if

12:34

you're running fast. We'll just make

12:36

this wider. They'll spawn further out.

12:38

All right. The limb we need to do the

12:41

path 2D selected. Right click, add child

12:43

node. We need a path follow 2D. So

12:47

create that. And we need to make sure

12:50

our script can find this path follow 2D.

12:53

Super easy. And GDAU. That's not too

12:55

hard. Right click on path follow 2D. You

12:58

want to be able to access this as a

13:00

unique name.

13:02

So, make sure you see this little

13:03

percentage system here. Right click and

13:07

then make sure this box is checked here.

13:10

Now, this lets us access this node here

13:13

regardless of where it is in the

13:14

hierarchy. Now, we script the spawner.

13:17

The script has three jobs. Spawn an

13:19

enemy, pick a random location that we

13:21

just drew, and tell the enemy this is

13:24

the player.

13:26

So, with our mob spotter selected, we're

13:28

going to add a new script not in our

13:31

scenes folder. Click the path, go back

13:33

up to our root, double click on scripts,

13:37

mob spotter.gd is fine,

13:40

and create. Now, we get rid of this

13:42

boilerplate code here. Just delete this

13:46

empty script. Keep the extends. You need

13:48

that in GDO. The first thing we want to

13:50

do is connect the timer to our mob

13:53

spotter script. So we click on timer

13:56

over here and go to its node. We want to

13:59

collect the time out signal. Now signals

14:03

are things like events and calls to say

14:07

this thing happened at this time and

14:10

then scripts can access when these

14:12

things happen in a nutshell. So if you

14:15

double click on timeout, you don't want

14:18

to connect it to the player, you want to

14:19

connect it to the mob spawner on

14:22

timerout. So with mob spotter selected,

14:25

connect.

14:27

Now inside our script here, we also need

14:30

a couple extends or export one export.

14:34

We need the enemy scene. So what enemy

14:36

scene is this mob spotter going to

14:39

spawn? That's going to be a packed

14:41

scene. So we can in the inspector and

14:44

sign assign it. We also need an on ready

14:48

call which

14:50

if you don't know what on ready is it

14:51

really just says make the following

14:53

property when the node is ready. So when

14:56

this node's ready function is called

14:58

then it's ready. Then this node will be

15:00

initialized.

15:02

Back in our mob spawner here this is

15:05

going to be the spawn path. Now like we

15:08

did with the percentage sign. This is

15:10

how we're going to access our path

15:12

follow 2D.

15:15

So, it's going to show where to spawn

15:18

the enemy and it's going to spawn it

15:20

along that path. Right. Now that we have

15:22

those, we can go into our timer function

15:25

here. The first thing we need to do in

15:27

here, well, we need to create the enemy.

15:31

And we do that by in a variable, we'll

15:34

store in the enemy scene. So, we have a

15:37

new enemy. We're going to instantiate

15:40

that scene that we have in our export

15:43

variable up there and instantiate there.

15:47

And after we have our enemy scene, well,

15:50

we need to pick a random spot, if I know

15:53

how to spell random on the path. How do

15:56

we do that? Well, we take our spawn path

16:00

on ready variable we have. And then

16:02

we're going to get a thing called

16:04

progress

16:06

ratio. Now what is that? Control-click

16:09

on that. It just shows you that it's a

16:12

distance along the path and a range 0 to

16:14

one. So we just need to get a number

16:16

between that. So we do that. We want to

16:19

randomize it. So to get that number

16:21

randomized, we do a thing called rand f.

16:25

Now it's going to return a random float

16:29

between zero. If we highlight over that,

16:31

there you go. Between zero and one. And

16:33

our progress ratio needs between zero

16:36

and one for the vertex. That's perfect.

16:40

And after that, well, we need to put the

16:43

enemy.

16:44

The new enemy. Oh, yeah. This wasn't

16:47

supposed to be new enemy. It's supposed

16:48

to be new enemy.

16:50

It's not enemy scene. We're going to

16:51

overwrite our scene there. That would be

16:53

silly. So, new enemy

16:57

here. There we go. Now we're rolling.

17:00

So, new enemy. We want to change the

17:03

global position of that enemy.

17:06

And we want to make it the spawn point

17:10

we just got to spawn path. Global

17:13

position. Great. Now, the new random

17:16

spawn path position, we're going to set

17:18

that to our new enemy position.

17:22

So, each new enemy that spawns will be

17:24

at a random spot on the path. So after

17:26

that we need to make sure we actually

17:28

tell the enemy where the player is pass

17:32

the player reference

17:35

which is very important. So and remember

17:37

this script is on the player so the

17:39

player is going to be the parent. So

17:42

since this script is on the player

17:47

is well call is a child of the player

17:51

the get parent

17:53

is the player if that makes sense. So we

17:57

do that by saying well we take this new

17:59

enemy and then we're going to take the

18:03

player reference

18:07

player reference. Now, since this new

18:10

enemy here is a enemy scene, remember in

18:14

our enemy here script, we have the

18:17

player reference here. And through our

18:19

mob spotter, we can call that with our

18:21

new enemy since it knows what references

18:23

it has. So, new enemy.player reference.

18:26

We're going to use the get parent

18:28

because our parent is the player.

18:31

Perfect. Well, after that, what we need

18:33

to do is well, we need to add the enemy

18:36

to the world.

18:38

So not the player

18:41

or they will move with us. So how do we

18:45

differentiate between that? Well, we

18:48

want to get the tree. So it gets the

18:50

whole scene tree of the current scene

18:52

we're in.

18:54

So it's going to get the root node and

18:56

then add a child to that. We don't want

18:58

to add a child to our player. We want to

19:00

get the tree of the scene we're in. Get

19:04

that root node and then add the child.

19:06

and the child's going to be the new

19:08

enemy.

19:10

Perfect. So, save all that. I'm going

19:13

minimize my scripts here. The last but

19:15

definitely not least, I'm going to click

19:17

on our mob spawner section here and our

19:20

inspector. Go back to our inspector. And

19:22

we notice there's an enemy scene here.

19:23

Now, it's empty. We need to put which

19:26

enemy we want to spawn in here. So,

19:28

click and drag our enemy scene down here

19:30

in the file. Drag it up to our enemy

19:33

scene here and drop it in. Now, this is

19:35

our enemy scene. Now, you want more than

19:37

one enemy. Well, it gets a little more

19:39

complicated, not too much, but you have

19:40

to create an array and all those things

19:42

and then loop through them. Then you

19:43

randomize which ones spawn. We're not

19:45

going to do that today. We're just stick

19:46

with one enemy. We now we have that in

19:49

there. We'll be able to spawn it along

19:53

that path. Go and save that.

19:58

Now, before we get out of here and test,

19:59

let's do a quick optimization. So if we

20:01

run away fast, enemies might get left

20:03

thousands of pixels behind us. We should

20:06

definitely delete them to save memory.

20:09

So back in our project in our enemy

20:11

script here, we'll make this bigger. So

20:14

inside our player reference here, we

20:16

want to check if we are too far away

20:18

from the player. So if we are too far,

20:23

say there go 2,000 pixels or so. You can

20:26

change this depending on your game. We

20:28

can delete ourselves if we know we're

20:30

that far. And how do we do that? Well,

20:32

we just want to check our global

20:33

position of whatever this script is

20:36

attached to. And the distance two will

20:38

give us a distance to whatever we want.

20:41

In this case, we want the player. So,

20:43

the player reference global position.

20:45

So, now we know where the player is and

20:47

how far away we are from it. And we'll

20:49

check if it's over 2,000 pixels. If it

20:52

is, well, simply we just want to delete

20:53

ourselves. In GDO, that's pretty simple.

20:55

just Q free. And that frees us from

20:59

memory and existing in the game world.

21:02

And that optimizes pretty easily.

21:06

Great. Now we can minimize this. Make

21:08

sure we're in our main game here 2D. And

21:12

what we're going to do is go and give

21:13

this test. So if you click our run

21:16

current scene, we should have our player

21:19

be able to run around and then enemies

21:21

start spawning, chasing us. And it looks

21:24

like they are just fine there and there.

21:30

And it looks like Oh, did we forget to

21:32

flip the enemy sprite? Let's exit that.

21:36

I'm back in our enemy script here. Our

21:39

enemy script there. Let's maximize that.

21:43

We did. Okay. So what we can do here, so

21:46

when we're moving, we also want to just

21:49

like we did with the player here, we

21:52

want to actually handle the animation

21:54

and the sprite flipping as in our enemy

21:57

as well. So, we can do that simply since

22:01

we did the we don't have an idol, but we

22:04

can still take this code from the

22:08

player, copy that, and then go to our

22:12

enemy script. Since we have the

22:14

animation player on there, after we get

22:16

our velocity,

22:18

we want to paste that in. Make sure this

22:22

velocity is tab indented properly.

22:26

There we go. Tab these over.

22:30

Tab these over. Tab these over. And then

22:32

untab this one. Shift tab. We don't have

22:35

an idle, so we just want to run. So we

22:39

can delete that and handle sprite

22:42

flipping. Control save. And we do have

22:45

an animation player and a sprite 2D in

22:47

our enemy. I minimize this. Go to our

22:50

enemy scene. We have those two things.

22:52

So, this will still run just the same as

22:55

it does on the player. Great. Now, let's

22:58

test that again. Run as a main game

23:01

scene selected. Then we can run the

23:03

current scene. And then we will run

23:06

around and our enemies. Yep. Look,

23:08

they're running and running and flipping

23:11

and running. Perfect.

23:15

We now have the core loop. You move,

23:17

they chase, but we have no way to defend

23:19

ourselves. So, in the next video, we're

23:21

going to build an auto attacker. It's a

23:23

weapon system that's automatically going

23:24

to target the nearest enemy and fire it.

23:28

Now, if you found this helpful at all or

23:29

fun, leave a comment what your GDO game

23:31

dev goals are. I love seeing your stuff.

23:34

Now, please like, subscribe, and hit

23:35

that notification bell. It really helps

23:37

out the channel. And thank you to all

23:38

our current and past Patreon and coffee

23:41

members. Your generous support keeps the

23:43

channel moving. Now, if you want early

23:45

access to tutorials, source code, or

23:47

suggesting future tutorials, please

23:49

consider becoming a member yourself. The

23:51

links are in the description. I'm

23:53

Spaghetti Syntax, and remember, finished

23:55

not perfect, fail fast, fail often.

23:58

Always stay curious, and I'll see you at

24:00

the next checkpoint.

Interactive Summary

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.

Suggested questions

9 ready-made prompts