Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a Frames option to the Timer node. #131

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>
67 changes: 61 additions & 6 deletions scene/main/timer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,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 @@ -60,12 +68,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 @@ -81,7 +96,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 @@ -152,13 +172,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 @@ -168,6 +188,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 @@ -190,6 +230,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 @@ -213,9 +261,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 @@ -224,6 +276,9 @@ void Timer::_bind_methods() {

BIND_ENUM_CONSTANT(TIMER_PROCESS_PHYSICS);
BIND_ENUM_CONSTANT(TIMER_PROCESS_IDLE);

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 @@ -47,13 +47,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 @@ -77,13 +85,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
Loading