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 Timers:
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 Timers. 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 Timers 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(_value): pass
func _ready() -> void:
one_shot = true
connect("timeout", queue_free, [], CONNECT_ONESHOT)
start()
func set_cancelled() -> void:
cancelled = true
emit_signal("timeout")
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.set_cancelled()
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.set_cancelled()
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.