Making Projectiles Move

Unless you want your ship to attack enemies by ramming into it with a plasma drill, the projectile that got emitted should probably move forwards over time until it hits something. We use motion resources that combine to dictate how the projectile moves.

We start with a base class just like we did with projectiles and emitters. It’s a Resource, like a texture or audio file, with data that relates to the motion but can also call a function. All it needs is a reference to the projectile it’s affecting and a function that affects it. We call the function _update_movement and pass in the current direction of travel and delta time. It returns a Vector2 - the actual movement it should effect based on this motion.

We can add all motions’ movements together to get a final intended movement.

src/ProjectileMotion/ProjectileMotion.gd

class_name ProjectileMotion
extends Resource


# The projectile this motion should affect
var projectile: Projectile


func _update_movement(_direction: Vector2, _delta: float) -> Vector2:
    return Vector2.ZERO

The Straight Motion

The first implementation is going to be a straight motion. It has an exposed travel_speed property, and its _update_movement function returns the product of speed, delta and direction.

src/ProjectileMotion/Motions/StraightMotion.gd

class_name StraightMotion
extends ProjectileMotion


export var travel_speed := 300.0


func _update_movement(direction: Vector2, delta: float) -> Vector2:
    return direction * travel_speed * delta

Now you can create a straight motion as a Resource in the base of your modular system directory. Right-click on the ProjectileMotions folder, select New Resource and find StraightMotion to create it.

ProjectileMotions/StraightMotion.tres

Adding new motions

You can add the motion to the weapons system via the inspector right away. However, if you intend to pick up motion modifiers and add them during gameplay, it’d be nice to have a helper function that prevents duplicates, because two straight motions mean double the speed!

In the ModularWeapon.gd script, we create the add_motion function. It compares script names with current motions and adds those that don’t already exist. An optional boolean checks if it should allow duplicates instead.

func add_motion(new_motion: ProjectileMotion, allows_duplicates := false) -> void:
    if not allows_duplicates:
        var has_motion := false
        for motion in projectile_motions:
            has_motion = new_motion.get_script() == motion.get_script()
            if has_motion:
                return
    projectile_motions.append(new_motion)

Updating the projectile’s movement

All that’s left now is to have a way to use those motions to affect projectiles. In the base projectile Projectile.gd, the _update_movement name is re-used from the motions. Its job is to go through each motion it has access to and return a vector that is the total of all its motions.

func _update_movement(delta: float) -> Vector2:
    var movement_vector := Vector2.ZERO

    if motions.empty():
        return movement_vector

    for motion in motions:
        movement_vector += motion._update_movement(direction, delta)

    return movement_vector

In BasicProjectile.gd, we use this new function to get the expected movement and use move_and_collide to slide the projectile along. If we hit something, then we disappear. We’ll react with collisions next.

func _physics_process(delta: float) -> void:
    var movement := _update_movement(delta)

    var collision := move_and_collide(movement)

    if collision:
        queue_free()