0 votes

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

https://youtu.be/WIxQw5KP1ys

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.

Godot version 3.3.2
in Engine by (36 points)

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

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

3 Answers

+1 vote

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
by (2,832 points)

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.

+1 vote

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)
by (636 points)

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.

0 votes

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.

by (36 points)
edited by

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.

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.