Godot 4.5 Beginner Tutorial: XP Systems, Level Up UI & Pausing (Part 4)
601 segments
Welcome back to the checkpoint. Right
now, our game is endless but pointless.
In this video, we're going to fix that.
We'll create XP gems that drop from
enemies, a UI to track our progress, and
a system that pauses the chaos so we can
choose an upgrade. Killing monsters is
fun, but getting stronger is addictive.
Today, we're going to close that
gameplay loop by adding experience,
levels, and the most important screen in
the game, the upgrade menu.
Well, first we need the loot. This is a
simple object that sits in the world
waiting to be picked up. Now we do that
the scene, new scene. Now we're going to
use the other node again. We're going to
use an area 2D
2D. Enter. Now we're going to rename
this to experience gem.
There we go. And then we need to add
what it looks like. Add child node. Just
a sprite 2D.
And bring my file system back up here.
go to our sprites folder. And once
again, I'm using some Kenny assets here.
And what I'm going to use is the little
coin from the pixel platformer one here.
I'll link all these in the description.
And once you get that download, if you
haven't already, the sprites folder
selected. Now, the one I'm going to use
is tile0151.
If you're following along with that,
that's going to be the little coin
thing. All right. And now with sprite 2D
selected, I can click and drag and drop
that into the texture here. I zoom in. I
got a little coin. And then we just need
to add a to the experience gem. Select
that. Add child node collision 2D.
Collision shape 2D. And once again,
we're just going to use a shape circle.
We're going to make that make it a
little bigger than the actual probably
twice as big as the pickup itself. It's
a quality of life thing for the player.
You don't want to make it hard for these
to pick up. Great. Now, if you remember,
we need to go to our experience gem and
our collision. Now, we set up a layer
already. We want it's not a player, it
is loot. So, we want this to be a loot.
And we want the player on the mask to be
able to detect when it runs into it.
Great. Now that we have that, we need to
have a script to tell what happens when
this collides with the player. the
experience gem selected. We're going to
add a new script. Not in the root
folder. We'll go the path. We're gonna
make sure we're in the scripts folder.
Open. Now we're in the scripts folder.
Create. Let's delete the boiler plate.
The first thing we want to do is on the
experience gem selected on the node. If
this is blank, just click away and click
back on. We want to get body entered
again. Just like you did with the
projectile. Double click. Make sure it's
on the experience gem. connect. That
should populate into your script we just
wrote. I'll make this bigger. Get rid of
this output. First thing we want to do
is we want an export variable. So this
export variable is going to tell us well
the XP amount that this is going to give
us. So for now we'll just set it to 10.
They're automatic entered and get rid of
this placeholder.
Now, first thing I do is say, well, if
the player touches this,
give them XP.
There we go. So, we need to check a
certain method just like we did with the
enemy, the take damage one. But in this
one, we got to make sure the body since
we know the body is the player. Well, if
it has the method, we're going to write
this method here in a second just like
we did the shoot method or the take
damage one.
We will with the take damage one. So has
method gain XP.
Okay. And after that we're going to go
into the if statement there. Well, first
we're just going to print to the console
experience gained
colon concatenate plus. We want to turn
our XP amount into a string. XP amount.
That'll print to a console. If you don't
turn that into a string, it will throw
an error. So make sure you do the str in
the parenthesis. All right. And then
after that, well, we're going to call
the the gain XP method that we're going
to write here in a second. XP amount.
Perfect. And then after we've collected
the gem, well, we want to get rid of it.
You never want to keep things floating
around for no reason. So remove the gem
after we've given the XP. Great. Now
let's control S to save that. as
experience gem scene in the scenes
folder. Save. Now we go back to our
enemy. Remember that take damage
function we pretended to have in the
last video? Well, it's time to write it
for real.
Great. Now, let's go to our enemy GD
script here. Now, we're going to go up
to the top here. We're going to add
another export variable here. What this
is going to do is going to hold the uh
loot that is going to drop. So, it's
going to be the loot scene. That's going
to be another pack scene here.
And then we'll scroll down to the
bottom. What we're going to do here is
add that take damage function function
take damage.
Great. The first thing we want to do in
this take damage. Well, we want to spawn
the gem because if we're going to die in
one hit. Now, once your enemies have
health, well, you want to spawn the gem
just when they die, not every time they
get hit. That's something we can add in
the polish section later. So, first
thing is var new gem. I'm going to store
this new gem in in a variable. And we're
going to do that by instantiating the
loot scene we established up top and
instantiating it. Perfect.
And the with this new gem, we're going
to need the global position of well the
enemy, which is what this is script is
attached to. So, we can just write
global position. Now, this enemy is
going to get deleted here in a second.
So, we don't want to add it to the enemy
tree or the enemy uh as an enemy to a
child to the enemy. We want to once
again add it to a child of the main
scene. So, if you remember, it's going
to be get tree
current scene. Now, we're going to do a
little differently because you can't
modify the physics tree by adding and
removing bodies during a collision call
back without risking crashing. So we're
going to use a thing called call
deferred. Now this waits until the end
of the frame so it does it safely. So
call deferred.
Now you use this a lot going through. It
saves a lot of headaches. So call
deferred is going to save us from doing
too many things at once. So what is call
deferred going to do? Well, we need it
to add child
there. We're going to call the add child
function in call deferred. So when the
frame ends and when what child do we
want to add? Well, we just want to call
the new we're going to use the new gem.
So call deferred is going to wait till
the end of the frame. So anything that's
happening in the physics can finish and
then it's going to add this to the
current scene root node just like we
need to. And well last thing we need to
do Oh die. Yep. So we're going to kill
the enemy. It's going to be cube free.
Now everything's done. This gem is
spawned. Now the enemy can go away. Now
none of this will work if you don't
assign the projectile. So I'm going to
minimize this here quick. Experience.
I'm go to the main or the enemy scene
here. Go back to my inspector node and
my enemy here. Scroll up to the top. I
have a loot scene here. So the loot
scene is the scene I want to spawn when
I die, which is the experience gem here.
Drag and drop that there. Now the enemy
knows what loot to drop when it dies.
Now control S to save. Now the gem tries
to call gain XP on the player, but
remember that doesn't exist yet. So
let's add the stats to our hero. The
first thing we need to do is open up the
player GD script. I'll make this bigger.
And we need to add some variables up at
the top here. So we'll do another export
variable. We can edit this as your heart
desires, but we're going to start it
with bar health equals 100. That's good
for now. We need some normal variables
that the script just needs access to,
which is going to be the experience. We
start with zero. We also need the level
of the player. So, experience level
start at one. And then the requirement
for each level, which we can edit as the
levels get higher. So the first level is
required is going to be 100. We also
need to add I'm add up top here. Enter
down a couple times and add some
signals. So signals is going to let
signals that let the UI know when things
change. Perfect. Now what this ones we
need, we need a signal for I spell it
right. Signal for the experience gained.
So, we update the progress bar and the
experience gained.
And we we need the growth data, which
we're going to use to make the bar go up
as we get more experience. And we also
need to signal when we've leveled up.
So, when we've leveled up is going to
show us, well, when the level up screen
is going to pop up.
All right, I'll scroll down to the
bottom here. Enter down a couple times
after our process function. enter back
and write a new function called gain XP.
And how much XP? Well, the amount is
going to be passed in. Great. So now we
need to well, we need to take the
experience and add the amount to it.
Just like that. Easy enough. And then
what we need to do is emit the signal so
the UI can update our experience bar. So
the leveled up leveled
I spelled right. Leveled up. There it
is. Leveled up. That emits the signal
that we just wrote up here
that we have leveled up. Perfect. And
then after we'll enter down a couple
more times, we write another function
called level up. Level up. And what
happens when we level up? Well, we're
going to update the experience level by
one. Just like that. Plus equals one.
And we also need to update how much more
experience it's going to require for the
next level. Then we want to be 100 every
time. We want to make it harder as we
go. So we're going to plus equal by 50
every time. So this just makes uh the
next level harder. The next level
harder. Perfect. And then here, well,
after we've done that, well, we're going
to pause. So we'll pause the game here.
And then oh we'll do that in part five
in the next video or next uh section.
Same video. Perfect. Part five.
So for now what we're going to do is
just print. Let me finish up this
function. See if it works. Level up.
And it's going to be new level
is going to be with a comma. Experience
level. This will just add the experience
level to the end of the print statement.
So, new level will be the current
experience level we're on. Great. Now,
before we need to tell the gain XP here,
we'll go in the middle after the
experience amount. Well, we need to
check if we've leveled up
after we've added the amount to the
experience. So, we're going to check for
level up. Then, we can call that
function that we just wrote. So if the
experience
is greater than or equal to the
experience gained,
no not gained, required. Experience
required. That's the one we're looking
for. And what we need to do there is
just take the experience minus equal the
experience required. We're going to
reset that
there so you can start leveling up on
the next one after the level up function
has been fired. We're going to check if
our experience is enough. We're going to
reset our experience and then we're
going to level up. All right, with all
that done, we can save. Go to our main
game here and make this smaller and then
run the current scene and give this a
test and go around shoot the enemies.
collect the coins and we pull this up a
little bit. We can see every coin we
collect here is 10. So if we get enough
coins, we need 10 coins to level up. So
if we get to coins, 10 10. And now we're
level two. Perfect. That is working as
intended.
Now you've reached a checkpoint. If you
run the game now, you can shoot the
enemies. They drop the gems. You collect
them. Check the output tab and you can
see all the print messages. But the
player without that window has no idea
they're progressing. We need a heads up
display, a HUD. Now, if you never
created one before in GDAU, it's a node
called a canvas layer. Now, this special
node ensures our UI is going to stay
stuck to the screen even when the camera
moves around. So, let's build that UI.
We'll keep it simple. A progress bar at
the top of the screen. So, I go to our
main game scene, 2D tab here. Make this
a little smaller here. Get rid of that.
Make this bigger. Perfect.
Now, for this, we're just going to
attach it to the main game. You can
attach to the player, but for
simplicity, we're just use the main
game. Right click, add child node. We're
going to use the canvas layer. Perfect.
So, create that. And then, as a child of
this canvas layer, we're going to add a
progress bar right here. Create. And
then in the game window here, we're
going to use these anchors. We're going
to do top wide. Just like that. We go
into here and scroll in and see our
progress bar right there. Now that we
have that, we can go to our canvas
layer. I'm going to rename this to UI.
And then we're going to attach a script
to this UI. Right click, attach script.
And now in our scenes folder, go to our
path up our root node and scripts
folder. UI.gd open. Defaults are fine.
I'll make this bigger. I'm going to get
rid of the
boilerplate code just by habit. So what
we want to do now is we want to have an
export on top to get all the player so
we know where and what to track. So
we're going to export the player and
that's a character body 2D.
And then we also need in a on ready
check. So when this this node this
script is attached to is ready, we want
to get the XP bar which is going to be
the progress bar. So once again, money
symbol progress. That should pop up
automatically unless you did not or you
did rename it something else. You just
want to make it so it matches your path.
Now we have both of those. We actually
need to make that ready function
function ready. First thing we want to
do is initialize
the bar.
Initialize there we go bar. So when this
is ready we start well we want to set
the bar initially to zero. The value to
zero.
So and then we set the max value of the
bar
to 100.
Just like that. So, we want to go from
zero to 100. That's how we're starting
out. Okay. Now, we have that set up.
Let's make another function. We're going
to call this one. Enter down a couple
times. Function on experience
gained.
Okay.
We're going to take in a couple things
called current experience and then our
max XP. And so we're going to check our
bar value and make that the current
that's passed in to our current XP. And
then we need to check the max value if
that's gone up. If we've leveled up, we
want to check the XP bar max value and
set that to the new max XP. Perfect. But
we want to be able to track the player.
So we can't get any of this if we don't
have the player. So we go into our ready
function again, enter down a couple
times. So we want to connect to the
player but safely. Connect player but
safely. So we want to have an if
statement here. If the player exists,
then we want to connect. How do we
connect? Well, we have the player. We
want to connect the experience
experience gained. So we look at our our
player script.
Again, we have the scroll to the top in
our player script, we have the
experienced gain signal. That's what
we're connecting to. Back in our UI
script, our player experience gain
signal. We want to connect to that. And
we're connect to that with our function
we just wrote on. We want to pass that
in experience gained. We do not want the
parenthesis on the end, just the one.
So, we're going to send in to this
signal when this experience is gained.
So, we can update
appropriately. Now, none of this will
work if we do not assign the player to
our export variable. So, I'll make this
smaller. And in our UI selected here, we
have in our inspector a player section.
Now, we want to drag our player that we
have in our main game and drop into our
player. Now the UI knows where the
player is. Now finally, when we hit the
limit, we want the game to stop. We need
a level up popup.
Now on our UI screen here, go back to
our 2D so we can see what's happening.
Make this a little smaller.
Now with this UI selected, right click,
add child node. We're going to add a
panel. Now this panel here, I'm going to
create. We're going to rename this panel
level up panel. Now, we're going to
anchor this. And right here in the
anchor to the center. And then in the
layout, I'll make this bigger.
Try it. There we go. And our layout in
the inspector with the level up panel
selected. I'll make the custom minimum
size 400x
then 500 Y. It's a decent size to take
up our viewport. And then in this level
up panel, right click. I'm going to add
a child node. And I'm going have a label
right there.
And then this label I'm going to anchor
to the top center right there. And I'm
also going to have horizontal alignment
with the label selected to center. And
in the text field going to say level up.
Great. Now, this level up panel
selected. Well, I'm going to hide that.
We don't want to see that until we level
up. And back in our scripts, let's go
there. And I'll expand this out. We want
to go in our player GD script here.
We need to handle the pausing. So, we
need to go to our update. We need our
level up. We need to update our level up
function. So, our filter methods level
up. It's on the bottom. Well, after our
experience required and all that stuff's
updated, well, we're going to pause the
game. Pause the game. How do we do that?
Well, we get the whole scene. We get
tree and we need to pause and we make
that true. So, now the game will be
paused soon as we level up. Now, instead
of printing, what we're going to do now
that we're paused, well, we need to show
the UI. Show the UI. Not shot. show
there. So, we need a reference a signal
to actually show the UI. So, the one
we're going to emit is the emit the
signal
leveled up.
There we go. So, we have it there. So,
the one we're calling up here. So, if we
scroll to the top, we have our signal
leveled up. That's what we're signaling
that we've leveled up and show the level
up screen. But if we send the signal,
it's currently falling on deaf ears
because nobody is listening. So let's go
back to our UI GDScript.
Control S and save all these scripts.
Scroll down the bottom underneath
experience gained. We're going to make a
new script called on level up.
And then when we have the level up
signal that we hear and use the money
symbol, level up panel and make sure you
this doesn't pop up. Then maybe you
named it different. This is has to match
or it won't work. So level up panel
visible
there. And we're going to make that
true.
In order to actually hear what the
signal was from the player, we need to
connect it just like we did with the
experience gained. So in here, enter
down. We're going to do player leveled
up.
Leveled up. going to connect to that
signal and we're going to pass in our
function on level up.
It's going to call the function that we
have on level up. Make this visible.
Now you reached a checkpoint. Now if you
run this game now and you level up,
everything pauses. Can't click buttons,
can't do anything. Now there's a certain
option you can select to make the level
up screen immune to the pause. Now, in
the UI canvas layer, it needs a process
mode always or when paused. If you leave
it on the inherit, the UI is going to
freeze along with the enemies, and you
won't be able to click anything.
So, our UI here, our UI panel, we want
to go to our process. You want to go to
our mode. You want to make sure when
paused or always. We can use when paused
for now. So, when the game is paused, we
can still use all of our UI stuff.
Great. Now, with all that connected,
let's try this current scene. All right,
run around an enemy. Collect a coin.
That should not pop up right away. We
got a bug here. Let me take a look.
Okay, after looking in our player GD in
our gain XP function,
accidentally was calling leveled up
right away. We don't want to do that. We
want level up to handle that. So it
actually pauses. So we want to get rid
of this signal here. And we want to
actually make a call something else
which is the experience gained.
So we're not leveling up. We just want
to see the experience gained emit, not
the leveled up emit. My fault. So we
want to emit the experience that we have
and the experience required.
There we go. We're leveling up a little
too soon, but now we're emitting the
experience gained, which will fix that
bug. Let's go and give it another test
after we save main game scene. And then
we'll minimize this or 2D. We're in
current scene. Now, let's try blasting
some enemies and collecting some coins.
All right, perfect. They got one, 10,
20,
30, 40,
50, 60, 70, 80, 90, and level up. There
we go. Our game pauses and level up
screen pops up and our progress bar
resets to zero. Great. Now, if we run
the game, we can blast the horde,
collect the gems, watch the bar fill.
Apart from a bug, well, we fixed it.
That's part of gamede dev is debugging
our projects when we call things too
early or not at all. So, and when it
hits the max, boom, the game freezes and
your level up screen appears. So, of
course, right now we're stuck in the
pause screen forever. So, in the next
video, we're going to fix that by adding
upgrade cards. Now, there going to be
buttons that let us choose new powers or
upgrades once we have. Then, we need to
unpause the game and make our character
even stronger. 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 focuses on adding core gameplay mechanics to an endless game, specifically experience gems, a UI progress bar, and a level-up system with an upgrade menu. It covers creating the XP gem as a scene with collision, scripting its behavior to grant XP to the player upon collection and then self-destructing. The enemy script is updated to drop these XP gems upon death, using `call_deferred` to safely add the gem to the scene. The player script is enhanced to include variables for health, experience, level, and experience required for the next level. It also introduces signals for 'experience gained' and 'leveled up' to communicate with the UI. A UI canvas layer is implemented to display a progress bar that updates as the player gains experience. Finally, a level-up panel is created, and the game is set to pause upon leveling up, with the UI configured to remain responsive. A bug where the 'leveled up' signal was emitted too early is identified and fixed by ensuring the 'experience gained' signal is used correctly.
Videos recently processed by our community