From cbe084f5cb2d3696eb4e42d465263040cdec4e6a Mon Sep 17 00:00:00 2001 From: CoreTaxxe <47256718+CoreTaxxe@users.noreply.github.com> Date: Fri, 1 Nov 2024 18:33:50 +0100 Subject: [PATCH] Implements frame Timer --- doc/classes/Timer.xml | 16 ++++++++-- scene/main/timer.cpp | 68 +++++++++++++++++++++++++++++++++++++++---- scene/main/timer.h | 14 +++++++++ 3 files changed, 89 insertions(+), 9 deletions(-) diff --git a/doc/classes/Timer.xml b/doc/classes/Timer.xml index f8f9393847c..665b1d5ad7d 100644 --- a/doc/classes/Timer.xml +++ b/doc/classes/Timer.xml @@ -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]. https://godotengine.org/asset-library/asset/2712 @@ -53,13 +53,17 @@ Specifies when the timer is updated during the main loop (see [enum TimerProcessCallback]). + + Specifies which units the timer uses to count. (see [enum TimerProcessType]). + 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]. - 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]. @@ -76,5 +80,11 @@ Update the timer every process (rendered) frame (see [constant Node.NOTIFICATION_INTERNAL_PROCESS]). + + Timer works with seconds. In this mode the timer is affected by [member Engine.time_scale]. + + + Timer works with frames. Speed depends on the framerate. Accepts non-fractional values only. + diff --git a/scene/main/timer.cpp b/scene/main/timer.cpp index cc2bc8757af..24948f5e1ff 100644 --- a/scene/main/timer.cpp +++ b/scene/main/timer.cpp @@ -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) { @@ -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) { @@ -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(); } @@ -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; @@ -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: @@ -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); @@ -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"); @@ -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() {} diff --git a/scene/main/timer.h b/scene/main/timer.h index 3282ab57d84..d3078962493 100644 --- a/scene/main/timer.h +++ b/scene/main/timer.h @@ -49,6 +49,7 @@ class Timer : public Node { protected: void _notification(int p_what); static void _bind_methods(); + void _validate_property(PropertyInfo &p_property) const; public: enum TimerProcessCallback { @@ -56,6 +57,13 @@ class Timer : public Node { 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; @@ -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