Skip to content

Projectile types

Now that we’ve made something we can check if the player has met the requirements to get a new bullet type, we can talk about making the different bullet types.

Inheritance is a core foundational programming concept. We’ll be using it to make two, or potentially more, bullet types.

We’ll be using it by extending the bullet class and to make inherited scenes.

For this guide we’ll have a speed bullet and a piercing bullet. One will move fast, the other will move slower but go through enemies and destroy multiple.

  1. In the FileSystem dock, find your bullet scene, right-click it and select New Inherited Scene. The new scene in your editor should look identical, but all child node names will be yellow. This just means any changes to the original scene will copy over to these.

    In the future if we want to update all bullets, for example by giving them an AnimatedSprite2D instead of just a Sprite2D, then we would only need to change the bullet scene instead of every scene.

  2. Save this scene as speed_bullet.tscn.

  3. Make another inherited scene based on the original bullet scene. Save this new scene as piercing_bullet.tscn.

    Both types of bullet will inherit from the base bullet scene/class.

  4. On the root Area2D node you will need to disconnect the current script and add new ones.

First, we’ll need to change how the original bullet script works to be ready for inheritance.

  1. If you haven’t already, give the original bullet script a class name of Bullet.

    Your function that’s connected to the body_entered signal of Area2D starts by checking if the body that entered is an enemy. Every bullet is going to be doing that, so we don’t want to rewrite that every time when we overwrite the function.

  2. Instead, we should just make a bare-bones function that will be the default if the extending class doesn’t overwrite it:

    func _on_body_entered(body):
    if body is Enemy:
    enemy_entered(body)
    func enemy_entered(enemy: Enemy) -> void:
    enemy.queue_free()
  3. Also, while we’re here change the SPEED constant to @export var speed. @export means it shows up in the inspector, and we change it from CONSTANT_CASE to snake_case because it’s no longer a constant. You’ll also have to change it in _physics_process. This just lets us change the speed of each bullet without having to overwrite more things in the script.

  1. In your speed bullet scene replace the script with a new script and give it this:

    class_name SpeedBullet extends Bullet
    func enemy_entered(enemy: Enemy) -> void:
    enemy.queue_free()
    queue_free()

    It’s a very small script, and you might be wondering how it’ll move and do all the other things a bullet needs to do. Well, all of that is already in the Bullet class that you’ve written previously. Now, we’re just adding onto that and replacing enemy_entered with new contents.

  2. Since it’s a speed bullet, in the Inspector dock, increase the speed on the Area2D node. Well, now it’s a SpeedBullet node, and you’re changing the Bullet node variable.

Do all the same for piercing bullet, but remove the final queue_free(), change the class name to PiercingBullet and decrease the speed.

In your projectile spawner script, add the two new bullet scenes as global variables. Then, replace var new_projectile: Bullet = BULLET.instantiate() (or equivalent) with:

var new_projectile: Bullet
if Singleton.score < 10:
new_projectile = BULLET.instantiate()
elif Input.is_action_pressed("ui_accept"):
new_projectile = SPEED_BULLET.instantiate()
else:
new_projectile = PIERCING_BULLET.instantiate()

This is just a temporary check to see if the player has unlocked the upgraded bullets. If the player has got less than 10 score it’s just the base bullet. If it’s above 10 then they can use either the speed or piercing bullet by holding down spacebar or not.

You should come up with some system of your own, perhaps a shop, that decides which bullet is being used currently.

Contribute Donate