I have this code :
extends Area2D
func _ready() -> void : auto_call()
func auto_call() :
await get_tree().create_timer(1, false).timeout
print( "area autocall" )
auto_call()
func stop_auto_call() :
auto_call = null # how stop?
I know, I can use two options : create a normal Timer
or save the references in a global array to later stop it/them, but... it could take a lot of refactoring in several nodes.
I'm tring desactive or pause the node and the code works... less the auto_call calls.
I try using unreference()
, null
, stoping : process
, input
and physics
. But nothing. I see a little hope with get_tree().get_processed_tweens()
but it see than don't exist any for SceneTreeTimer
. Anyone knows what to do, alternatives or ideas?
Godot has a pause system. You can pause the SceneTree
by setting its paused
to true
:
get_tree().paused = true
Which scripts will execute when paused or not depends on process_mode
.
Furthermore, any SceneTreeTimer
created with always_process
set to false
(which you specify as second argument of create_timer
), will not run while the scene tree is paused.
However, this approach will also affect anything else working the pause system and does not give you the granularity of pausing only some of these timers.
You could also use Timer
s:
var timer := Timer.new()
add_child(timer)
timer.wait_time = 1.0
timer.one_shot = true
timer.start()
await timer.timeout
timer.queue_free()
Or like this:
var timer := Timer.new()
add_child(timer)
timer.wait_time = 1.0
timer.one_shot = true
timer.connect("timeout", timer.queue_free, [], CONNECT_ONESHOT)
timer.start()
await timer.timeout
Then to pause them, you can get each Timer
children, that is not queued for deletion, and pause it:
for node in get_children():
var timer:Timer = node as Timer
if !is_instance_valid(timer) or timer.is_queued_for_deletion():
continue
timer.paused = true
Note that this will take all children Timer
s. And that might be more than you want to.
This will allow you to only pause the timers of a specific Node
.
We can create an Autoload. I'll call it TimerRoot
. With a script with similar code as described above:
extends Node
func delay(wait_time:float) -> void:
var timer := Timer.new()
add_child(timer)
timer.wait_time = 1.0
timer.one_shot = true
timer.start()
await timer.timeout
timer.queue_free()
func set_paused(pause:bool) -> void:
for node in get_children():
var timer:Timer = node as Timer
if !is_instance_valid(timer) or timer.is_queued_for_deletion():
continue
timer.paused = pause
Which you can the use like this await TimerRoot.delay(1.0)
and pause them all like this TimerRoot.set_paused(true)
.
Alternatively you can return the Timer
:
func delay(wait_time:float) -> Timer:
var timer := Timer.new()
add_child(timer)
timer.wait_time = 1.0
timer.one_shot = true
timer.connect("timeout", timer.queue_free, [], CONNECT_ONESHOT)
timer.start()
return Timer
And use it like this: await TimerRoot.delay(1.0).timeout
.
Another variant is to add a parent
parameter, and have delay
add the timer as a child to it. Which gives you back the granularity of pausing only the Timer
s of a Node
.
By the way, in this code we are pausing. You could implement a cancel mechanism, where you could add an if
where you call delay
to check if it was cancelled. If you are returning the Timer
, you could have a script attached (which also helps us identify which are the correct timers) with a cancelled
property. This suggest us to change from an Autoload, to a class:
class_name OneShotTimer extends Timer
@export
var cancelled:bool:
get: return cancelled
set(mod_value):
if mod_value and not cancelled:
cancelled = true
emit_signal("timeout")
func _ready() -> void:
one_shot = true
connect("timeout", queue_free, [], CONNECT_ONESHOT)
start()
static func delay(parent:Node, time:float) -> bool:
if !is_instance_valid(parent):
push_error("Parent Node is not valid.")
if !parent.is_inside_tree():
push_error("Parent Node is not in the scene tree.")
var timer := OneShotTimer.new()
timer.wait_time = time
parent.add_child(timer)
await timer.timeout
return !timer.cancelled
static func set_paused(parent:Node, pause:bool) -> void:
for node in parent:
var timer:OneShotTimer = node as OneShotTimer
if !is_instance_valid(timer) or timer.is_queued_for_deletion():
continue
timer.paused = pause
static func cancel(parent:Node) -> void:
for node in parent:
var timer:OneShotTimer = node as OneShotTimer
if !is_instance_valid(timer) or timer.is_queued_for_deletion():
continue
timer.cancelled = true
Which you can use like this:
if await OneShotTimer.delay(self, 1.0):
print("OK")
else:
print("Aborted operation")
And pause all the timers of a node like this OneShotTimer.set_paused(self, true)
. And to cancel them you do OneShotTimer.cancel(self)
If you want both the granularity but also the ability to globally pause or cancel them all, we can bring back the Autoload TimerRoot
with a different code:
extends Node
var _timers:Array
func register(timer:OneShotTimer) -> void:
if is_instance_valid(timer) and !timer.is_queued_for_deletion():
_timers.append(timer)
timer.connect("timeout", func(): _timers.erase(timer), [], CONNECT_ONESHOT)
func cancel() -> void:
for timer in _timers:
timer.cancelled = true
func set_paused(pause:bool) -> void:
for timer in _timers:
timer.paused = pause
Then add TimerRoot.register(self)
on _ready
on OneShotTimer
so they are all registered. And then you can pause all with TimerRoot.set_paused(true)
or cancel all with TimerRoot.cancel()
.
But that is cancel. Which is not really stopping. Stopping is an issue, because you have some code awaiting for it. If you are Ok with them waiting indefinitely for the signal, you can simply remove the timers with queue_free
.