Timer Guidance

I was hoping I could get some guidance with an issue I am having with Godot. I am about 1/2 through the Alien Attack tutorial for the GoDot course with professor Kaan. The course has done a great job at teaching me all about the use of signals in GoDot, but I am having one minor issue.
I have been tinkering with the projects outside of the videos to learn more/have fun and I am attempting to create a timer that will prevent my gun from being able to fire constantly (or in this case the rocket from the ship).
What I have tried.
I created a timer variable that was attached to my player character. My player character is a CharacterBody2D node, consisting of a sprite, collisionshape2d, area2d (with a collisionshape2d child), node2d (rocket container) and a timer.
The timer has access as a unique name turned on. I created an onready variable called shootTimer and accessed it with $Timer

so it looks like this currently:

#Node variables to instantiate (@onready)
@onready var rocket_scene = preload("res://scenes/rocket.tscn")
@onready var rocketContainer = $RocketContainer
@onready var shootTimer = $Timer

#timer for shooting
func _ready():
	shootTimer.timeout.connect(shoot)

#handle inputs from player
func _process(delta):
#shooting
	if Input.is_action_just_pressed("shoot"):
		shoot()
#movement
func _physics_process(delta):
	velocity = Vector2(0,0)
	if Input.is_action_pressed("move_right"):
		velocity.x = speed
	if Input.is_action_pressed("move_left"):
		velocity.x = -speed
	if Input.is_action_pressed("move_up"):
		velocity.y = -speed
	if Input.is_action_pressed("move_down"):
		velocity.y = speed
	move_and_slide()
	
	#limit player movement to the viewport
	var rect = get_viewport_rect().size
	global_position = global_position.clamp(Vector2(0,0), rect)

#instance rocket and add a spawning position
func shoot():
	var rocket_instance = rocket_scene.instantiate()
	rocketContainer.add_child(rocket_instance)
	rocket_instance.global_position = global_position
	rocket_instance.global_position.x += 75

So in my mind this code checks out. There are no errors thrown, and I have autostart selected for my timer. So it should be allowing me to fire every three seconds, but nothing has changed and my player can still spam the fire button and get a wall of rockets.
I was hoping that this would be easy to address and I’m missing something minor. Please, if I am far off base just let me know and I will stick with the videos, but I do feel that I’m close to the solution.

– minor edit to note that I have watched the video on connecting the spawner signals, which is what gave me the idea that I could easily convert this timer node into one for my firing.
For example, if I want say a pump shotgun there would be more of a delay in between shots than an smg, but no matter what I don’t want the bullets to stack on top of one another, so this is the issue I’m currently facing.

Sure! I think you just need to rethink exactly what you want your timer to do. The way it’s set up at the moment, it looks closer to a timer that will cause the player to automatically shoot on timeout. This is how you would most likely go about creating a player in a bullet heaven game… which you could certainly do, but it’s probably not what you’re aiming for.

I think what you’re actually looking to do is create a cooldown timer that:

  • Is turned off when the game runs
  • Starts when the player shoots
  • Prevents the player from shooting again while the timer is running
  • And turns off again when it times out

Writing out a list of steps like this is a fundamental part of algorithm design and will reduce situations like this one in the future. I do this all the time and it makes an enormous difference! Give this a shot and see how it goes =)

1 Like

Sure enough this is what is happening now. I’m not sure what I changed or perhaps it was just inpatience yesterday and I didn’t notice, I can still spam my shots, and it does spawn a bullet itself every three seconds.
So I have the concept of the timer being on three seconds down, its just that I need to apply it to only allowing the gun to shoot when the timer has reached zero, and not to restart again until my input happens.
I’m having trouble making the connection of what I should do though.
I thought perhaps to create a new function that will countdown when I do the shoot input, but it would make more sense to just modify my shoot function.
Then I thought to look at the signals available for timer, but I can’t seem to map it out correctly.
I believe the issue would be something similar to:

func shoot():
	var rocket_instance = rocket_scene.instantiate()
	rocketContainer.add_child(rocket_instance)
	rocket_instance.global_position = global_position
	rocket_instance.global_position.x += 75
    if Input.is_action_just_pressed("shoot"):
     shootTimer.start()```

I think my issue is that I don't know what to do, or how to apply the restarting of the timer? I was wondering if the timer should start in the _process function, and it would instead look like this:

_

process(delta):
     if Input.is_action_just_pressed("shoot"):
            shootTimer.start()
            shootTimer.timeout(shoot)

However, this causes my game to crash when I press my shoot button, so I have been stuck on this for a bit today. I am sure I am missing something simple, but not sure what it is.

In short, I tried rearranging both my shoot function and my _process(delta) function. Neither of which improved the situation. For now I’ve simply disabled the timer as I had everything working with the tutorial I’ve been following. I hope to find out how to do this though as it seems like a rather elementary idea/concept (something that should absolutely be included in most any game with shooting mechanics.)

This is a great thought; just keep in mind that there is much more at your disposal than signals! You also have a node’s member variables and functions, any of which might be exactly what you need. You can always find details about these things in the docs. I’ll come back to this in a moment.

I realized I could have been more specific with my second point: Starts when the player shoots if it is stopped.

Check my third point: prevents the player from shooting again while the timer is running. Because the timer is a cooldown timer, this means that when you attempt to shoot, you need to check if the timer is running. Look through the Timer documentation for is_stopped() and I think you’ll find your answer. This addresses the new addition to the second point as well.

Your shoot function should look like this:

  • if shootTimer.is_stopped():

  • [fire a rocket]

  • shootTimer.start()

Finally, you don’t need any code running in timeout() itself.

Hey, I must be messing something up at the end because I feel like this would work, and I generally followed what you stated here, but now I can still spam fire bullets.
I have attached a print function to try and discern why this is but it simply states 2 (the amount of time I have set my timer to)

func shoot():
	var rocket_instance = rocket_scene.instantiate()
	rocketContainer.add_child(rocket_instance)
	rocket_instance.global_position = global_position
	rocket_instance.global_position.x += 75
	if shootTimer.is_stopped() and Input.is_action_just_pressed("shoot"):
		shootTimer.start()
		print(shootTimer.time_left)

I printed my code above where I state that the shoot timer is stopped, to allow the instance of it to happen, and then tell it to start once I instantiate the rocket into the scene by pressing the shoot button, but the timer just stays on 2, and seems that it is resetting it.

It also only prints two once, which I believe suggests that it is only reading my for loop once rather than applying the condition that I tried to set for it?

Ok, you’re getting closer. The order that each line runs in is important - that’s true for pretty much all code. Think about what’s happening in your shoot function right now if you walk through it line by line:

  • shoot() is called:
  • a rocket is fired (unconditionally),
  • then a condition is checked:
    • if the timer is stopped AND the shoot button is pressed (which is always going to be true since that’s the only way you’re shooting):
    • start the timer that currently has no bearing on the rockets being fired, because they were already fired earlier in the function.
    • print the time left on the timer that was immediately restarted in the previous line.

So, there are a few things that need cleaning up:

  • Get rid of and Input.is_action_just_pressed("shoot") - this will always return true because you are only ever trying to shoot when pressing the shoot button, and therefore that’s the only time shoot() will ever be called. It does no harm to leave it in, but it changes nothing and it’s just extra code to overcomplicate things.
  • Checking the status of the timer should be the first thing you do in the function, because you should not be able to shoot unless the timer is stopped.
  • The 4 lines of rocket-spawning code should run ONLY IF the timer is stopped, so move those lines to where your print statement currently is, inside the if-statement.

The full code should look like this:

func shoot():
	#first, check timer
	if shootTimer.is_stopped():
		#timer stopped? Start it and shoot a rocket
		shootTimer.start()
		var rocket_instance = rocket_scene.instantiate()
		rocketContainer.add_child(rocket_instance)
		rocket_instance.global_position = global_position
		rocket_instance.global_position.x += 75
	#timer running? Do nothing.

Try your best to understand the logic behind this though, rather than just copying it in. That’s how you’ll learn to apply this thinking to other situations in the future! And if there is still confusion about this, I’ll still clear it up if you ask =)

Hey, I really appreciate the input, but I am still struggling here. I updated the code with comments to highlight what I believe should be happening in the script, and if you can tell me where I am incorrect?

The issue is still the same, even when I implemented this same code (with the addition of printing the timer so I can see the time left on it, but that shouldn’t interfere I don’t think because its just a way to troubleshoot?)

extends CharacterBody2D

#global variables
var speed = 650
var rocket_container


#Node variables to instantiate (@onready)
@onready var rocket_scene = preload("res://scenes/rocket.tscn")
@onready var rocketContainer = $RocketContainer
@onready var pew = $pewpew #laser sound
@onready var ded = $dead #explosion sound
@onready var shootTimer = $Timer #rocket timer

#custom signals
signal took_damage # for taking damage


#create _process function to handle shooting
func _process(delta):
	if Input.is_action_just_pressed("shoot") and shootTimer.is_stopped():
		shoot()
		pew.play()


#create physics process for movement controller
func _physics_process(delta):
	velocity = 400 * Input.get_vector("move_left", "move_right", "move_up", "move_down")
	move_and_slide()
	
	#limit the players viewport to the visible screen area using get_viewport_rect function
	var rect = get_viewport_rect().size
	global_position = global_position.clamp(Vector2(0,0), rect)



#create an instance of the rocket and add the spawn position for the rocket
func shoot():
	if shootTimer.is_stopped():
		shootTimer.start() # start the timer if it is stopped
		var rocket_instance = rocket_scene.instantiate() #instantiate the scene, give it the variable name rocket_instance
		rocketContainer.add_child(rocket_instance) #get the scene from the rocketContainer node 
		rocket_instance.global_position = global_position # gets player global position and places 
		rocket_instance.global_position.x += 75 # moves the rocket 75 pixels in the positive direction on the x axis
		print(shootTimer.time_left) #print the amount of time left
		#this still sticks to only letting me fire once, and doesn't register any further inputs. 
		#im wondering if this is because of my input for shooting?


func take_damage():
	took_damage.emit() #damage signal 


func die():
	queue_free() #kill the player object


I tried to name everything appropriately and comment on the portions that were relevant/might cause confusion since I have some odd naming conventions for some of my variables

I made an assumption here that I think isn’t actually the case. If you can shoot exactly once, that’s progress, because now the timer is dictating when you can shoot (this is still progress!). The only problem at this point is that the timer isn’t turning off again afterwards. Two ways to fix this:

  • In the Inspector, tick Timer–>One Shot (I assumed this was ticked already based on what you were trying to do)
  • In the timeout() callback, write shootTimer.stop()
1 Like

This was indeed the issue. I was surprised when you said that timeout wasn’t necessary because I was wondering when the timer would know to stop. I thought one shot was that the timer would be used one time only, not that it would reset itself afterwards, but that is incredibly helpful. Could this be properly used to create an smg style firing pattern? Or would it be better to use await? ( I guess that might be a poor question because in that case the timer is created in the scene tree then disposed of rather than just having a fixed node?)

I can only assume you’re talking about burst fire. This is a bit more complicated, but the way I would do it builds on what was done here.

If you’re referring to await get_tree().create_timer(x).timeout (specified because await can be used for many other things), that’s used to make the function wait to continue until the timer runs out, but it doesn’t prevent other code from calling the function again in the meantime. You still could use that approach for something like burst fire, the code would just look a bit different.

1 Like

Yes, await is a keyword so I know it has a range of applications, sorry I should have been more specific. My train of thought was along the lines I create a gun that shoots when pressed (not just pressed, so it continuously shoots when held) and then add a brief time delay between bullets that are instantiated into the scene. (so in the same if condition, but a different portion of the code, just after the bullet is instantiated the timer would start) I will test this out later tomorrow its very late where I am. I greatly appreciate all of your input as it has definitely helped guide me into the correct direction more than once.

1 Like

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms