The Weapons Management System

We’re ready to build the last object before we can shoot something: the actual weapons system. It doesn’t have a complicated job. It exposes properties in the inspector and places cannons according to the template it’s given.

src/ModularWeapon.gd

class_name ModularWeapon
extends Node2D


# The scene template for the position of cannons in space
export var emitter_configuration: PackedScene

# The emitter that controls what projectile to shoot
export var projectile_emitter: PackedScene

# An array of motion resources that control how the projectiles move.
export(Array, Resource) var projectile_motions := []

We’ll cover the motions next!

Removing and cleaning up old emitters

When you change firing configuration, you should also be removing the old ones, so we need functions to clean up and to add new emitters.

To clean up, we remove any that are ProjectileEmitters for each child under the weapons system.

It takes a frame before the object is removed from the scene tree when using queue_free, so we also call remove_child because we want to replace the old emitters with the new ones right away. Otherwise, for one frame, you’d have both the old and the new cannons, and this could lead to undesired results. It could also not, but it’s better to be safe.

func _clear_emitters() -> void:
    for child in get_children():
        if child is ProjectileEmitter:
            remove_child(child)
            child.queue_free()

Adding new emitters using the configuration as a template

To configure the cannon load-out, we grab the template, analyze it, and then create emitters in their place. Since it’s a PackedScene, we have access to instance and get_children to do both. For each weapon location, add a new emitter, position it, and rotate it.

Note the manual call to free at the end to remove the configuration. We never add it as a child of any node, so there is no parent to clean up when it leaves the scene tree. It falls to us to clean up the node.

We could use queue_free instead, but since it’s not in the scene tree (we didn’t call add_child), nothing depends on it.

func _add_new_emitters() -> void:
    var configuration := emitter_configuration.instance()

    for weapon_position in configuration.get_children():
        var new_emitter := projectile_emitter.instance()
        new_emitter.position = weapon_position.position
        new_emitter.rotation = weapon_position.rotation

        new_emitter.weapons_system = self
        add_child(new_emitter)

    configuration.free()

Upgrading the weapons at runtime

Right now, the weapons get configured once at the start. If you replace either the emitter or the configuration in the middle of the game, it won’t change the cannon load-out. A setter using setget solves that problem: when you set emitter_configuration or projectile_emitter, Godot calls the setter. In those setters, we call clear and add emitters to redo all the instancing.

export var emitter_configuration: PackedScene setget _set_emitter_configuration
export var projectile_emitter: PackedScene setget _set_emitter


func _set_emitter_configuration(value: PackedScene) -> void:
    emitter_configuration = value
    if not is_inside_tree():
        yield(self, "ready")

    _clear_emitters()
    _add_new_emitters()


func _set_emitter(value: PackedScene) -> void:
    projectile_emitter = value
    _set_emitter_configuration(emitter_configuration)

The weapons system scene

The actual scene is a resource, the first resource you add to anything that should shoot projectiles with this system. It’s one Node2D with the weapons system script.

ModularWeaponsSystem.tscn

Emitting our plasma bolt

And now, after this hard work creating base classes with virtual functions for emitters and projectiles, we can finally add the modular system onto the ship, drag the firing configuration and emitter into its properties, and shoot a plasma pellet.

Glorious.