Shooting (Throwing) in Platformer 2D

shooting or throwing mechanics in godot

We have an existing player character; we want to make him fire at enemies, or throw projectiles.

This Godot 4 recipe solves this problem.

Straight-line vs Parabolic

Some objects such as rocks, daggers or arrows should follow parabolic path (under the influence of gravity).

But sometimes we are not looking for realism and so we want to throw an object in straight line; as in Lep’s World or Dangerous Dave.

dangerous dave gameplay

For both the cases, we need to:

  1. Spawn the object in front of player.
  2. Set its direction to be direction of player.
  3. Apply velocity to make it move.
    • If velocity is not defined, it can be done by changing position with respect to time.
  4. Once the object has collided with anything; remove it from game world and:
    • If the collider has health property, decrement it.
    • Else do nothing.

So lets do this now.

Creating throwable

In Godot, we make a scene for throwable object. I used RigidBody2D as root node. If object is supposed to go in straight line, just make the gravity_scale to 0.0.

This is how the scene looks like:

shooting mechanics in godot, dagger

The rigid body physics will be applied to it resulting in a good projectile motion. However, if you don’t want to use rigid-body-physics (for performance reasons), then make Area3D as root and manually update position like this:

# We will have to call this function continously in _process(delta)
# with the user-defined direction and speed parameters
func move(direction: Vector2, speed: float):
    position += direction.normalized() * speed

But for for rigid-body-based solution, following is script code for the throwable object:

extends RigidBody2D
class_name Throwable

# How much health reduced if it hit
@export var health_impact: float = 1.0


func _ready():
	add_to_group("throwables")



func throw(initial_force: float, direction: Vector2):
	var unit_direction := direction.normalized()
	var impulse := unit_direction * initial_force
	apply_central_impulse(impulse)


func _on_body_entered(body):
	if "health" in body:
		body.health -= health_impact
	queue_free()

The above code also reduces the health of any object having health attribute by health_impact amount.

Spawning throwable

The throw() function defined above will be called by the player. Following is an example code for throwing dagger. It loads the dagger scene (which is rigid-body object, and sets its initial position to player’s position (with some offset) and sets direction towards the mouse pointer, and finally calls the throw(force, direction) function on it):

	# Throwable weapon (example of dagger)
	if Input.is_action_just_pressed("shoot"):
		var dagger = load("res://Weapons/Dagger.tscn").instantiate() as Throwable
		dagger.position = self.position + Vector2(0, -8)
		var direction = (get_global_mouse_position() - self.position).normalized()
		dagger.throw(640, direction)
		get_parent().add_child(dagger)

Once spawned, the throwable scene must take care of its movement, and impact. After hitting something, it is responsible for reducing health of target and queue_free() itself and produce smoke or anything to visualize the impact on target site.

Another interesting approach for bullet like things

Raycast (see this). It is great for very fast things such as bullets or lasers. Ray casting is the use of a ray to test if it hits something in given direction, and if it does, we get the collider and reduce its health.

This is especially helpful for FPS games in 3D.

Thank you for reading <3

Leave a Reply

Your email address will not be published. Required fields are marked *