Random beginner-question #11: How to auto-chain a row of timers

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

Hi everyone,

I wonder if it’s somehow possible to have a timer automatically start at finish of another timer.

The idea is a button with 3 phases: at release within the first time frame (timer_1) it should trigger effect #1. After timer_1 runs out, timer_2 follows automatically, and a release within this second time frame should cancel the operation (effect#2). However, keeping the button held down until the end of timer_2 will automatically trigger effect#3.
This would mean that timer_1 has to start at button-down and releasing the button during any of the time frames stops the timer(s).

I’m thinking of something like setting a variable “timer_1_finished = true” in the on_timer_1_timeout function and have a timer_2 start if this occurs. Initiately it works, but then, for some reason, it seems that releasing won’t stop the timers properly anymore.

Is there any better way to achieve such button behaviour?

:bust_in_silhouette: Reply From: jgodfrey

Timers seem overly complex in this case. Rather than that, why not simply:

  • In the pressed event
  • Record the current time
  • In the released event
  • Record the current time
  • Calculate a delta between the two time values
  • Compare that delta to your 3 delay values and do something appropriate

That sounds reasonable and would work for my manual triggers. (May I ask for a little setup-assistance here? How can I “capture” the curremt time?)

However, what I need is some automated triggers as well, that’s why I’m looking for a running timer.
I set something up:

extends Node2D

var counting = false
var count = 0

func _process(delta):
	if counting:
		count += delta

func _on_HoldButtonArea_input_event(viewport, event, shape_idx):
	if Input.is_action_just_pressed("mousebuttonclick"):
		counting = true
		print ("counter started!")
	if count < 0.7:
		if Input.is_action_just_released("mousebuttonclick"):
			print ("not yet 0.7!")
	if count == 0.7:
		print ("reached 0.7!")
	if count > 0.7 and count < 2:
			if Input.is_action_just_released("mousebuttonclick"):
				print ("counter between 0.7 and 2!")
	if count == 2:
		print ("reached 2!")
	if count >= 2:
		if Input.is_action_just_released("mousebuttonclick"):
			print ("more than 2, don't do a thing!")

	if Input.is_action_just_released("mousebuttonclick"):
		counting = !counting
		count = 0
		print ("counter stopped!")

This starts a timer when holding down and obviously recognizes the thresholds.
The remaining problem now: it’s only printing on release, not automatically when reaching the thresholds (I mean even the prints not "if"d with _just_released, which doesn’t seem to make sense). How can I achive this? (And is there a way to stop the counter when reaching 2, although the button remains pressed?)

pferft | 2020-09-01 10:56

Here’s an example of what I had in mind, though there are many ways to do it.

var start_time = 0

func _on_Button_button_down():
	start_time = OS.get_ticks_msec()
func _on_Button_button_up():
	var elapsed_time = OS.get_ticks_msec() - start_time
	if (elapsed_time < 100):
	elif (elapsed_time < 500):

For this example to work, you need to create a button and connect its button_down() and button_up() signals to the above functions.

Really, it does what I outlined earlier. Records the time when the button is pressed, calculates how much time has passed when the button is released, and then decides what to do based on that delta time value.

Again, there are lots of ways to accomplish this…

jgodfrey | 2020-09-01 16:10

Thanks for this! This will come in handy for sure.

I just edited my entry a second ago, adding another timer possibility I find rather accessible. (And that I would never even tried to find without your input in the first place! You’re inspiring! : )
I just can’t find the reason why the two " == " prints won’t work… do you have any idea?

pferft | 2020-09-01 16:19

Yeah, your == aren’t working as your probably never exactly equal to either 0.7 or 2.0. If you think about it, you’re counter is just a running accumulation of delta times from _process. There’s no guarantee that the total will ever exactly either value you’re testing against. In fact, it’s highly unlikely that it ever would be equal.

And, yeah, there are lots of ways to get time in Godot. Here’s a good article on the subject:


jgodfrey | 2020-09-01 16:32

And, I see my posted sample is doing something a bit different than your posted sample. When I get some time, I’ll try to post an update that accomplishes what I think you want.

jgodfrey | 2020-09-01 16:34

This seems to be close to what you want (though what you’re after is still not 100% clear to me). Try this and let me know what it’s missing (if anything):

var timer = 0
var clicked = false

func _process(delta):
	timer += delta

	if Input.is_action_just_pressed("mousebuttonclick"):
		clicked = true
		timer = 0

	if Input.is_action_just_released("mousebuttonclick"):
		clicked = false

	if clicked:
		if timer >= 2:
			print("reached 2.0")
		elif timer >= 0.7:
			print("reached 0.7")
			print("not yet 0.7")

jgodfrey | 2020-09-01 16:43

Your approach calculates the value after button up and asks what to do with it, mine captures it and sees where it fits - same goal, different methods, very interesting!

I had a feeling my == technique would be problematic but I hoped the engine would be able to automatically “get the moment” where an exact manual button release is impossibe. Now I’m looking forward to another thought of yours, feeling a bit greedy already!

pferft | 2020-09-01 16:48

In case you missed it, I posted another variation above your last post…

jgodfrey | 2020-09-01 16:54

That was quick! I was too slow, therefore the post-shift.

Basically, the sequence I have in mind is like this:
If I press the button (and keep it down): after 0.7 sec. a signal is emitted, and after 2 sec. another signal.
Releasing the button triggers its own release-signals, but they are different as well depending on whether I release before 0.7 or between 0.7 and 2. (Releasing after 2 won’t do anything anymore.)

This should be one of those buttons that will trigger something at quick press/release and automatically trigger something different at keeping held down for some time (indicating “something’s coming up” with a “bar-filling”-animation above the button, im my case starting this animation after 0.7 secs, ending at 2 secs). However, releasing while that bar is filling up will cancel the whole thing…

I feel like I’m almost there, if only I could get those == to trigger!

pferft | 2020-09-01 17:03

So, is this a proper description of the problem?

  • You want a single signal instance if the button is held for at least 0.7 seconds
  • You want a single signal instance if the button is held for at least 2.0 seconds
  • You expect both signals if the button is held for at least 2.0 seconds
  • You want a single signal instance if the button is released before 0.7 seconds
  • You want a single signal instance if the button is released between 0.7-2.0 seconds
  • You want no release signal if the button is released after 2.0 seconds

Is that right?

And, you’ll never get the == to work as you want, so you should let that one go… :wink:

jgodfrey | 2020-09-01 17:15

Here’s a variation that I think satisfies my above outline…

var timer = 0
var down_short = false
var down_long = false

func _process(delta):
	timer += delta

	if Input.is_action_just_pressed("mousebuttonclick"):
		timer = 0

	if Input.is_action_pressed("mousebuttonclick"):
		if timer >= 0.7 && !down_short:
			down_short = true
			print("button held for 0.7 secs")
		if timer >= 2 && !down_long:
			down_long = true
			print("button held for 2.0 secs")

	if Input.is_action_just_released("mousebuttonclick"):
		if timer >= 0.7 && timer <= 2.0:
			print("button released between 0.7 and 2.0")
		if timer < 0.7:
			print("button released before 0.7")
		down_short = false
		down_long = false

jgodfrey | 2020-09-01 17:33

Simply mind blowing! This works like a charm. I should have asked you before wasting 30+ hours on this… ; )
(Never a waste, I learned a ton!)

One more thing though: I’m trying to reduce the input area to only the button.

func _on_HoldButtonArea_input_event(_viewport, _event, _shape_idx):
	if Input.is_action_just_pressed("mousebuttonclick"):
		timer = 0


In this case, again, the auto-triggers only work while moving the mouse around, but not when holding still. Could an Area2D/CollisionShape2D setup be problematic here? I’ll try to figure this out…


I tried adding InputEventMouseButton:

func _on_HoldButtonArea_input_event(_viewport, event, _shape_idx):
	if event is InputEventMouseButton:
		if Input.is_action_just_pressed("mousebuttonclick"):

This makes the mouse motion not being registered anymore, but it still won’t trigger the automated actions. I’m stuck, what a shame, such elegant code! What am I missing?

(The scene is a simple Node2D with its child Area2D “HoldButtonArea” (and grandchild CollisionShape2D).)


Allow me to add your answer from my Random beginner-question #12 (indeed very related) for completion, Jgodfrey:

Why not simply connect the mouse_entered and mouse_exited signals of your Area2D. In those signal handlers, just set a simple boolean flag indicating whether the mouse is currently inside the Area2D. For example, mouse_over_button = true in the mouse_entered and … = false in the mouse_exited.
Then, use that flag to skip the code you have in _process if the value is false?
As I mentioned in the other thread, there are lots of ways to do the same thing. While this might not be the cleanest overall (based on a better understanding of what you’re doing), but it’s probably one of the easier ways to get from what you have to what you want…

Thanks so much for your help again!

pferft | 2020-09-01 18:01