Introduction to GDScript in Godot 4 Part 1
Get started learning GDScript in this two-part tutorial. You’ll learn about the built-in script editor, using variables and player input. By Eric Van de Kerckhove.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Introduction to GDScript in Godot 4 Part 1
45 mins
Signals
A signal in Godot is a message emitted by a node when a certain event occurs. Nodes can subscribe these signals to do actions of their own. Some built-in examples if signals include:
- The pressing of a button
- A node entering the viewport
- The collision of two areas
- A sound effect that finished playing
This a powerful system that allows you to keep your code flexible and loosely coupled, as the node emitting the signal doesn’t care about its subscribers.
To start off, open the jumper scene in the scenes folder and add a new script to the Jumper node named jumper.gd. Like with the player_avatar script, make sure to place it in the scripts folder.
After creating the script, it should be automatically opened in the script editor. Remove both the _ready
and _process
functions, as you won’t need them. You should be left with just this line:
extends AnimatedSprite2D
In contrast to the player avatar’s Node2D
, the root node type for the jumper is an AnimatedSprite2D
, which inherits from Node2D
and adds support for animations.
You’ll be using a signal to detect when the avatar enters a jumper’s Area2D node. Signals can be linked to a script either via the editor or via code, in this case via the editor makes the most sense. Select the Area2D node in the Scene dock and open the Node tab next to the Inspector tab.
You’ll now see a list of signals the selected node supports.
In the case of Area2D, this list is massive and full of useful signal to connect to. To detect when another Area2D enters this one, you’ll need the area_entered
signal, so double click it to start connecting it. This will show a dialog window with some options for the connection.
By default, it will select the root node, Jumper as the node to connect to. This is what you want, so there’s nothing to change there.
Below that is the Receiver Method, this is the name of the function that will be called when the area_entered
signal is emitted. The default value, _on_area_2d_area_entered
works fine in this case. Now click the Connect button to connect the signal, this will add the new function to the jumper script.
From now on, whenever another Area2D enters the Jumper’s Area2D, this function will be called. You can tell the function is connected to a signal by the green “exit” symbol to the left of it. Clicking that shows what signals will call this function.
To test if this actually works, replace the pass keyword with the line below:
print("Got hit!")
By now I’m sure you know exactly what this does: printing text to the console, a programmer’s best friend.
Save the script and open the game scene in 2D mode. Now drag a few instances of the jumper scene from the FileSystem dock onto the viewport, next to the player avatar.
What you just did there is called instantiating scenes, which is using a scene as a blueprint and creating instances of it in another scene. This powerful system allows you to create a scene once, and use it wherever you need.
With the jumpers added, press F5 to play the project. Move the avatar against the jumpers and you should see messages popping up in the console as expected.
Now to make things more interesting, the following should happen when a jumper gets hit:
- Play a shatter sound
- Play a shatter animation
- Destroy the jumper at the end of the animation
How do you do all of this? By scripting of course! Open the jumper scene again and switch to the script editor. To play a sound effect, you need a reference to an AudioStreamPlayer node first. If you remember, jumper happens to have one named ShatterSound.
There are two ways to add a reference to a node in your scripts when working in the built-in script editor: the boring manual method or the awesome speedy method. I’ll start off with the first one, which is adding the following line below extends AnimatedSprite2D
:
@onready var shatter_sound : AudioStreamPlayer2D = $"ShatterSound"
I recommend typing this one out yourself if you haven’t been doing so already, to see how the auto completion helps you by listing all nodes in the scene once you get to the last part.
Here’s a breakdown of this variable declaration:
-
@onready
is an annotation like@export
. This makes it so the variable gets assigned when the node enters the node tree for the first time. This might ring a bell, as that’s the same time as the_ready
function is called you’ve seen before. In essence,@onready
allows you to write one-liners to create and assign a node to a variable. -
var shatter_sound
declares a new variable. -
: AudioStreamPlayer2D
specifies the type of the variable as a AudioStreamPlayer2D. -
= $"ShatterSound"
gets a reference to the node named ShatterSound. The dollar sign ($) is shorthand for theget_node()
function.
Now you know what this line does, it’s time to learn about the more exciting way to add a reference to a node.
Remove the line you just added and start dragging the ShatterSound node from the Scene dock into the script editor. Hold CTRL/CMD before releasing your mouse button to complete the drop. This will add a node reference automatically, how cool is that?
You’re now one of the few people that know about this arcane knowledge, use this power well!
With the reference to the AudioStreamPlayer set up, you can now make it play its sound effect whenever the avatar hits the jumper by replacing the print
call with this:
shatter_sound.play()
This calls the play
function on the AudioStreamPlayer, which will make a nice glass shattering sound. Save the script and run the project to give it a try. Whenever the avatar passes over a jumper, you should hear the sound effect play.
If that’s working as expected, you can move on to playing the shatter animation, which is as simple as adding this line below the line you just added:
animation = "destroy"
Because Jumper is an AnimatedSprite2D node, it has a property called animation, which sets the current animation by its name. If you select the Jumper node and take a look at the bottom of the editor, you’ll see a list of available animations. The default animation is aptly called “default”, while the animation you’re setting here is called “destroy”.
Once again, it’s time to test if this is working as expected, so play the project and try moving into the jumpers again. The jumpers should now shatter when hit!
On to the actual self-destructing, which should happen after the animation finishes playing. There’s way of knowing when the animation ends at the moment, and while you could use some sort of timer, a much more elegant way is by using a signal. AnimationSprite2D has an animation_finished
signal you can connect to, but connecting it via the editor now would result in it being emitted constantly as the default animation is playing on a loop.
The solution is to connect the signal via code after starting the destroy animation, as that guarantees perfect timing and the function is only being called once. To start off, create a new function that you want to be called by the signal to the end of the script:
func _destroy_animation_finished() -> void:
queue_free()
As this function isn’t intended to be used outside of the script, it starts with an underscore. It does one thing: call the queue_free()
method, which queues up the Jumper node for removal from the node tree, effectively destroying it.
To connect the animation_finished
signal to the _destroy_animation_finished
function, add the following line below animation = "destroy"
in the _on_area_2d_area_entered
function:
animation_finished.connect(_destroy_animation_finished)
This connects animation_finished
to _destroy_animation_finished
, just like how you did it before via the editor. The benefit of connecting signals via code is that it makes it easy to “rewire” signals just by editing a few variables and you can connect and disconnect them whenever you please. All of this plays into the modular nature of working with Godot and its nodes.
Play the project once again to test if the jumpers disappear after their animation finishes.
There is a small bug though that might not be obvious straight away: if you hit a jumper, move away and hit it again before the animation finishes, the sound effect will play a second time and you’ll get an error. Here’s what it says:
<code>jumper.gd:9 @ _on_area_2d_area_entered(): Signal 'animation_finished' is already connected to given callable 'AnimatedSprite2D(jumper.gd)::_destroy_animation_finished' in that object.</code>
This error is thrown because there was already a connection made on the animation_finished
signal. In other words, the _on_area_2d_area_entered
function got called twice on the same jumper, which is undesirable. To fix this, you can set a flag on the jumper that states whether the jumper is active or not. When inactive, it shouldn’t react to any collisions.
To implement this, add the following variable below extends AnimatedSprite2D
:
var active : bool = true
This boolean
will act as the flag and it starts as true
, meaning jumper will be active out of the gate. Next, change the code inside the _on_area_2d_area_entered
function as follows to use the flag:
if active: # 1
active = false # 2
shatter_sound.play()
animation = "destroy"
animation_finished.connect(_destroy_animation_finished)
Here’s what this does:
- An if-statement that checks if the jumper is active before doing anything else.
- Set the
active
flag tofalse
. This will prevent the code being ran more than once.
Be sure to save the script once you’re done. With this safety check added, the script is bug-free and you’re ready to make the avatar jump.