Skip to content

Commit

Permalink
Implements frame Timer
Browse files Browse the repository at this point in the history
  • Loading branch information
CoreTaxxe committed Nov 1, 2024
1 parent 3302f80 commit cbe084f
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 9 deletions.
16 changes: 13 additions & 3 deletions doc/classes/Timer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
print("Time to attack!")
[/codeblock]
[b]Note:[/b] To create a one-shot timer without instantiating a node, use [method SceneTree.create_timer].
[b]Note:[/b] Timers are affected by [member Engine.time_scale]. The higher the time scale, the sooner timers will end. How often a timer processes may depend on the framerate or [member Engine.physics_ticks_per_second].
[b]Note:[/b] Timers in Time mode are affected by [member Engine.time_scale]. The higher the time scale, the sooner timers will end. How often a timer processes may depend on the framerate or [member Engine.physics_ticks_per_second].
</description>
<tutorials>
<link title="2D Dodge The Creeps Demo">https://godotengine.org/asset-library/asset/2712</link>
Expand Down Expand Up @@ -53,13 +53,17 @@
<member name="process_callback" type="int" setter="set_timer_process_callback" getter="get_timer_process_callback" enum="Timer.TimerProcessCallback" default="1">
Specifies when the timer is updated during the main loop (see [enum TimerProcessCallback]).
</member>
<member name="process_type" type="int" setter="set_timer_process_type" getter="get_timer_process_type" enum="Timer.TimerProcessType" default="0">
Specifies which units the timer uses to count. (see [enum TimerProcessType]).
</member>
<member name="time_left" type="float" setter="" getter="get_time_left">
The timer's remaining time in seconds. This is always [code]0[/code] if the timer is stopped.
[b]Note:[/b] This property is read-only and cannot be modified. It is based on [member wait_time].
</member>
<member name="wait_time" type="float" setter="set_wait_time" getter="get_wait_time" default="1.0">
The time required for the timer to end, in seconds. This property can also be set every time [method start] is called.
[b]Note:[/b] Timers can only process once per physics or process frame (depending on the [member process_callback]). An unstable framerate may cause the timer to end inconsistently, which is especially noticeable if the wait time is lower than roughly [code]0.05[/code] seconds. For very short timers, it is recommended to write your own code instead of using a [Timer] node. Timers are also affected by [member Engine.time_scale].
The time required for the timer to end, in seconds or frames. This property can also be set every time [method start] is called.
Time mode uses floats while Frame mode only accepts and returns non-fractional numbers.
[b]Note:[/b] Timers can only process once per physics or process frame (depending on the [member process_callback]). In Time mode, an unstable framerate may cause the timer to end inconsistently, which is especially noticeable if the wait time is lower than roughly [code]0.05[/code] seconds. For very short timers, it is recommended to write your own code instead of using a [Timer] node. Timers are also affected by [member Engine.time_scale].
</member>
</members>
<signals>
Expand All @@ -76,5 +80,11 @@
<constant name="TIMER_PROCESS_IDLE" value="1" enum="TimerProcessCallback">
Update the timer every process (rendered) frame (see [constant Node.NOTIFICATION_INTERNAL_PROCESS]).
</constant>
<constant name="TIMER_PROCESS_TYPE_TIME" value="0" enum="TimerProcessType">
Timer works with seconds. In this mode the timer is affected by [member Engine.time_scale].
</constant>
<constant name="TIMER_PROCESS_TYPE_FRAMES" value="1" enum="TimerProcessType">
Timer works with frames. Speed depends on the framerate. Accepts non-fractional values only.
</constant>
</constants>
</class>
68 changes: 62 additions & 6 deletions scene/main/timer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,15 @@ void Timer::_notification(int p_what) {
if (!processing || timer_process_callback == TIMER_PROCESS_PHYSICS || !is_processing_internal()) {
return;
}
time_left -= get_process_delta_time();

switch (timer_process_type) {
case TIMER_PROCESS_TYPE_TIME: {
time_left -= get_process_delta_time();
}; break;
case TIMER_PROCESS_TYPE_FRAMES: {
time_left -= 1;
}; break;
}

if (time_left < 0) {
if (!one_shot) {
Expand All @@ -62,12 +70,19 @@ void Timer::_notification(int p_what) {
emit_signal(SNAME("timeout"));
}
} break;

case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
if (!processing || timer_process_callback == TIMER_PROCESS_IDLE || !is_physics_processing_internal()) {
return;
}
time_left -= get_physics_process_delta_time();

switch (timer_process_type) {
case TIMER_PROCESS_TYPE_TIME: {
time_left -= get_physics_process_delta_time();
}; break;
case TIMER_PROCESS_TYPE_FRAMES: {
time_left -= 1;
}; break;
}

if (time_left < 0) {
if (!one_shot) {
Expand All @@ -83,7 +98,12 @@ void Timer::_notification(int p_what) {

void Timer::set_wait_time(double p_time) {
ERR_FAIL_COND_MSG(p_time <= 0, "Time should be greater than zero.");
wait_time = p_time;
if (timer_process_type == TIMER_PROCESS_TYPE_FRAMES) {
ERR_FAIL_COND_MSG(p_time != (int)p_time, "Frame mode only accepts non-fractional values. (Frames will be rounded.)");
wait_time = (int)p_time;
} else {
wait_time = p_time;
}
update_configuration_warnings();
}

Expand Down Expand Up @@ -154,13 +174,13 @@ void Timer::set_timer_process_callback(TimerProcessCallback p_callback) {
if (is_physics_processing_internal()) {
set_physics_process_internal(false);
set_process_internal(true);
}
};
break;
case TIMER_PROCESS_IDLE:
if (is_processing_internal()) {
set_process_internal(false);
set_physics_process_internal(true);
}
};
break;
}
timer_process_callback = p_callback;
Expand All @@ -170,6 +190,26 @@ Timer::TimerProcessCallback Timer::get_timer_process_callback() const {
return timer_process_callback;
}

void Timer::set_timer_process_type(TimerProcessType p_type) {
if (timer_process_type == p_type) {
return;
}
switch (timer_process_type) {
case TIMER_PROCESS_TYPE_FRAMES: {
wait_time = (int)wait_time;
time_left = (int)time_left;
}; break;
case TIMER_PROCESS_TYPE_TIME:
break;
}
timer_process_type = p_type;
notify_property_list_changed();
}

Timer::TimerProcessType Timer::get_timer_process_type() const {
return timer_process_type;
}

void Timer::_set_process(bool p_process, bool p_force) {
switch (timer_process_callback) {
case TIMER_PROCESS_PHYSICS:
Expand All @@ -192,6 +232,14 @@ PackedStringArray Timer::get_configuration_warnings() const {
return warnings;
}

void Timer::_validate_property(PropertyInfo &p_property) const {
if (timer_process_type == TIMER_PROCESS_TYPE_FRAMES && p_property.name == "wait_time") {
p_property.type = Variant::INT;
p_property.hint = PROPERTY_HINT_NONE;
p_property.hint_string = "suffix:f";
}
}

void Timer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_wait_time", "time_sec"), &Timer::set_wait_time);
ClassDB::bind_method(D_METHOD("get_wait_time"), &Timer::get_wait_time);
Expand All @@ -215,9 +263,13 @@ void Timer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_timer_process_callback", "callback"), &Timer::set_timer_process_callback);
ClassDB::bind_method(D_METHOD("get_timer_process_callback"), &Timer::get_timer_process_callback);

ClassDB::bind_method(D_METHOD("set_timer_process_type", "type"), &Timer::set_timer_process_type);
ClassDB::bind_method(D_METHOD("get_timer_process_type"), &Timer::get_timer_process_type);

ADD_SIGNAL(MethodInfo("timeout"));

ADD_PROPERTY(PropertyInfo(Variant::INT, "process_callback", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_timer_process_callback", "get_timer_process_callback");
ADD_PROPERTY(PropertyInfo(Variant::INT, "process_type", PROPERTY_HINT_ENUM, "Time,Frames"), "set_timer_process_type", "get_timer_process_type");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wait_time", PROPERTY_HINT_RANGE, "0.001,4096,0.001,or_greater,exp,suffix:s"), "set_wait_time", "get_wait_time");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "is_one_shot");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autostart"), "set_autostart", "has_autostart");
Expand All @@ -226,6 +278,10 @@ void Timer::_bind_methods() {

BIND_ENUM_CONSTANT(TIMER_PROCESS_PHYSICS);
BIND_ENUM_CONSTANT(TIMER_PROCESS_IDLE);

// bind enums
BIND_ENUM_CONSTANT(TIMER_PROCESS_TYPE_TIME);
BIND_ENUM_CONSTANT(TIMER_PROCESS_TYPE_FRAMES);
}

Timer::Timer() {}
14 changes: 14 additions & 0 deletions scene/main/timer.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,21 @@ class Timer : public Node {
protected:
void _notification(int p_what);
static void _bind_methods();
void _validate_property(PropertyInfo &p_property) const;

public:
enum TimerProcessCallback {
TIMER_PROCESS_PHYSICS,
TIMER_PROCESS_IDLE,
};

// Type instead of Mode to prevent name collisions when upgrading from 3 to 4
// Godot 3 used TimerProcessMode which is now TimerProcessCallback
enum TimerProcessType {
TIMER_PROCESS_TYPE_TIME,
TIMER_PROCESS_TYPE_FRAMES,
};

void set_wait_time(double p_time);
double get_wait_time() const;

Expand All @@ -79,13 +87,19 @@ class Timer : public Node {

void set_timer_process_callback(TimerProcessCallback p_callback);
TimerProcessCallback get_timer_process_callback() const;

void set_timer_process_type(TimerProcessType p_type);
TimerProcessType get_timer_process_type() const;

Timer();

private:
TimerProcessCallback timer_process_callback = TIMER_PROCESS_IDLE;
TimerProcessType timer_process_type = TIMER_PROCESS_TYPE_TIME;
void _set_process(bool p_process, bool p_force = false);
};

VARIANT_ENUM_CAST(Timer::TimerProcessCallback);
VARIANT_ENUM_CAST(Timer::TimerProcessType);

#endif // TIMER_H

0 comments on commit cbe084f

Please sign in to comment.