12

Introduction to GDScript in Godot 4 Part 1 [FREE]

 1 year ago
source link: https://www.kodeco.com/38442713-introduction-to-gdscript-in-godot-4-part-1
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Home

Introduction to GDScript in Godot 4 Part 1 Home Introduction to GDScript in Godot 4 Part 1

Introduction to GDScript in Godot 4 Part 1

Apr 14 2023, GDScript, Godot 4, Godot

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.

Scripting is a fundamental skill for any game developer. You can implement just about any game mechanic you can think of by writing one or more scripts that tells the game engine what to do. From making a character move to creating enemy AI that taunts you from behind a cover, it’s all possible by writing some code.

Note: This tutorial assumes you’re familiar with the basics of nodes and scenes. If you’ve never used Godot, you may want to follow along the Godot 4: Getting Started tutorial to find your away around the editor.

In this tutorial, you’ll learn the basics of GDScript 2.0, the main programming language of Godot 4. You’re going to create a simple game inspired by Winterbells, a classic Flash game from 2006 by Ferry Halim. The goal of this game is to jump higher and higher by hitting the bells with your white rabbit friend.

White rabbit in snowfield, with white bells above it

The game you’ll be making involves hitting energy orbs with a robot to boost yourself up as high as you can.

GIF og robot jumping up and down

There’s nothing better than creating a small game to get a feel for a programming language! The tutorial is split into two parts, in this first part you’ll learn about:

  • The fundamentals of GDScript and the built-in script editor.
  • Creating and using variables to change and balance the gameplay.
  • Using Godot’s powerful signal system to pass values from one node to another.
  • Polling for player input and using it to change the game state.
  • Scripting jumping and gravity.

Getting Started

This tutorial uses the most recent version of Godot 4, which you can download here.

Download the projects by clicking Download Materials at the top or bottom of the tutorial and extract the zip file to a location of your choosing. After extracting the zip, you’ll see two folders: starter and final. The starter project is what you’ll be using to build upon.
To import the project, open Godot and click the Import button in the Project Manager. Now either paste the starter folder path in the text field, or click the Browse button to navigate to the folder.

Browse button is highlighted

Next, click the Import & Edit button to load the project in Godot’s editor. Make sure that the 2D screen is active and the game scene is opened. If this is not the case, open it by double-clicking game.tscn in the scenes folder.

ClickGameScene.png

Before taking a look around the different scenes that I set up, try to run the game by pressing F5 on your keyboard or clicking the Run Project button at the top right. You should see a blue robot standing at the bottom of the screen while some music plays in the background.

game.tscn is highlighted

If that’s working as expected, it’s time to give a quick tour around the starter project.

Project Tour

I’ve prepared some basic scenes and assets so you can focus primarily on scripting. To start off, take a look at the FileSystem dock and the folders and files inside:

  • music: A single audio file that’s used for the background music.
  • scenes: All scenes used throughout the project. More on those below.
  • sounds: A glass-shattering sound effect.
  • sprites: This contains the background graphics and the sprite sheets for the robot and jumper orbs.
  • credits.txt: A list of sources and attributions for the included assets.

Next up is a brief overview of the scenes.

Game Scene

The first and most important one is game. This the main scene and it contains the following nodes:

  • A Camera2D with a repeating background Sprite2D as a child
  • A ground Sprite2D node
  • An instance of the player_avatar scene named PlayerAvatar
  • An AudioStreamPlayer that automatically plays and loops a music track
  • An instance of the ui scene named UI

List of nodes, top one reads Game

Note: If you’re not sure what a specific node does, you can right-click the node and select Open Documentation in the context menu. This opens the documentation for that node class in the Script screen.

At the end of this tutorial, this will be the scene where all the action happens as the avatar will move about and hit energy orbs called “jumpers” to launch itself upwards. Speaking of jumpers, double-click the jumper.tscn scene file to take a look what’s inside.

Jumper Scene

In this scene, the root node Jumper is an AnimatedSprite2D which holds its animations. The Area2D node and its CollisionShape2D are used to detect collisions with the player avatar. Finally, ShatterSound is an AudioStreamPlayer2D that references the glass shatter sound effect found in the sounds folder.

List of nodes, top one reads Jumper

Now open the player_avatar scene and take a peek at its nodes.

Player Avatar Scene

At its root is a Node2D node which is used to group its children together: an AnimatedSprite2D with several animations and an Area2D used for detecting collisions. Simple but effective!

List of nodes, top one reads PlayerAvatar

The final scene to take a look at is the UI scene, which you can find in ui.tscn.

UI Scene

The UI scene contains the user interface for the game, which is a CanvasLayer that has two text labels to keep track of the height and score. The MarginContainer and HBoxContainer nodes align these labels to the bottom of the screen with some margin from the borders of the screen.

List of nodes, top one reads UI

That concludes the project tour. Don’t worry if you’re not sure what the scenes are supposed to do just yet, you’ll get familiar with the smaller details while adding logic via scripts and playtesting. With the bird’s eye view out of the way, it’s time to start your GDScript adventures!

Why GDScript?

GDScript is a high-level, object-oriented programming language that was built specifically for use with the Godot game engine. While other languages like C# and C++ can also be used with Godot, GDScript is often the language of choice due to the wealth of examples, questions, and tutorials available online. It’s also the first-class citizen in Godot as it’s fully supported by the built-in script editor.

If you’re familiar with Python, then you’ll find GDScript’s syntax to be intuitive and easy to learn. Even with minimal knowledge of other programming languages, you’ll be comfortable with the language with a few days of practice.

In this tutorial, all scripting will be done using Godot’s built-in script editor, so you won’t need anything else to get started. If you prefer to write your code in a different window or IDE, you can use an external editor like Visual Studio Code, Vim or Sublime Text. Personally, I recommend using Visual Studio Code with the godot-vscode-plugin, even though some functions like debugging are not yet compatible with Godot 4.

As a final recommendation, here are two editor settings for the built-in script editor you might want to change via Editor ▸ Editor Settings in the top menu:

  • Disable Text Editor ▸ Behavior ▸ Smooth Scrolling. This is a personal preference, but I find smooth scrolling slows down the speed to navigate a script by scrolling.
  • Enable Text Editor ▸ Completion ▸ Add Type Hints. This adds static typing to variables and functions generated by Godot, which improves performance, autocompletion and decreases the likelihood of bugs. I’ll explain why this is the case later in the tutorial.

Scripting Basics

Let’s put the theory and background information aside for now and focus on what you came here to do: write scripts!

Creating New Scripts

To start off, create a new folder at the root of the project’s filesystem by right-clicking res:// and selecting New ▸ Folder.

New and Folder are highlighted

This folder will hold all the scripts, so name it scripts. The first script you’re going to create will be for the player avatar, so open the player_avatar scene. Now select the root PlayerAvatar node and press the attach script button that looks like a scroll with a green plus sign.

White scroll with green plus icon is highlighted

This opens the Attach Node Script window, which you can use to set the initial settings for the script.

Window with several fields, path is highlighted

Here’s a rundown of these settings:

  • Language: Godot supports other languages besides GDScript, which you can choose here. The default build of Godot only comes with GDScript, while the .NET build also has C# included.
  • Inherits: The Node class to inherit the script from. This gives your script access to variables and functions unique to that node type.
  • Class Name: The class name of the script. This only applies to different languages than GDScript. For C# this would be PlayerAvatar for example.
  • Template: What script template to apply. This changes how the new script will be structured, with or without comments for example. Godot comes with a Default and an Empty template out of the box. You can also create your own script templates.
  • Built-in Script: By default, Godot saves scripts as files. When this is checked, the script is built into the scene itself. This can be useful in rare cases, but having external scripts is more intuitive and better for source control.
  • Path: Project path where Godot will create the new script.

Notice that the path points to the scenes folder by default. Make sure to replace “scenes” with “scripts” in the path, either by hand or by clicking the folder icon and navigating to the scripts folder. The default filename “player_avatar.gd” is fine as Godot uses snake_case for its files and folders. With the correct path set, click the Create button to create the script.

Path and create button

This will automatically open the script editor with the new script opened. Press CTRL/CMD + S to save the scene and the script and then take a look around the different parts of the editor.

Script editor overview

Below is a summary of the different sections:

  1. The toolbar located at the top of the screen contains a menu on the left with typical commands found in text editors plus some useful scripting options like changing the syntax highlighting. On the right side are two buttons; Online Docs which opens Godot’s online documentation in your default web browser, and Search Help which opens a search window that allows you to search and find info about the built-in classes.
  2. Opened scripts are displayed at the top, while the bottom list shows the functions of the script you’re editing.
  3. The text editor displays the content of the script, and is the main workspace you will use throughout this tutorial. At the top right is a minimap, which makes it easy to quickly navigate larger scripts.
  4. The panel at the bottom has an arrow button to hide the two lists at the left, and shows any warnings and errors at the right, along with the line number and column the caret is placed on.

Now you’re a bit more familiar with the interface, it’s time to look at the script itself.

Script Anatomy

Godot’s default script template makes for a great starting point to build upon further. It even contains some useful comments! I’ll bridge the gap to make it more obvious what the unexplained parts do.
The first line reads extends Node2D, which means this script inherits from the Node2D class. The extends keyword allows your script to use the variables and functions from the inherited class.

Below that are two of the most used functions in Godot: _ready and _process. As the comments suggest, _ready is called once at the start of the node’s lifespan, while _process is called every frame. These are overrides for the virtual functions of the same name in the Node class, the class all other node-based classes inherit from. You can see which functions override their base class by little blue arrow in front of the line number.

Blue arrows pointing at code

Both functions start with an underscore to mark them as private, meaning you shouldn’t call these functions from another script. Godot doesn’t enforce this in any way though, it’s a convention to keep your code clean.
The pass keyword in both functions is a placeholder, it doesn’t do anything except for making sure there’s something indented inside the function so Godot doesn’t throw errors. Try removing the whole pass line in the _ready function to see for yourself.

Function with pass keyword removed

This will show an error at the bottom saying: “Expected indented block after function declaration”. GDScript uses indentation with tabs (or spaces) to specify what code belongs to what function or statement. Now change the _ready function as follows:

func _ready() -> void:
    print("Player avatar has loaded!")

The print function prints arguments of any type to the output console. Save the script and run the game by pressing F5 to see it in action.
If everything went well, you should now see the text being displayed at the bottom. If you don’t see the console, click the Output button to toggle it on.

Console

With the fundamentals covered, you’re now ready to tap into the power of GDScript!

Variables

The player avatar needs to move left and right towards the position of the cursor. To do this, you’ll need keep track of the avatar’s current velocity, its movement speed and the mouse position. You can store these values as variables in your script.
You can define variables in Godot using the var keyword. To start with, define a new variable named velocity that can be accessed throughout the script by adding this line below extends Node2D:

var velocity : Vector2 = Vector2.ZERO

There are a few things happening here:

  • var velocity defines a new variable and names it velocity. This by itself is enough to create a new variable if you don’t care about the type or default value.
  • : Vector2 sets the type of variable, in this case a Vector2, which has a x and y coordinate to hold the horizontal and vertical velocity respectively.
  • = Vector2.ZERO gives the variable a default value. Vector.ZERO is a constant with a value of 0 for both x and y.

Providing a type for variables is optional in Godot, this called dynamic typing. Setting the type while defining new variables like you just did is called static typing. You should prefer static typing over dynamic typing for several reasons:

  • It makes Godot’s compiler understand what you’re tying to do better, which results in better autocompletion in the script editor.
  • The code will be more readable as it’s clear what value a variable can hold. This in turn makes your code less error-prone.
  • You get a huge performance boost for free, as statically typed code runs between 25 and 100 percent faster than its dynamic counterpart.

All variables throughout this tutorial will use static typing to make it easier to follow along and learn the different types.

Next on the list is the movement speed, which is the speed at which the avatar will move horizontally in pixels per second. Unlike the velocity, you should be able to tweak its value via the inspector. Add the following line below extends Node2D to add a new move_speed variable:

@export var move_speed : float = 600.0

By adding the @export annotation before the variable declaration, it exposes the variable to the editor. This is extremely useful for quickly changing values to improve the balance or feel of your game. The move_speed variable is a float, which is short for floating-point number. It can store numerical values with decimal digits and is often used for positional calculations.

Save the script player_avatar script, make sure you’ve got the PlayerAvatar node selected in the Scene dock and take a look at the Inspector on the right. You should now see a new property named Move Speed with the a value of 600.

Move speed 600

I found 600 pixels per second to be a sensible speed, but you can change this value once the avatar can move to make the game more easy or difficult. Next on the list is getting the player to move the mouse cursor.
For this to work, you’ll need to get the position of the cursor, check whether it’s left or right from the avatar and then adjust the velocity accordingly every frame. To get the position of the mouse cursor, replace the pass keyword in the _process function with this:

var mouse_pos : Vector2 = get_global_mouse_position()

This grabs the position of the cursor via the built-in get_global_mouse_position() function, which returns a Vector2. To test if this is working, you can add the following print call below the line you just added:

print(mouse_pos)

This will print the cursor position every frame. Go ahead and give it a go by pressing F5 on your keyboard to run the scene. Move your mouse around a bit and you’ll see the positions appearing in the console.

647, 275 in console

Now you know that’s working, remove the print line you added for testing purposes and replace it with the following to make the avatar move to the cursor:

Note: If you get an error saying “Mixed use of tabs and spaces for indentation” while copying any of the code snippets to your scripts, press CTRL/CMD + I on your keyboard while in the script editor. This will automatically fix any incorrect indentation. The reason you might get this error when copying snippets from a web browser is because they tend to convert tabs to spaces, while Godot expects tabs.
    var x_distance_to_cursor = mouse_pos.x - global_position.x # 1
    var cursor_right : bool = x_distance_to_cursor > 0 # 2

    if cursor_right: # 3
        velocity.x = move_speed
    else:
        velocity.x = -move_speed

    global_position += velocity * delta # 4
  1. Calculate the distance to the cursor by subtracting the avatar’s X position from the cursor’s X position. For example, if the avatar’s position is (X:10, Y:0) and the cursor’s position is (X:50, Y:0), the distance would be 40 pixels. Store this value in a new variable named x_distance_to_cursor.
  2. Check if the cursor is right or left from the avatar by getting the distance calculated above and seeing if it’s a positive or negative value. A positive value means the cursor is to the right. Store this in a boolean variable named cursor_right. A boolean has two possible values: true or false.
  3. This is an if-statement that changes the X-value of the velocity to the movement speed if the cursor is to the right, or to the negative movement speed if the cursor is to the left. If-statements in GDScript follow the same rules as functions, so anything that applies to the statement should be indented with a tab and the statements end with a colon.
  4. The global_position variable is part of the Node2D class and stores the node’s position as a Vector2. The code applies the velocity to the avatar’s global position and multiplies it with delta to make it framerate-independent.

That was a lot to take in at once! Your efforts are rewarded though, as you’ll see when running the game by pressing F5.

GIF of robot moving left and right

The avatar now smoothly follows your cursor! You might have noticed a little quirk though: when you move the cursor outside of the borders of the window, the avatar follows and disappears off-screen. To fix this, you can get the rectangle that makes up the viewport, add a border to the sides and limit the avatar’s horizontal movement inside.

Window with red borders left and right

The illustration above shows the concept of a virtual border that the avatar can’t pass with a red color. To implement this in code, you need the size of the viewport, the border width and the avatar’s position. To get the viewport’s size, add this line to the _process function, below mouse_pos:

var viewport_size : Vector2 = get_viewport_rect().size

This line gets the viewport’s rectangle, which is the position and size of the game window, and extracts just the size from it. This value then gets stored in viewport_size.
For the border, you’re going to add another exported variable, so you can tweak its value in the Inspector. To do that, add the following to the top of the script, right below the move_speed variable declaration:

@export var viewport_border : float = 80.0

This is the width of the border in pixels. To actually limit the avatar’s movement, adjust the velocity-changing if-statement in _process as follows:

    velocity.x = 0 # 1

    if cursor_right:
        if global_position.x < viewport_size.x - viewport_border: # 2
            velocity.x = move_speed
    else:
        if global_position.x > viewport_border: # 3
            velocity.x = -move_speed

Here’s what this does:

  1. Reset the horizontal velocity so the avatar doesn’t move if there’s no horizontal velocity applied further down.
  2. If the avatar wants to move to the right, only allow it if its position hasn’t passed the border on the right. The border calculation takes the horizontal size of the window and subtracts the size of the border from it.
  3. This does the same as above, but for moving to the left. Instead of taking the size of the viewport into account, you simply use the border size as the leftmost viewport position is 0.

With this added, run the game again and move your cursor outside of the window. The avatar will now wait patiently at one of the borders.

GIF of robot moving left and right

With the horizontal movement done, you’re now ready to explore Godot’s signal system.

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.

Jumper and Path are highlighted

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.

Node tab highlighted

You’ll now see a list of signals the selected node supports.

Signals list

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.

Signal connect window

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.

Green symbol before function code

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.

Area2D, area_entered, Jumper

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.

Dragging jumper scene

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.

Got Hit in console

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.

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.

GIF of autocompletion

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 the get_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?

Drag node to script editor with instructions to hold CTRL or CMD before releasing

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”.

default and destroy animations

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!

GIF of robot hitting orbs, which shatter

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:

  1. An if-statement that checks if the jumper is active before doing anything else.
  2. Set the active flag to false. 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.

Jump Using Player Input

The goal of the game will be flying from jumper to jumper, so you need a way of propelling the avatar upwards. The first jump will be performed with a mouse click, which means you need some way of reading the player’s input and act upon it.

While you can hard code mouse button polling, Godot has a useful input system that uses input actions to handle player input. Take a character moving forward for example, without input actions you’d have something like this:

if Input.is_physical_key_pressed(KEY_W) || Input.is_physical_key_pressed(KEY_UP) || Input.get_joy_axis(0, JOY_AXIS_LEFT_Y) < 0:
    move_forward()

With input actions, you can assign the W key, arrow up key and joystick up to an action and check that:

if Input.is_action_pressed("move_forward"):
    move_forward()

That’s a whole lot nicer, isn’t it? This allows you to reassign keys and buttons tied to an action from the editor or even during gameplay. You can also assign multiple inputs to the same action.
To add an action yourself, first select Project ▸ Project Settings… in the top menu to open the project settings.

Project settings

Now open the Input Map by clicking the corresponding tab at the top.

Input map

This where you can define new actions. Add a jump action by clicking the Add New Action field, typing “jump” followed by clicking the Add button to its right.

GIF of adding new action

With the jump action added, you can add input events to it by clicking the plus button next to its name. This opens the Event Configuration window. In here, select Mouse Buttons ▸ Left Mouse Button and click the OK button at the bottom to confirm.

Left mouse button, OK

Back in the Input Map, you can now see the jump action is tied to the left mouse button. Sweet!

Left mouse button, all devices

Go ahead and close the Project Settings window. Now open the player_avatar script again as it needs some additions to make the avatar jump.
To start with, add a new variable for the jump speed to the top of the script, below viewport_border:

@export var jump_speed : float = 2500.0

This is the vertical speed of the avatar when jumping, whenever it jumps from the ground or when it hits a jumper, this is the amount of pixels per second it will travel at. To use it, add the following function below the _process function:

func _jump() -> void:
    velocity.y = -jump_speed

This _jump function will change the Y velocity of the avatar to the value of the jump speed you just added, but negative. The value has to be negated as positive Y means down in Godot, while negative Y means up.
To call _jump, add this function above the function you just added:

func _process_input() -> void:
    if Input.is_action_just_pressed("jump"):
        _jump()

This function calls upon the Input class to check whether the jump action was just pressed and then calls the _jump() function if that’s the case. The is_action_just_pressed method checks for a single press, not a continuous hold, unlike is_action_pressed. To finish the function chain, add this line at the end of the _process function:

_process_input()

This will call the _process_input function every frame, which in turn checks for any inputs. While you could have put everything in _process, that makes it harder to track down the pieces of code and change them later on. Writing clean code is a skill in itself. :]
Time for another test drive, press F5 to play the project and try to make the player jump.

Robot jumping up and disappearing

I’d say that is a successful jump. It’s more like a rocket launch at the moment, but that’s fixable!
The reason why the avatar leaves the screen and never returns is because there’s no gravity applied to it. What goes up must come down after all. Add a new variable below jump_speed named gravity:

@export var gravity : float = 4000.0

This is a downward force that will be applied to the avatar. To apply it every frame, add this line to _process, right below velocity.x = 0:

velocity.y += delta * gravity

This will push the avatar down continuously, simulating gravity. Run the project again to see if this improves the jumping. You will notice the avatar falls down straight away and keeps falling, unless you click now and then to bounce it back up.

Robot constantly jumping and falling

This concludes the first part of the tutorial, what a cliffhanger! Or is it cliff-faller in this case? :]
Don’t worry, in the second part of this tutorial you’ll help the poor avatar out so it doesn’t have to jump all the time to prevent falling the endless abyss.

Where to Go From Here?

Thanks for reading this first part of the tutorial! You can download the project files using the link at the top and bottom of this page.
In the second part of this tutorial, you’ll learn how to use a finite state machine to neatly manage the avatar’s state, instantiate scenes using code, make the camera follow the avatar and much more! At the end, you’ll have a small game to build upon further.

We hope you enjoyed this first part of the tutorial. Leave a comment below or join the forum discussion to share your thoughts and ask any questions you might have!

Contributors

Over 300 content creators. Join our team.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK