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;
]