In this tutorial, we’ll set up our player node to use Godot physics engine fo movement and write a simple script to use the keyboard or the joypad for player input.
To start, let’s open the SimpleRPG project and pick up where we left off in the last tutorial.
Reorganizing the project
Before moving on, I would like to reorganize the project folder a bit.
First of all, let’s delete two files automatically added at project creation but that we don’t need: holding down shift, select the icon.png and default_env.tres files, then right-click, choose Delete and confirm removal.
To keep the project tidy, we will organize the project files in folders; it will be useful later because we’re going to add new files that would mix with existing ones. Right click on res://, choose New Folder… and create a folder named Scenes. Then drag the Main.tsch scene into this folder.
Repeat the previous operations and create a folder called Entities. For now, the only entity we have is the player. Then, under Entities, create a folder named Player, and drag the player.png file into it. The final result will be this:
Another thing we can do is rename the root node to Root. To do this, select it and then click again on the name to enter editing mode, or right-click on it and press Rename. After editing, our scene will have this structure:
Press Play to check that everything is all right. The project should work properly because Godot will automatically update all references to the files we have moved.
Setting up player node for movement
To move the player, we will use the KinematicBody2D node type. KinematicBody2D nodes are special types of physical bodies that are meant to be user-controlled. They are part of Godot’s physics engine, which we will discuss in depth in the next tutorial. For now, the only important thing to know is that KinematicBody2D has an API for moving objects.
First, add KinematicBody2D as a child of Root:
Now, move Sprite into it and rename KinematicBody2D to Player:
An important thing to know is that the position of a node is always relative to its parent node. Now that we’ve moved Sprite inside Player, its position (160,90) is relative to that of the Player node. Since Player will be the object we move (and not just Sprite), we will have to set the position of Sprite to (0,0), so that it is in the same position as the Player node. Now we can move Player to the center of the screen at the position (160,90).
If we look at the Player node, we see that there is a yellow triangle at his side. This warning tells us that we have not defined a collision shape for the KinematicBody2D. For now let’s ignore it, we’ll take this up again in the next tutorial when we talk about collisions.
The most flexible way to handle player input is using Godot’s Input Mapping. You use Input Mapping by creating named input actions, to which you can assign any number of input events, such as keypresses or mouse clicks. A new Godot project includes some default actions already defined. To see them, and to add your own, open Project → Project Settings and select the InputMap tab:
The ui_left, ui_right, ui_up and ui_down actions are the ones we will use to move our character. By default, these actions are associated with the keyboard arrow keys and the joypad D-Pad events. We want to add the ability to move the character using the joypad analog stick as well. To do this, for each of these actions we will add the relative joypad axis event.
Click on the Plus button to the right of ui_left and choose Joy Axis. You will be shown a window to choose the device and the axis to be added: choose Device 0 and Axis 0 – (Left Stick Left) and press Add.
To the right of ui_left, you’ll see a numeric value, currently set to 0.5: it’s the deadzone. The deadzone corresponds to how much a joypad stick (or trigger) needs to be moved before the action is considered pressed. Reduce this value to 0.1, so you can capture even small movements of the analog pad.
Repeat these operations for the other input actions. The final result will be this:
The movement script
Right-click on Player and select Attach script. Make sure that the language chosen is GDScript and choose the Entities/Player folder we created earlier as Path:
Now we are in the Script workspace. Replace the default code with this:
extends KinematicBody2D # Player movement speed export var speed = 75 func _physics_process(delta): # Get player input var direction: Vector2 direction.x = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left") direction.y = Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up") # If input is digital, normalize it for diagonal movement if abs(direction.x) == 1 and abs(direction.y) == 1: direction = direction.normalized() # Apply movement var movement = speed * direction * delta move_and_collide(movement)
Before analyzing this script, if you are not familiar with programming (and in particular with object-oriented programming), I’ll give you some definitions of terms you’ll see often:
- Variable: it’s a container for storing data.
- Function: it’s a block of code which only runs when it is called inside other code. You can pass data, known as parameters, to a function, and it can return data as a result.
- Class: it’s a user-defined data type that includes variables (also called properties) and functions (also called methods). A class serves as a template for creating objects.
- Instance: it’s a specific object created from a particular class.
- Inheritance: it’s the mechanism by which a class acquires properties and methods of another class and extends them.
- Method overriding: it’s a feature that allows a child class to provide a specific implementation of a function that is already provided by its parent class.
As we saw in our first Godot project, in GDScript each script file represents a class. When you attach a script to a node, you are creating an instance of that class.
Now let’s take a look at the script line by line. If reading the explanation you have any doubts, you can go to the GDScript basics page on the Godot website to learn more about the features of the language.
The extends keyword is used for inheritance and defines what class to extend with the current class. Using this line of code, in our class we’ll have all the KinematicBody2D properties and methods available.
# Player movement speed
In GDScript, anything from a # to the end of the line is ignored and is considered a comment.
export var speed = 75
In this line, we use the var keyword to create a new variable, called speed, that will store the player movement speed (expressed in pixels per second). We are giving it a default value of 75 using the assignment operator (=).
The export keyword makes the variable visible and modifiable in the editor (you’ll see it in the Inspector). If you change the variable in the editor, its new value is saved in the scene file.
The func keyword is used to define a function. The _physics_process() function is inherited from the Node class. In our script we are overriding it to process player movement.
This function is called during the physics processing step of the main loop. Any code that needs to access a physical body’s properties or use a physicalbody’s method should be run in this function, which is called before each physics step at a constant rate (60 times per second by default). Since the rate is constant, the delta parameter of the function, which represents the time elapsed since the last physical processing, should be constant too.
GDScript uses indentation to indicate a block of code. Each line following the declaration of the _physics_process() function is indented, so they are all part of the code block executed when the function is called.
var direction: Vector2
This line of code creates a new variable named direction of Vector2 type. Vector2 is a 2-element structure used to represent positions in 2D space. You can access its values using the x and y properties. We will use the direction variable to store the direction in which the player wants to move.
direction.x = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left") direction.y = Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up")
This code is used to calculate the direction of movement based on user input. Looking at these two lines, we learn a few things:
- the dot operator (.) is used to access the properties of a variable (for example, direction.x)
- there is a core Godot’s object, named Input, that deals with input from keyboard, mouse and joypads.
- Input has a method called get_action_strength()
- the dot operator is also used to call object methods (for example, Input.get_action_strength(“ui_right”) )
- methods are called passing their arguments inside brackets (separated by commas)
- the argument of get_action_strength() is a string, i.e. a sequence of characters. In GDScript, a string is enclosed by double quotes.
What does this code do?
The get_action_strength() function returns a value between 0 and 1, representing the intensity of the input action passed as the argument. In a joypad, for example, the further away the stick is from the dead zone, the closer the value will be to 1. If the action is mapped to a digital control like a key or a button, the value returned will be 0 (not pressed) or 1 (pressed).
For the x component of the direction variable, we call this function for the two actions ui_right and ui_left and calculate the difference of the two values. The result we get is a range that goes from -1 (when the stick is on the left) to 1 (when it’s on the right).
We do the same thing for ui_down e ui_up actions, getting a range that goes from -1 (when the stick is up) to 1 (when it’s down).
if abs(direction.x) == 1 and abs(direction.y) == 1: direction = direction.normalized()
This code is used to correct a problem that occurs when we use the keyboard or the D-Pad to move diagonally.
When we press a single key, we get a direction vector of length 1. For example, for right movement, direction has a value of (1,0). But if we press Right and Up simultaneously, the direction vector will have components (1,-1), and its length will be about 1.4 (just apply the Pythagorean theorem). If we used this variable directly to calculate movement, diagonal ones would be 40% faster! So, we should perform a so-called normalization operation, that is, compute a vector of length 1 that points in the same direction of the original vector.
On the contrary, when we use the analog stick, this problem does not occur. So we have to check if we have a non-normalized diagonal movement, and in that case normalize it.
The if statement is used to check if a condition is true or not, and based on that, decide whether to execute a code block. The condition we check is whether the absolute value (calculated with the abs() function) of both the x and y components of direction is equal to 1. If it is true, it means that the player input is a non-normalized diagonal movement. In this case, the length of the vector will be different from 1, so we will simply use the normalized() function of Vector2, which will return a normalized vector with the same direction.
var movement = speed * direction * delta
Now that we are sure we have a valid direction vector, we can calculate the actual movement that our character must perform. We simply calculate it as speed multiplied direction (which gives us the velocity) multiplied by the elapsed time delta. We will store the result in a new variable called movement.
This function moves the character along the vector movement. We will see in the next tutorial that if there are other CollisionObject2D nodes, the character will stop if it collides with them.
Testing the game
Run the game. You can move the character by pressing the arrow keys. If you have a joypad, you can test the movement with both the D-Pad and the analog stick.
In this tutorial we have learned to get player input and use it to move the character on the screen. Probably, we could have achieved the same result more easily, if we had directly modified the player’s Position property. But making use of the “right method” right away, we have laid the foundation for understanding Godot’s physics engine and collisions, a topic we will discuss in the next post.