diff --git a/doc/classes/Particles.xml b/doc/classes/Particles.xml index 90bdf5a2ab4..6c9e74edc97 100644 --- a/doc/classes/Particles.xml +++ b/doc/classes/Particles.xml @@ -105,6 +105,14 @@ [b]Note:[/b] If the [ParticlesMaterial] in use is configured to cast shadows, you may want to enlarge this AABB to ensure the shadow is updated when particles are off-screen. + + + + Emitted when all active particles have finished processing. When [member one_shot] is disabled, particles will process continuously, so this is never emitted. + [b]Note:[/b] Due to the particles being computed on the GPU there might be a delay before the signal gets emitted. + + + Particles are drawn in the order emitted. diff --git a/doc/classes/Particles2D.xml b/doc/classes/Particles2D.xml index 867ed5d124e..7db32ddbc6a 100644 --- a/doc/classes/Particles2D.xml +++ b/doc/classes/Particles2D.xml @@ -21,6 +21,7 @@ Returns a rectangle containing the positions of all existing particles. + [b]Note:[/b] When using threaded rendering this method synchronizes the rendering thread. Calling it often may have a negative impact on performance. @@ -83,6 +84,14 @@ Grow the rect if particles suddenly appear/disappear when the node enters/exits the screen. The [Rect2] can be grown via code or with the [b]Particles → Generate Visibility Rect[/b] editor tool. + + + + Emitted when all active particles have finished processing. When [member one_shot] is disabled, particles will process continuously, so this is never emitted. + [b]Note:[/b] Due to the particles being computed on the GPU there might be a delay before the signal gets emitted. + + + Particles are drawn in the order emitted. diff --git a/scene/2d/particles_2d.cpp b/scene/2d/particles_2d.cpp index 185ce24698c..bc57da06c7a 100644 --- a/scene/2d/particles_2d.cpp +++ b/scene/2d/particles_2d.cpp @@ -39,13 +39,30 @@ #endif void Particles2D::set_emitting(bool p_emitting) { - VS::get_singleton()->particles_set_emitting(particles, p_emitting); + // Do not return even if `p_emitting == emitting` because `emitting` is just an approximation. if (p_emitting && one_shot) { + if (!active && !emitting) { + // Last cycle ended. + active = true; + time = 0; + signal_canceled = false; + emission_time = lifetime; + active_time = lifetime * (2 - explosiveness_ratio); + } else { + signal_canceled = true; + } set_process_internal(true); } else if (!p_emitting) { - set_process_internal(false); + if (one_shot) { + set_process_internal(true); + } else { + set_process_internal(false); + } } + + emitting = p_emitting; + VS::get_singleton()->particles_set_emitting(particles, p_emitting); } void Particles2D::set_amount(int p_amount) { @@ -148,7 +165,7 @@ void Particles2D::set_show_visibility_rect(bool p_show_visibility_rect) { #endif bool Particles2D::is_emitting() const { - return VS::get_singleton()->particles_get_emitting(particles); + return emitting; } int Particles2D::get_amount() const { return amount; @@ -287,6 +304,16 @@ void Particles2D::_validate_property(PropertyInfo &property) const { void Particles2D::restart() { VS::get_singleton()->particles_restart(particles); VS::get_singleton()->particles_set_emitting(particles, true); + + emitting = true; + active = true; + signal_canceled = false; + time = 0; + emission_time = lifetime; + active_time = lifetime * (2 - explosiveness_ratio); + if (one_shot) { + set_process_internal(true); + } } void Particles2D::_notification(int p_what) { @@ -322,9 +349,23 @@ void Particles2D::_notification(int p_what) { } if (p_what == NOTIFICATION_INTERNAL_PROCESS) { - if (one_shot && !is_emitting()) { - _change_notify(); - set_process_internal(false); + if (one_shot) { + time += get_process_delta_time(); + if (time > emission_time) { + emitting = false; + if (!active) { + set_process_internal(false); + } + } + if (time > active_time) { + if (active && !signal_canceled) { + emit_signal(SceneStringNames::get_singleton()->finished); + } + active = false; + if (!emitting) { + set_process_internal(false); + } + } } } } @@ -371,6 +412,8 @@ void Particles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("restart"), &Particles2D::restart); + ADD_SIGNAL(MethodInfo("finished")); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting"); ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_EXP_RANGE, "1,1000000,1"), "set_amount", "get_amount"); ADD_GROUP("Time", ""); diff --git a/scene/2d/particles_2d.h b/scene/2d/particles_2d.h index e8f15b4925e..5b0a453716a 100644 --- a/scene/2d/particles_2d.h +++ b/scene/2d/particles_2d.h @@ -48,7 +48,10 @@ public: private: RID particles; - bool one_shot; + bool emitting = false; + bool active = false; + bool signal_canceled = false; + bool one_shot = false; int amount; float lifetime; float pre_process_time; @@ -73,6 +76,10 @@ private: void _update_particle_emission_transform(); + double time = 0.0; + double emission_time = 0.0; + double active_time = 0.0; + protected: static void _bind_methods(); virtual void _validate_property(PropertyInfo &property) const; diff --git a/scene/3d/particles.cpp b/scene/3d/particles.cpp index ef816da6555..97b13b1e482 100644 --- a/scene/3d/particles.cpp +++ b/scene/3d/particles.cpp @@ -32,6 +32,7 @@ #include "core/os/os.h" #include "scene/resources/particles_material.h" +#include "scene/scene_string_names.h" #include "servers/visual_server.h" @@ -43,13 +44,31 @@ PoolVector Particles::get_faces(uint32_t p_usage_flags) const { } void Particles::set_emitting(bool p_emitting) { - VS::get_singleton()->particles_set_emitting(particles, p_emitting); + // Do not return even if `p_emitting == emitting` because `emitting` is just an approximation. if (p_emitting && one_shot) { + if (!active && !emitting) { + // Last cycle ended. + active = true; + time = 0; + signal_canceled = false; + emission_time = lifetime; + active_time = lifetime * (2 - explosiveness_ratio); + } else { + signal_canceled = true; + } set_process_internal(true); } else if (!p_emitting) { set_process_internal(false); + if (one_shot) { + set_process_internal(true); + } else { + set_process_internal(false); + } } + + emitting = p_emitting; + VS::get_singleton()->particles_set_emitting(particles, p_emitting); } void Particles::set_amount(int p_amount) { @@ -118,7 +137,7 @@ void Particles::set_speed_scale(float p_scale) { } bool Particles::is_emitting() const { - return VS::get_singleton()->particles_get_emitting(particles); + return emitting; } int Particles::get_amount() const { return amount; @@ -281,6 +300,16 @@ String Particles::get_configuration_warning() const { void Particles::restart() { VisualServer::get_singleton()->particles_restart(particles); VisualServer::get_singleton()->particles_set_emitting(particles, true); + + emitting = true; + active = true; + signal_canceled = false; + time = 0; + emission_time = lifetime * (1 - explosiveness_ratio); + active_time = lifetime * (2 - explosiveness_ratio); + if (one_shot) { + set_process_internal(true); + } } AABB Particles::capture_aabb() const { @@ -309,9 +338,23 @@ void Particles::_notification(int p_what) { // Use internal process when emitting and one_shot are on so that when // the shot ends the editor can properly update if (p_what == NOTIFICATION_INTERNAL_PROCESS) { - if (one_shot && !is_emitting()) { - _change_notify(); - set_process_internal(false); + if (one_shot) { + time += get_process_delta_time(); + if (time > emission_time) { + emitting = false; + if (!active) { + set_process_internal(false); + } + } + if (time > active_time) { + if (active && !signal_canceled) { + emit_signal(SceneStringNames::get_singleton()->finished); + } + active = false; + if (!emitting) { + set_process_internal(false); + } + } } } @@ -365,6 +408,8 @@ void Particles::_bind_methods() { ClassDB::bind_method(D_METHOD("restart"), &Particles::restart); ClassDB::bind_method(D_METHOD("capture_aabb"), &Particles::capture_aabb); + ADD_SIGNAL(MethodInfo("finished")); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting"); ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_EXP_RANGE, "1,1000000,1"), "set_amount", "get_amount"); ADD_GROUP("Time", ""); diff --git a/scene/3d/particles.h b/scene/3d/particles.h index ed1e2d97a2e..d0ebe1b1793 100644 --- a/scene/3d/particles.h +++ b/scene/3d/particles.h @@ -53,7 +53,10 @@ public: private: RID particles; - bool one_shot; + bool emitting = false; + bool active = false; + bool signal_canceled = false; + bool one_shot = false; int amount; float lifetime; float pre_process_time; @@ -71,6 +74,10 @@ private: Vector> draw_passes; + double time = 0.0; + double emission_time = 0.0; + double active_time = 0.0; + protected: static void _bind_methods(); void _notification(int p_what); diff --git a/scene/scene_string_names.cpp b/scene/scene_string_names.cpp index 27ca186cf92..b155742ac92 100644 --- a/scene/scene_string_names.cpp +++ b/scene/scene_string_names.cpp @@ -61,7 +61,6 @@ SceneStringNames::SceneStringNames() { finished = StaticCString::create("finished"); loop_finished = StaticCString::create("loop_finished"); step_finished = StaticCString::create("step_finished"); - emission_finished = StaticCString::create("emission_finished"); animation_finished = StaticCString::create("animation_finished"); animation_changed = StaticCString::create("animation_changed"); animation_started = StaticCString::create("animation_started"); diff --git a/scene/scene_string_names.h b/scene/scene_string_names.h index 4b74da5415d..3c395dbc5c4 100644 --- a/scene/scene_string_names.h +++ b/scene/scene_string_names.h @@ -93,7 +93,6 @@ public: StringName finished; StringName loop_finished; StringName step_finished; - StringName emission_finished; StringName animation_finished; StringName animation_changed; StringName animation_started;