diff --git a/doc/classes/GPUParticles2D.xml b/doc/classes/GPUParticles2D.xml index c4193a5b010..e6d3c130cae 100644 --- a/doc/classes/GPUParticles2D.xml +++ b/doc/classes/GPUParticles2D.xml @@ -18,6 +18,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. @@ -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. + + + + 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/GPUParticles3D.xml b/doc/classes/GPUParticles3D.xml index 8338535a24e..0dc7c23315d 100644 --- a/doc/classes/GPUParticles3D.xml +++ b/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. + + + + 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/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp index 735159c6606..06215245a15 100644 --- a/scene/2d/gpu_particles_2d.cpp +++ b/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"); diff --git a/scene/2d/gpu_particles_2d.h b/scene/2d/gpu_particles_2d.h index e518ffec6f1..3131698e5c3 100644 --- a/scene/2d/gpu_particles_2d.h +++ b/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(); diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp index 4d0bc8b02f8..fb889c928d1 100644 --- a/scene/3d/gpu_particles_3d.cpp +++ b/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"); diff --git a/scene/3d/gpu_particles_3d.h b/scene/3d/gpu_particles_3d.h index 474f5500f8c..dba6a8f2ab3 100644 --- a/scene/3d/gpu_particles_3d.h +++ b/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> draw_passes; Ref skin; + double time = 0.0; + double emission_time = 0.0; + double active_time = 0.0; + void _attach_sub_emitter(); void _skinning_changed(); diff --git a/scene/scene_string_names.cpp b/scene/scene_string_names.cpp index 536ffd1fe48..e2eed3ccd37 100644 --- a/scene/scene_string_names.cpp +++ b/scene/scene_string_names.cpp @@ -57,7 +57,6 @@ SceneStringNames::SceneStringNames() { sleeping_state_changed = StaticCString::create("sleeping_state_changed"); finished = StaticCString::create("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 ca8f7a1e7df..41ba26575d9 100644 --- a/scene/scene_string_names.h +++ b/scene/scene_string_names.h @@ -93,7 +93,6 @@ public: StringName sort_children; StringName finished; - StringName emission_finished; StringName animation_finished; StringName animation_changed; StringName animation_started;