Bläddra i källkod

Merge pull request #81559 from matorin57/3.x-backport-finished-singal-GPU-particles

[3.x] Backport "Add `finished` signal to GPUParticles"
Rémi Verschelde 1 år sedan
förälder
incheckning
862d63e9f7

+ 8 - 0
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.
 		</member>
 	</members>
+	<signals>
+		<signal name="finished">
+			<description>
+				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.
+			</description>
+		</signal>
+	</signals>
 	<constants>
 		<constant name="DRAW_ORDER_INDEX" value="0" enum="DrawOrder">
 			Particles are drawn in the order emitted.

+ 9 - 0
doc/classes/Particles2D.xml

@@ -21,6 +21,7 @@
 			<return type="Rect2" />
 			<description>
 				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.
 			</description>
 		</method>
 		<method name="restart">
@@ -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.
 		</member>
 	</members>
+	<signals>
+		<signal name="finished">
+			<description>
+				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.
+			</description>
+		</signal>
+	</signals>
 	<constants>
 		<constant name="DRAW_ORDER_INDEX" value="0" enum="DrawOrder">
 			Particles are drawn in the order emitted.

+ 49 - 6
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", "");

+ 8 - 1
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;

+ 50 - 5
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<Face3> 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", "");

+ 8 - 1
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<Ref<Mesh>> 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);

+ 0 - 1
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");

+ 0 - 1
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;