What causes the inconsistent firing rate when shooting automatic weapons?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By UMTomato

Here’s a video of my problem (notice how the firing rate sometimes changes slightly between shots, at random):

On higher fire rates this becomes very noticeable and feels really bad.

(I tried modifying the fire rate to higher and lower values, but the problem still persisted.)

Here is the relevant code:

func _input(event: InputEvent) -> void:
	if event.is_action_pressed("left_click"):
		shoot(current_gun)

func shoot(gun: Object) -> void: 
	#shoot bullets and play sound effect
    next_shot(gun)

The problem isn’t with the shoot() function, because when I removed almost all of its code, nothing changed

func next_shot(gun: Object) -> void:
	yield(get_tree().create_timer(gun.fire_rate), "timeout")
	gun.can_shoot = true
	if gun.is_automatic and Input.is_action_pressed("left_click"):
		shoot(gun)

I don’t know if the problem is with my code or something else, but I’m really stumped on this issue at the moment.

Have you logged Project Settings > General > Debug > Settings > Print fps?

And what happens if you remove and Input.is_action_pressed("left_click") ?

clemens.tolboom | 2021-07-07 17:49

:bust_in_silhouette: Reply From: timothybrentwood

I think yield(get_tree().create_timer(gun.fire_rate), "timeout") is causing your issues. When you yield() you actually drop out of the function and step back into the function when the signal is emitted. It can be finicky depending on how you use it. In this case _input() may be firing in such a way where it doesn’t line up with the timer created in your yield() statement

I think your gun object should have a Timer as a child node that keeps track of whether it can shoot or not.

gun_script.gd

onready var shot_timer = $Timer

func _ready() -> void:
    shot_timer.one_shot = true
    shot_timer.process_mode = TIMER_PROCESS_IDLE 
    shot_timer.wait_time = 1.0 # whatever you want it to be

...

func can_shoot() -> bool:
    return shot_timer.is_stopped()

func shoot() -> void:
    # you can check if can_shoot(): here or do it in a function that calls this
    shot_timer.start()
    ... # the rest of the logic here

Thank you for the answer! I created a timer node in the gun scene and accessed its state in the player script. I compared the result with how it was previously and it definitely seems to be better, but the delay is still present.
I wrote another comment where I explain what I found upon further testing.

UMTomato | 2021-07-11 17:18

:bust_in_silhouette: Reply From: clemens.tolboom

I was thinking about removing the input check like

var can_shoot:bool = false

func _input(event: InputEvent) -> void:
    can_shoot = false

```
if event.is_action_pressed("left_click"):
    can_shoot = true
    shoot(current_gun)
```

func shoot(gun: Object) -> void: 
    #shoot bullets and play sound effect
    next_shot(gun)

func next_shot(gun: Object) -> void:
    yield(get_tree().create_timer(gun.fire_rate), "timeout")
    gun.can_shoot = true
    if gun.is_automatic and can_shoot:
        shoot(gun)

Thank you for the answer! I’ve tried the code above and also printing the FPS to the console. It seems that some of the inconsistencies are caused by lag spikes, but replacing the input check did not fix the problem, unfortunately.
I might have an idea of what is causing the main issue, I wrote another comment where try to I explain it.

UMTomato | 2021-07-11 17:15

:bust_in_silhouette: Reply From: UMTomato

Thank you everyone for the answers. I did a lot of further testing and I might have figured out the reason behind the issue, but I might be completely wrong. I made a timer node to time the delay between each shot. Even when the shots are very out of sync, the console prints exactly or something very close to the correct value. This leads me to believe that the main problem I have is not with the timing of the shots, but with the sound.
In this (short) example the values printed in the console show no irregularities at all, but the sound is clearly off: https://youtu.be/RjVTpj84fLc)

The code that handles the sound effect is very simple and nothing I do with it seems to affect the outcome:

var player = AudioStreamPlayer.new()
player.stream = load(path)
self.add_child(player)
player.play()

This might be unrelated, but this is the only idea I have at the moment.

Why do you create a Streamplayer for each shot and load the stream. Maybe this causes the delay.
Why not create a streamplayer once (maybe for each barrel) then call play(0.0) whenever you shot.

klaas | 2021-07-12 17:44