Until now, we’ve written input names directly in the main character’s script. But what if we wanted to allow keys rebinding? Or what if we want to duplicate the player character but only change its input actions for a two-player co-op?
In this lesson, we’ll extract the inputs to a resource class. We will then use this class in two resource files to allow two players to control two characters on the same computer.
Remember, a resource class is a script
ending with .gd
. A resource files is an instance
of the class, generally saved to the disk as a file ending with the
.tres
extension (it can have other extensions too).
As you’ll see, we need minimal changes to the character’s code to achieve that.
We’ll work with the two characters isolated in a small scene, but the changes will directly translate to the obstacle course.
Also, please note that we renamed the Godot scene and script to PlayerCharacter in this project.
We didn’t modify the existing code, but we added a pair of hands that turn with the character. We’ll use them when coding character customization later in the series.
Previously, we called Input.get_vector()
with hard-coded
input actions to get our character’s movement direction.
func _physics_process(delta: float) -> void:
var direction := Input.get_vector("move_left", "move_right", "move_up", "move_down")
If something is hard-coded, it means we cannot modify it without modifying the code.
Because of that, every copy of the character will move when pressing one of these four actions.
Open
ObstacleCourse_Part3/TwoPlayersDemo/TwoPlayers.tscn
. It’s a
small demo we prepared for this lesson with two instances of the
PlayerCharacter scene.
Run the scene and move with the WASD keys. The two characters listen to the same input actions so they move simultaneously.
We need different input actions for each character instead.
We will:
Let’s start with the input actions.
Go to Project -> Project Settings… -> Input Map and
add four new input actions for the second player:
move_left_p2
, move_right_p2
,
move_up_p2
, and move_down_p2
.
Assign fitting key mappings to each, ensuring that they don’t overlap
with the existing move_left
, move_right
,
move_up
, and move_down
actions.
You could use the IJKL letters, the arrow keys, or, if you have two gamepads, map the new controls to the second gamepad’s joystick.
To map a key on the keyboard, click the + button to the right of your new input action and select Physical Key.
Then, type the desired key on your keyboard and click Ok.
With the inputs ready, we will use different input actions on each character instance.
Open the PlayerCharacter.gd
script in the
ObstacleCourse_Part3/
folder.
As mentioned earlier, our problem is that the call to
Input.get_vector()
uses hard-coded input actions.
We could define four
variables and export them to customize the input actions on each character instance.export var move_left_action := "move_left"
export var move_right_action := "move_right"
export var move_up_action := "move_up"
export var move_down_action := "move_down"
For our second player, we’d change them to
"move_left_p2"
, "move_right_p2"
and so on.
That would work.
But what if the players want to change the game’s controls? We should save their settings.
We usually advise against thinking about future needs when coding, but the ability to change controls is always important for players, so we want to consider that from the start.
We’ll use a resource to get free support for saving and loading players’ settings.
Right-click on the ObstacleCourse_Part3/
directory and
create a new script named PlayerSettings.gd
.
We start by extending Resource
and giving it a class
name so we can create new resources of that type in the editor.
# Stores a player's input actions and customization options.
class_name PlayerSettings
extends Resource
Then, we define four exported variables, one for each move action we need.
export var move_up_action := "move_up"
export var move_down_action := "move_down"
export var move_left_action := "move_left"
export var move_right_action := "move_right"
They default to the first player’s actions, so we’ll only need to customize them for the second character instance.
Save the script.
We can now create two PlayerSettings
resources and slot
them into each character.
Right-click on the project directory and select New
Resource… to create a new resource of type
PlayerSettings
.
Name the file player_1_settings.tres
. Now, right-click
on the new file and duplicate it. Name the new copy
player_2_settings.tres
.
We need to update the second file use the second player’s input actions.
Double-click the file to open it and the Inspector, add the “_p2” suffix to each input action.
We can now update the characters’ code to use our new resources and implement a two-player co-op.
Open the PlayerCharacter.gd
script, where we start by
defining a new variable to store the player settings.
var settings: PlayerSettings
Because we gave our PlayerSettings
resource a class
name:
Notice that we do not use the export
keyword here. Godot
3 does not support exporting our own resource classes.
We can now use the settings variable in our get_vector()
function call. Replace the line where we got the input direction with
the following.
func _physics_process(delta: float) -> void:
var direction := Input.get_vector(
settings.move_left_action,
settings.move_right_action,
settings.move_up_action,
settings.move_down_action )
We spread the function call over several lines because it is too long to fit on-screen as a single line. We often format long lines like this, with one function argument per line.
Because we use the actions stored in the PlayerSettings
resource, we can now create as many characters as we want, each with
different controls.
All that’s left is to give each character a different
PlayerSettings
resource.
In the TwoPlayers.tscn
scene, open the script attached
to the TwoPlayers node. It already has two variables to get the
two characters.
onready var player_1 := $PlayerCharacter
onready var player_2 := $PlayerCharacter2
In the _ready()
function, we preload our two
PlayerSettings
resources and assign each to one character’s
settings
variable.
func _ready() -> void:
= preload("../player_1_settings.tres")
player_1.settings = preload("../player_2_settings.tres") player_2.settings
You can now run the scene to see each character move separately from the other.
You turned a single-player game into a two-player co-op with minor code adjustments!
We abstracted our hard-coded input actions and made our
code more flexible. That is what we achieve by moving some code to
a data structure like our PlayerSettings
resource.
Importantly, we only did that when we needed to: we wanted to offer a two-player co-op in our game. We did not make our code more complex until it became necessary.
In the next lesson, we’ll expand our PlayerSettings
resource and add support for customizing the characters’ look.