07.adding-the-speed-boost-pickup

Adding the speed boost pickup

In this lesson, we’ll create a new pickup type: the speed boost.

It will make the player run faster until they hit an obstacle or go off course.

With the slow down from earlier, we’ll start having many elements of a real game! Let’s dive in.

Creating the speed boost pickup scene

Just like you did for the Ghost pickup, right-click the pickups/Pickup.tscn file and create a New Inherited Scene. Rename the root node to PickupSpeed and save the scene in the pickups/ directory.

Drag and drop the image assets/pickup_lightning.png onto the PickupGem sprite node in the Scene dock and assign it to its Texture slot to change the pickup’s appearance.

We now want to assign a new script that extends Pickup.gd to the scene’s root node. Right-click on the PickupSpeed node and select Attach Script.

Coding the speed boost

To code the speed boost, we start in the character’s script. We need to increase its speed when touching the pickup and reset it when colliding with something.

Open the Godot.gd script. At the top, add a variable that we will use to know if the godot robot picked a speed up or not:

var speed_effect_is_active := false

Add the following function and speed constant.

const SPEED_FAST := 1100.0

func apply_speed_effect() -> void:
    speed_effect_is_active = true
    speed = SPEED_FAST

We will use this function later in the PickupSpeed.gd script to apply the pickup, just like we did with the ghost effect in PickupGhost.gd.

With this, we can change the speed of the character. But we’re not done: we also want to cancel the effect if we bump into anything.

We need to reset the character’s speed when colliding with obstacles. We use the KinematicBody2D.get_slide_count() function to do that.

This function returns the number of times the physics body collided in a given frame. If it is greater than 0, we know that the character hit an obstacle, like a wall or a rock.

We could write:

if get_slide_count() > 0:
    speed = SPEED_DEFAULT

But if we only check get_slide_count(), the player’s speed will also reset if bumping in an obstacle while being slowed down. We don’t want that. We want to reset speed when bumping in an obstacle, but only if the player has a speed up.

func _physics_process(delta: float) -> void:
    # ...
    if speed_effect_is_active and get_slide_count() > 0:
        speed = SPEED_DEFAULT

All that’s left now is to call our new function from the pickup. Reopen the PickupSpeed.gd script and override the pickup.apply_effect() function.

extends "Pickup.gd"

func apply_effect(body: Node) -> void:
    body.apply_speed_effect()

With that, we can test the speed boost pickup.

Testing our progress

Create a new scene file and add the Godot character, a PickupSpeed instance and scatter a few rocks around.

Play the scene to see how the character’s speed increases upon collecting the yellow gem.

The effect is lost when the character collides with a rock.

That’s two pickups done!

Like the turrets in the tower defense series, we saw how to create a base scene that we can reuse to create new pickups quickly.

If you wanted to create a another new pickup, you would inherit the Pickup scene, extend the Pickup script, and override the apply_effect() function.

Sometimes, we need to add more code to the character since we had to create new game mechanics.

We hope you can see how each new pickup requires a small number of code changes.

You could apply the same techniques to consumable items in a role-playing game.

The idea is the same: you want to have the same function on every item to apply their effect. That way, you only need to know one function to call, and it will work with any item.

The code

Here’s the complete code for the character and the pickup we added in this lesson.

Godot.gd

extends KinematicBody2D

const DIRECTION_TO_FRAME := {
    Vector2.DOWN: 0,
    Vector2.DOWN + Vector2.RIGHT: 1,
    Vector2.RIGHT: 2,
    Vector2.UP + Vector2.RIGHT: 3,
    Vector2.UP: 4,
}

const SPEED_SLOW := 100.0
const SPEED_DEFAULT := 700.0
const SPEED_FAST := 1100.0

# We update the speed variable to use our new constant.
var speed := SPEED_DEFAULT
var drag_factor := 0.13
var velocity := Vector2.ZERO

var start_collision_layer := collision_layer
var start_collision_mask := collision_mask
var speed_effect_is_active := false

onready var sprite := $Sprite
onready var animation_player_ghost := $AnimationPlayerGhost
onready var timer_ghost := $TimerGhost
onready var slowdown_area := $Area2D


func _ready() -> void:
    timer_ghost.connect("timeout", self, "toggle_ghost_effect", [false])
    slowdown_area.connect("area_entered", self, "_on_Area2D_area_entered")
    slowdown_area.connect("area_exited", self, "_on_Area2D_area_exited")


func _physics_process(delta: float) -> void:
    var direction := Input.get_vector("move_left", "move_right", "move_up", "move_down")
    var desired_velocity := speed * direction
    var steering_vector := desired_velocity - velocity
    velocity += steering_vector * drag_factor
    move_and_slide(velocity)
    if speed_effect_is_active and get_slide_count() > 0:
        speed = SPEED_DEFAULT

    var direction_to_frame_key := direction.round()
    direction_to_frame_key.x = abs(direction_to_frame_key.x)
    if direction_to_frame_key in DIRECTION_TO_FRAME:
        sprite.frame = DIRECTION_TO_FRAME[direction_to_frame_key]
        sprite.flip_h = sign(direction.x) == -1


func apply_speed_effect() -> void:
    speed_effect_is_active = true
    speed = SPEED_FAST


func toggle_ghost_effect(is_on: bool) -> void:
    if is_on:
        timer_ghost.start()
        collision_layer = 0
        collision_mask = 0
        animation_player_ghost.play("ghost")
    else:
        collision_layer = start_collision_layer
        collision_mask = start_collision_mask
        animation_player_ghost.stop()


func _on_Area2D_area_entered(area: Area2D) -> void:
    # When entering or leaving the area, we ensure that it has the offcourse
    # node group to distinguish it from the push wall's area.
    if area.is_in_group("offcourse"):
        speed = SPEED_SLOW


func _on_Area2D_area_exited(area: Area2D) -> void:
    if area.is_in_group("offcourse"):
        speed = SPEED_DEFAULT

PickupSpeed.gd

extends "Pickup.gd"


func apply_effect(body: Node) -> void:
    body.apply_speed_effect()