Using gun recoil to launch player

Godot Version

4.6

Question

Hello! I’m currently making a game about wizards and guns and I have been stuck on a problem for a day or two now. I’m trying to make it so when the player shoots their gun they get launched in the opposite direction but nothing works. I have watched multiple tutorials and have looked through other godot forums but I can’t get it to work. btw the player is a characterbody2d

extends CharacterBody2D

@onready var player = $".."
@onready var main = $"../.."
@onready var bullet = load("res://scenes/magic_bullet.tscn")
var shoot_cooldown_active = false
var ammo = 10
var reloading_active = false
var spells = ["recoil", "poison","piercing", "damage", "baguette"]
var current_spell = null
var look_at_pos = get_global_mouse_position()

func _process(_delta: float) -> void:
	look_at_pos = get_global_mouse_position()
	look_at(look_at_pos)
	$"../CanvasLayer/ammo".text = str("ammo: ", ammo)
	if Input.is_action_just_pressed("spell 1"):
		current_spell = spells[0]
		print(current_spell)
	if Input.is_action_just_pressed("spell 2"):
		current_spell = spells[1]
		print(current_spell)
	if Input.is_action_just_pressed("spell 3"):
		current_spell = spells[2]
		print(current_spell)
	if Input.is_action_just_pressed("spell 4"):
		current_spell = spells[3]
		print(current_spell)
	if Input.is_action_just_pressed("spell 5"):
		current_spell = spells[4]
		print(current_spell)
	if Input.is_action_pressed("shoot") and shoot_cooldown_active == false and ammo > 0 and reloading_active == false:
		shoot()
	if Input.is_action_just_pressed("reload"):
		reload()

func shoot():
	var bullet_instance = bullet.instantiate()
	bullet_instance.global_position = $Marker2D.global_position
	bullet_instance.rotation = global_rotation
	main.add_child.call_deferred(bullet_instance)
	$Shoot_cooldown.start()
	shoot_cooldown_active = true
	ammo -= 1
	player.recoil(look_at_pos)

func reload():
	reloading_active = true
	$Reload_timer.start()

func _on_shoot_cooldown_timeout() -> void:
	shoot_cooldown_active = false

func _on_reload_timer_timeout() -> void:
	reloading_active = false
	ammo = 10

This is the gun script there isn’t much in it for the recoil

extends CharacterBody2D

const recoil_strength = 10const SPEED = 500.0const JUMP_VELOCITY = -600.0

func _physics_process(delta: float) → void:
    if not is_on_floor():
    velocity += get_gravity() * delta

if Input.is_action_pressed("jump") and is_on_floor():
	velocity.y = JUMP_VELOCITY

var direction := Input.get_axis("left", "right")
if direction:
	velocity.x = direction * SPEED
else:
	velocity.x = move_toward(velocity.x, 0, SPEED)

move_and_slide()

func recoil(dir):
    var force = dir * recoil_strength
    velocity = force

this is the code for the player

If I’m shooting downwards it sends me flying upwards really far kinda of in a wierd way and if i shoot up I get sent downards really far but if I shoot towards the sides i barely move.

Post your scene tree structure as well.


this is the player scene

just looking at the code is actually enough. I’ll reduce it to the relevant lines:

gun script:

player script

(i hope the changes I made to the indentation in the code are correct)

In the recoil function, you change the complete velocity.

The velocity is only used within the _physics_process function (within move_and_slide()).

The y value of the velocity will be overwritten if jump was pressed, but this is usually only during one single frame.

If you have any sideways input, the x value of the velocity will be overriden.
If you don’t have any sideways input, the x value of the velocity will still be changed toward zero each frame.

Instead of directly setting the velocity in the recoil function, you could have a separate (vector) variable that you apply to the velocity before move_and_slide (also think about reducing it over time).

What do you mean by having a seperate vector variable, like what would I put inside of it? sorry for all these questions I’m still quite new to godot.

currently, in the recoil function, you are setting the velocity variable to a Vector2, force, which is computed from the Vector2 dir and a float recoil_strength. For more info on what e.g. floats and vectors are, check the documentation on built-in types

The velocity vector is a variable that already exists on a Character2D such as your player. My approach was to define a new, separate class member (a variable living in the class scope and not just inside a function) that holds this vector.

E.g.

var external_force: Vector2 = Vector2.ZERO

#...
func recoil(dir):
    var force = dir * recoil_strength
    external_force = force
    # you might want to add instead of set here, e.g.
   # external_force = external_force + force

and then use that vector in _physics_process:

func _physics_process(delta: float) → void:
  #...
   var direction := Input.get_axis("left", "right")
   if direction:
        velocity.x = direction * SPEED
   else:
        velocity.x = move_toward(velocity.x, 0, SPEED)

  # update the velocity here, e.g. just add the external force vector
  # velocity = velocity + external_force

  move_and_slide()
 
 # also don't forget to reduce the external_force unless you want it to be in effect forever
 # external_force = external_force * 0.9

In case you don’t have them yet, try to start learning the gdscript basics - it really helps