slowly easing into this project, thought up an idea for a generalized "cooldown" class for godot 4.
A generic custom class, acting as a wrapper around a SceneTreeTimer (or maybe keeping track itself if that makes sense), with a handful of attributes:
- cooldown_time, the value that it resets to after each activation
- activation_requires, a function reference to be checked whenever it finishes in order to see if it can activate and reset, and
- activation_function, another function reference that is called upon the cooldown reaching zero if requirements are met.
There would also be room to add some optional parameters like timescale and a toggle to continue timing when the scene tree is paused.
The activation callback could be replaced with a signal in order to keep things a bit more decoupled, but i'm not sure if the same can be said of the activation requirements function due to how this implementation of the overall concept works.
Usage would involve setting up the main attributes once, optionally randomizing the starting cooldown, and then simply letting it run in the background. At the end of the cooldown period, the object would automatically start checking its requirements and call the activation function as soon as they are met. Kind of like a reusable timer with extra steps.
For automated uses like enemy attacks and other objects, the requirement function would simply be "can this activate?", whereas for a player character you'd additionally hook it up to player inputs so that it will immediately fire only when pressing the right button.
I've disentangled the original manual timing method and implemented this cooldown timer class.
I ended up inheriting the Timer class so that i can use its built-in functions, since the SceneTreeTimer is extremely lightweight and quite limiting for anything beyond a very simple timer.
Code below for those interested:
class_name CooldownTimer
extends Timer
#main parameters
var cooldown_time:float = 1
var activation_requires:Callable = func():return true
var activation_function:Callable = func():pass
#state parameters
var pause_when_cannot_activate:bool = false
var randomize_first_time:bool = false
func _ready():
#make sure the timer doesn't cycle endlessly on its own
one_shot = true
#pause status
set_pause_status()
#start timing
if randomize_first_time:
start(randf_range(0,cooldown_time))
else:
start(cooldown_time)
func _process(_delta):
#pause status
set_pause_status()
#activation
if time_left <= 0:
if activation_requires.call():
activation_function.call()
start(cooldown_time)
func set_pause_status():
#pause when unable to activate, if that parameter is set
#otherwise timer can be paused manually if desired
if !pause_when_cannot_activate: return
paused = !activation_requires.call()
To set it up, create the node and add it to the scene tree from another script, and pass in Callable references for the two activation functions, as well as setting the cooldown time and other parameters as needed.
In practice, there are likely many simple cases where just using a normal Timer node on repeat would serve the same purpose, but this implementation feels more flexible for my particular use case, and i figure it's something of interest to share for anyone else.
I may update it to use a signal for activation instead, if i do i'll edit this post with the new code.
One thing to keep in mind:
As the activation_requires function is called every frame, sometimes more than once, you'll want to keep it lightweight when possible. Caching precalculated values and passing in a getter would likely be a good way to do this.