05.character-customization

Character customization

We can now have two players in our game, but they look the same. Wouldn’t it be nice if each player could customize their look a bit?

We will add that feature in the following lessons.

First, we need something to customize. We added some hands to the player characters and put several hand textures in the project.

There are two parts to character customization:

  1. Have the character’s script read the resource to reflect changes in the resource.
  2. Have a way for the player to change and save the resource from the running game.

In this lesson, we’ll be working on the first part: adding a hand_texture property to our PlayerSettings resource and updating the hands in-game when the texture changes in the resource.

In the following lesson, we will connect the resource to a menu and make it so changing menu options updates the character in the obstacle course.

This will teach you how to react to changes in a resource’s data and synchronize changes in separate game screens.

For now, let’s focus on making the character’s hands customizable.

We need to:

  1. Add a hand_texture property to the PlayerSettings resource.
  2. Emit a signal whenever the hand_texture changes.
  3. Update the character’s hands sprite according to the hand_texture property.

Let’s get started.

Storing a hand texture in the player settings resource

Open the PlayerSettings.gd script where we add a new exported variable.

Notice how we set the type of the hand_texture variable to Texture. When we use the Quick Load feature in the Inspector later, Godot will only list image textures.

export var hand_texture: Texture = null setget set_hand_texture

func set_hand_texture(new_hand_texture: Texture) -> void:
    hand_texture = new_hand_texture
    # This Resource function is equivalent to calling emit_signal("changed").
    emit_changed()

There are two ways to react to changes in a resource:

  1. The Resource class has a signal named "changed". Every node that needs to react to a change in a resource can connect to it.
  2. You can also use a setter function on the node that stores the resource. Every time you access the resource through it, you will trigger the setter function.

We’ll use both in this series.

We’ll need the setter function to give each character the correct look at the start of the game, and the signal will synchronize changes between the menu and the level.

Updating the character’s look based on the resource

Open the PlayerCharacter.gd script.

We need to add a function that updates the hands’ textures. We already defined onready variables for each hand sprite.

onready var hand_sprite_left := $HandsPivot/LeftHand
onready var hand_sprite_right := $HandsPivot/RightHand

We define a new function to update the sprites’ textures from the PlayerSettings resource stored in the settings property.

func update_skin() -> void:
    # We need to update the hands according to the settings' hands_texture
    # property.
    hand_sprite_left.texture = settings.hand_texture
    hand_sprite_right.texture = settings.hand_texture

We need to call that function in two cases:

  1. Immediately after assigning a PlayerSettings resource to the character.
  2. Whenever the resource emits its "changed" signal. Otherwise, changes in the menu will not synchronize with the characters in the game level when we put everything together.

We can do both by adding a setter function to the settings property. When we assign a new PlayerSettings, the setter will trigger.

Update the line where we defined the settings variable like so.

var settings: PlayerSettings setget set_settings

We then write the setter function where we connect to the settings’ "changed" signal.

We also call update_skin() to update the hands the first time we load the settings.

func set_settings(new_settings: PlayerSettings) -> void:
    if new_settings == settings:
        return

    settings = new_settings
    # We need to connect to this signal to synchronize changes to the character
    # between the menu and the game level.
    new_settings.connect("changed", self, "update_skin")
    update_skin()

Changing a setting through the character like player.settings.hand_texture = ... will trigger a call to set_settings() every time. That’s why the first two lines check if the settings resource didn’t change.

In that case, we’ll have already connected the changed signal, and we would get an error if we tried to do that again.

Testing that the code works

At this stage, we can test that the hand textures get applied from the resource in the TwoPlayers scene we used previously.

First, we need to assign different hand textures to our two PlayerSettings resource files, player_1_settings.tres, and player_2_settings.tres.

Open each file and assign them one of the hands textures you’ll find in the assets/ directory.

Open the scene TwoPlayers.tscn once again and run it. You should see the hands display the textures you put in the resources.

Open the TwoPlayers.gd script and add a line of code to replace the hands in one of the settings resources.

func _ready() -> void:
    # ...
    player_1.settings.hand_texture = preload("../assets/hand_red_open.png")

Changing the hand_texture property should reflect on the character if our resource code works.

Rerun the scene, and you should see the players’ hands change according to this last line of code.

You now have the foundations of character customization. All we need is a menu in which the player can choose hand options.

We’ll work on that in the next lesson.

Practice: Inventory items

Open the practice Inventory items.

In this practice, you will create a couple of resource files and give them to an inventory to display.