Shooting projectiles

Problem

You want to shoot projectiles from your player/mob/etc..

Solution

Setting up the bullet

First, we’ll set up a “bullet” object that we can instance. Here are the nodes we’ll use:

 Area2D: Bullet
     Sprite2D
     CollisionShape2D

For the Sprite2D’s texture, you can use any image you like. Here’s an example one:

alt alt

Set up the nodes and configure the sprite and collision shape. If your texture is oriented pointing up, like the one above, make sure to rotate the Sprite node by 90° so that it’s pointing to the right, ensuring it matches the parent’s “forward” direction.

Add a script and connect the Area2D’s body_entered signal.

extends Area2D

var speed = 750

func _physics_process(delta):
    position += transform.x * speed * delta

func _on_Bullet_body_entered(body):
    if body.is_in_group("mobs"):
        body.queue_free()
    queue_free()

For this example, we’ll remove the bullet if it hits anything at all. We’ll also delete anything tagged in the “mobs” group that it hits.

Shooting

We need to set up a spawn location for the bullets. Add a Marker2D and place it where you want the bullets to spawn. Here’s an example, placed at the barrel of the gun. I’ve named it “Muzzle”.

alt alt

Notice that as the player rotates, the Muzzle’s transform remains oriented the same way relative to the gun. This will be very convenient when spawning the bullets, as they can use the transform to get the proper position and direction. We just set the new bullet’s transform equal to the muzzle’s.

Tip

This will work for any character type, not just the “rotate-and-move” style shown here. Just attach the Marker2D where you want the bullets to spawn.

In the character’s script we add a variable to hold the bullet scene for instancing:

@export var Bullet : PackedScene

And check for our defined input action:

    if Input.is_action_just_pressed("shoot"):
        shoot()

Now in our shoot() function we can instance a bullet and add it to the tree. A common mistake is to add the bullet as a child of the player:

func shoot():
    var b = Bullet.instantiate()
    add_child(b)
    b.transform = $Muzzle.transform

The problem here is that since the bullets are children of the player, they are affected when the player moves or rotates.

alt alt

To fix this, we should make sure the bullets are added to the world instead. In this case, we’ll use owner, which refers to the root node of the scene the player is in. Note that we also need to use the muzzle’s global transform, or else the bullet would not be where we expected.

func shoot():
    var b = Bullet.instantiate()
    owner.add_child(b)
    b.transform = $Muzzle.global_transform

alt alt

Download This Project

Download the project code here: https://github.com/godotrecipes/2d_shooting