Просмотр исходного кода

Merge pull request #76859 from HolonProduction/emission-finished-gpu

Add `finished` signal to GPUParticles
Yuri Sizov 2 лет назад
Родитель
Сommit
0f7625ab46

+ 9 - 0
doc/classes/GPUParticles2D.xml

@@ -18,6 +18,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="emit_particle">
@@ -108,6 +109,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.

+ 8 - 0
doc/classes/GPUParticles3D.xml

@@ -130,6 +130,14 @@
 			Grow the box if particles suddenly appear/disappear when the node enters/exits the screen. The [AABB] can be grown via code or with the [b]Particles → Generate AABB[/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.

+ 50 - 6
scene/2d/gpu_particles_2d.cpp

@@ -32,19 +32,37 @@
 
 #include "core/core_string_names.h"
 #include "scene/resources/particle_process_material.h"
+#include "scene/scene_string_names.h"
 
 #ifdef TOOLS_ENABLED
 #include "core/config/engine.h"
 #endif
 
 void GPUParticles2D::set_emitting(bool p_emitting) {
-	RS::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_cancled = false;
+			emission_time = lifetime;
+			active_time = lifetime * (2 - explosiveness_ratio);
+		} else {
+			signal_cancled = 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;
+	RS::get_singleton()->particles_set_emitting(particles, p_emitting);
 }
 
 void GPUParticles2D::set_amount(int p_amount) {
@@ -211,7 +229,7 @@ void GPUParticles2D::set_speed_scale(double p_scale) {
 }
 
 bool GPUParticles2D::is_emitting() const {
-	return RS::get_singleton()->particles_get_emitting(particles);
+	return emitting;
 }
 
 int GPUParticles2D::get_amount() const {
@@ -405,6 +423,16 @@ NodePath GPUParticles2D::get_sub_emitter() const {
 void GPUParticles2D::restart() {
 	RS::get_singleton()->particles_restart(particles);
 	RS::get_singleton()->particles_set_emitting(particles, true);
+
+	emitting = true;
+	active = true;
+	signal_cancled = false;
+	time = 0;
+	emission_time = lifetime;
+	active_time = lifetime * (2 - explosiveness_ratio);
+	if (one_shot) {
+		set_process_internal(true);
+	}
 }
 
 void GPUParticles2D::_notification(int p_what) {
@@ -570,9 +598,23 @@ void GPUParticles2D::_notification(int p_what) {
 		} break;
 
 		case NOTIFICATION_INTERNAL_PROCESS: {
-			if (one_shot && !is_emitting()) {
-				notify_property_list_changed();
-				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_cancled) {
+						emit_signal(SceneStringNames::get_singleton()->finished);
+					}
+					active = false;
+					if (!emitting) {
+						set_process_internal(false);
+					}
+				}
 			}
 		} break;
 	}
@@ -638,6 +680,8 @@ void GPUParticles2D::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_trail_section_subdivisions", "subdivisions"), &GPUParticles2D::set_trail_section_subdivisions);
 	ClassDB::bind_method(D_METHOD("get_trail_section_subdivisions"), &GPUParticles2D::get_trail_section_subdivisions);
 
+	ADD_SIGNAL(MethodInfo("finished"));
+
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting");
 	ADD_PROPERTY_DEFAULT("emitting", true); // Workaround for doctool in headless mode, as dummy rasterizer always returns false.
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount");

+ 7 - 0
scene/2d/gpu_particles_2d.h

@@ -47,6 +47,9 @@ public:
 private:
 	RID particles;
 
+	bool emitting = false;
+	bool active = false;
+	bool signal_cancled = false;
 	bool one_shot = false;
 	int amount = 0;
 	double lifetime = 0.0;
@@ -78,6 +81,10 @@ private:
 	int trail_sections = 8;
 	int trail_section_subdivisions = 4;
 
+	double time = 0.0;
+	double emission_time = 0.0;
+	double active_time = 0.0;
+
 	RID mesh;
 
 	void _attach_sub_emitter();

+ 50 - 6
scene/3d/gpu_particles_3d.cpp

@@ -31,19 +31,37 @@
 #include "gpu_particles_3d.h"
 
 #include "scene/resources/particle_process_material.h"
+#include "scene/scene_string_names.h"
 
 AABB GPUParticles3D::get_aabb() const {
 	return AABB();
 }
 
 void GPUParticles3D::set_emitting(bool p_emitting) {
-	RS::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_cancled = false;
+			emission_time = lifetime;
+			active_time = lifetime * (2 - explosiveness_ratio);
+		} else {
+			signal_cancled = 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;
+	RS::get_singleton()->particles_set_emitting(particles, p_emitting);
 }
 
 void GPUParticles3D::set_amount(int p_amount) {
@@ -122,7 +140,7 @@ void GPUParticles3D::set_collision_base_size(real_t p_size) {
 }
 
 bool GPUParticles3D::is_emitting() const {
-	return RS::get_singleton()->particles_get_emitting(particles);
+	return emitting;
 }
 
 int GPUParticles3D::get_amount() const {
@@ -373,6 +391,16 @@ PackedStringArray GPUParticles3D::get_configuration_warnings() const {
 void GPUParticles3D::restart() {
 	RenderingServer::get_singleton()->particles_restart(particles);
 	RenderingServer::get_singleton()->particles_set_emitting(particles, true);
+
+	emitting = true;
+	active = true;
+	signal_cancled = false;
+	time = 0;
+	emission_time = lifetime * (1 - explosiveness_ratio);
+	active_time = lifetime * (2 - explosiveness_ratio);
+	if (one_shot) {
+		set_process_internal(true);
+	}
 }
 
 AABB GPUParticles3D::capture_aabb() const {
@@ -425,9 +453,23 @@ void GPUParticles3D::_notification(int p_what) {
 		// Use internal process when emitting and one_shot is on so that when
 		// the shot ends the editor can properly update.
 		case NOTIFICATION_INTERNAL_PROCESS: {
-			if (one_shot && !is_emitting()) {
-				notify_property_list_changed();
-				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_cancled) {
+						emit_signal(SceneStringNames::get_singleton()->finished);
+					}
+					active = false;
+					if (!emitting) {
+						set_process_internal(false);
+					}
+				}
 			}
 		} break;
 
@@ -571,6 +613,8 @@ void GPUParticles3D::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_transform_align", "align"), &GPUParticles3D::set_transform_align);
 	ClassDB::bind_method(D_METHOD("get_transform_align"), &GPUParticles3D::get_transform_align);
 
+	ADD_SIGNAL(MethodInfo("finished"));
+
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting");
 	ADD_PROPERTY_DEFAULT("emitting", true); // Workaround for doctool in headless mode, as dummy rasterizer always returns false.
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount");

+ 8 - 1
scene/3d/gpu_particles_3d.h

@@ -60,7 +60,10 @@ public:
 private:
 	RID particles;
 
-	bool one_shot;
+	bool emitting = false;
+	bool active = false;
+	bool signal_cancled = false;
+	bool one_shot = false;
 	int amount = 0;
 	double lifetime = 0.0;
 	double pre_process_time = 0.0;
@@ -87,6 +90,10 @@ private:
 	Vector<Ref<Mesh>> draw_passes;
 	Ref<Skin> skin;
 
+	double time = 0.0;
+	double emission_time = 0.0;
+	double active_time = 0.0;
+
 	void _attach_sub_emitter();
 
 	void _skinning_changed();