Also, your assumption that it’s “more efficient” is erroneous. From the docs:
As opposed to Timer, it does not require the instantiation of a node.
This means you do not have the processing and memory overhead of creating a Node object. Godot is highly optimizaed by the Godot team, so I’m willing to bet that get_tree().timer() is going to use less resources that anything you could do - even if you were it do it in C++.
I strongly disagree with this.
To connect to a timer created by a get_tree().timer() you have to assign it to a SceneTreeTimer object - which gets dereferenced when it expires. this is just begging for bugs in your code. Plus you turn a one-line piece of code into a minimum of four just to accomplish the same task.
await get_tree().create_timer().timeout has some very specific uses. Typically waiting until you can execute the next line of code.
Example 1 - Waiting To queue_free()
An example in the project I’m working on this week. When anything blows up (it’s a space shooter), I want to wait until the explosion sound and the explosion particle effect complete before the node they are attached to disappear.
@onready var explosion_sound: AudioStreamPlayer2D = $"Explosion Sound"
@onready var explosion: GPUParticles2D = $Explosion
.
.
.
await get_tree().create_timer(max(explosion_sound.stream.get_length(), explosion.lifetime)).timeout
queue_free()
It would look like this is if I wanted to tie it to another function:
@onready var explosion_sound: AudioStreamPlayer2D = $"Explosion Sound"
@onready var explosion: GPUParticles2D = $Explosion
var scene_tree_timer: SceneTreeTimer
.
.
.
scene_tree_timer = get_tree().create_timer(max(explosion_sound.stream.get_length(), explosion.lifetime))
scene_tree_timer.timeout.connect(_on_scene_tree_timer_timeout)
func _on_scene_tree_timer_timeout() -> void:
queue_free()
Example 2 - Quit Button
You just need a little time for the click sound to play before killing the program. (Or doing whatever the next thing is that wipes out the current screen. Like play a death sound.)
func _on_button_pressed() -> void:
await get_tree().create_timer(0.25).timeout # Just enough time to hear the click sound.
Game.quit() #Custom function, but it's instantaneous
Example 3 - Fading Music In/Out
_fade_out(music_player, fade_time)
await get_tree().create_timer(fade_time).timeout
music_player.set_stream(song)
music_player.play()
Example 4 - Removing/Adding Something in the UI with a Tween
func remove_missile() -> void:
displayed_missiles -= 1
var missile: TextureRect = missile_display.get_child(0)
var tween: Tween = create_tween()
tween.tween_property(missile, "material:shader_parameter/progress", 1.0, dissolve_speed)
await get_tree().create_timer(dissolve_speed).timeout
missile.queue_free()
Example 5 - Limiting the Rate of Fire for a Weapon/Ability
if Input.is_action_pressed("fire"):
is_gun_ready = false
shoot()
await get_tree().create_timer(fire_rate).timeout
is_gun_ready = true
Example 6 - Setting a Delay between Songs in Level Music
func _on_song_finished() -> void:
await get_tree().create_timer(3.0).timeout
level_playlist.pick_random().play()
Conclusion
In my opinion, you should always be creating an actual Timer (not a SceneTreeTimer) if you want to connect to a function on timeout. So you should use get_tree().create_timer() when you want to have your code wait asynchronously for time to pass before doing the next thing. It’s helpful in keeping your code together and readable.
Ultimately, I believe it’s important to use the option that makes your code more readable. It’s as simple as that. And if you are creating a Timer object in memory, you should be re-using that sucker.