Sound and Colors

Settings singleton

First, we’ll add a new script by choosing File -> New Script in the script tab. Name the script

In this script we’ll place the configuration settings for the game.

var enable_sound = true
var enable_music = true

var circles_per_level = 5

Add the script as an autoload by opening “Project Settings” and selecting the “Autoloads” tab. Click the folder to load the script and then click “Add”.

alt alt

Adding sound

To play sounds, we’ll be adding several AudioStreamPlayer nodes to different scenes.

  • First, add one to the Main scene and name it “Music”. For its Stream property use res://assets/audio/Music_Light-Puzzles.ogg.

  • To the Screens scene, add another called “Click”, which will play when we tap buttons. Use menu_click.wav from the assets folder.

  • In the Circle scene, add an audio player named “Beep” and use the 89.ogg sound file.

  • Finally, on the Jumper, we need two sound effects: “Jump” and “Capture”. Use 70.ogg and 88.ogg, respectively.

Now to play the sounds, we can call their play() methods. Add this to Main.new_game():

if settings.enable_music:

and Main._on_Jumper_died():

if settings.enable_music:

In add this to _on_button_pressed():

if settings.enable_sound:

On the circle, we want to play the Beep sound when a limited circle completes a full orbit. This is in check_orbits():

current_orbits -= 1
if settings.enable_sound:

And in, we add the sounds like so:

func jump():
    target = null
    velocity = transform.x * jump_speed
    if settings.enable_sound:

func _on_Jumper_area_entered(area):
    target = area
    velocity = Vector2.ZERO
    emit_signal("captured", area)
    if settings.enable_sound:

Run the game and test that you hear all the sounds as expected.

Sound settings

Now that we have sound working, we can connect the buttons on the “Settings” screen that can toggle sound and music.

The button appearance needs to be changed to match the current on/off state of the property. We’ll load the textures first so that we can assign them as needed:

var sound_buttons = {true: preload("res://assets/images/buttons/audioOn.png"),
                    false: preload("res://assets/images/buttons/audioOff.png")}
var music_buttons = {true: preload("res://assets/images/buttons/musicOn.png"),
                    false: preload("res://assets/images/buttons/musicOff.png")}

Right now, we’re not handling the buttons when they’re pressed. The issue is that we’re currently passing the button’s name, which won’t let us change its texture. Instead, we’re going to refactor register_buttons() to pass a reference to the button itself:

button.connect("pressed", self, "_on_button_pressed", [button])

Then we can update _on_button_pressed() like so:

func _on_button_pressed(button):
    if settings.enable_sound:
            yield(get_tree().create_timer(0.5), "timeout")
            settings.enable_sound = !settings.enable_sound
            button.texture_normal = sound_buttons[settings.enable_sound]
            settings.enable_music = !settings.enable_music
            button.texture_normal = music_buttons[settings.enable_music]

Color themes

We’re also goign to add a way to have different color schemes. These can change in different ways: perhaps as a settings option, or they change as the player gets to higher levels.

We’ll store the color scheme data in a dictionary, with the keys being the “name” of the scheme. Each color scheme will also be a dictionary, with the keys denoting the game component that will use that color.

Add this to

var color_schemes = {
    "NEON1": {
        'background': Color8(0, 0, 0),
        'player_body': Color8(203, 255, 0),
        'player_trail': Color8(204, 0, 255),
        'circle_fill': Color8(255, 0, 110),
        'circle_static': Color8(0, 255, 102),
        'circle_limited': Color8(204, 0, 255)
    "NEON2": {
        'background': Color8(0, 0, 0),
        'player_body': Color8(246, 255, 0),
        'player_trail': Color8(255, 255, 255),
        'circle_fill': Color8(255, 0, 110),
        'circle_static': Color8(151, 255, 48),
        'circle_limited': Color8(127, 0, 255)
    "NEON3": {
        'background': Color8(0, 0, 0),
        'player_body': Color8(255, 0, 187),
        'player_trail': Color8(255, 148, 0),
        'circle_fill': Color8(255, 148, 0),
        'circle_static': Color8(170, 255, 0),
        'circle_limited': Color8(204, 0, 255)

var theme = color_schemes["NEON1"]

Now on each object, we need to set the colors based on the settings property.

For the circle, the color is set using the shader material resource. Because resources are shared, that means that changing one circle’s color would change them all. Let’s make each circle’s material unique to avoid this:

$Sprite.material = $Sprite.material.duplicate()
$SpriteEffect.material = $Sprite.material

The color of the circle is determined by what mode it’s using, so set_mode() is where we’ll choose the color:

func set_mode(_mode):
    mode = _mode
    var color
    match mode:
            color = settings.theme["circle_static"]
            current_orbits = num_orbits
            $Label.text = str(current_orbits)
            color = settings.theme["circle_limited"]
    $Sprite.material.set_shader_param("color", color)

Then in the _draw() function where we’re filling in the limited circle, replace the red color with settings.theme["circle_fill"].

For the player, set the color in its _ready():

func _ready():
    $Sprite.material.set_shader_param("color", settings.theme["player_body"])
    $Trail/Points.default_color = settings.theme["player_trail"]

In the next part, we’ll add a movement to the circles.

Follow this project on Github:

Do you prefer video?