diff --git a/doc/classes/CPUParticle3D.xml b/doc/classes/CPUParticle3D.xml deleted file mode 100644 index 988b228adcc..00000000000 --- a/doc/classes/CPUParticle3D.xml +++ /dev/null @@ -1,107 +0,0 @@ - - - - Contains information about an individual particle from a [CPUParticles3D] system. - - - Contains information about an individual particle from a [CPUParticles3D] system. - [CPUParticle3D] is emitted as an [Array] of objects by the [signal CPUParticles3D.particles_updated] signal. - This can be used to make nodes (such as [Light3D]s, [AudioStreamPlayer3D]s or other [CPUParticles3D]s) follow particles individually for advanced effects. - [b]Note:[/b] To avoid performance issues, it's recommended to only use this feature with low numbers of particles (typically a few dozen at most). - [b]Example of placing [OmniLight3D]s to follow particles automatically and change color over time:[/b] - [codeblock] - extends CPUParticles3D - - # Used to keep track of light nodes added as children more easily. - var lights = [] - - func _ready(): - for i in amount: - var light = OmniLight3D.new() - lights.push_back(light) - add_child(light) - - particles_updated.connect(_on_cpu_particles_3d_particles_updated) - - - func _on_cpu_particles_3d_particles_updated(particles): - # Only update light positions if all light nodes have been added first. - if lights.size() >= particles.size(): - for particle_idx in particles.size(): - lights[particle_idx].visible = particles[particle_idx].is_active() - - # Change the light's color over the particle's lifetime. - lights[particle_idx].light_color.r = particles[particle_idx].get_phase() - lights[particle_idx].light_color.g = 1.0 - particles[particle_idx].get_phase() - lights[particle_idx].light_color.b = 1.0 - particles[particle_idx].get_phase() - lights[particle_idx].light_energy = 2.0 - particles[particle_idx].get_phase() * 2.0 - - # Increase the light's range over the particle's lifetime. - lights[particle_idx].omni_range = particles[particle_idx].get_phase() * 5.0 - - # Move lights to follow particles. - if local_coords: - lights[particle_idx].position = particles[particle_idx].get_transform().origin - else: - lights[particle_idx].global_transform.origin = particles[particle_idx].get_transform().origin - [/codeblock] - - - - - - - - Returns the particle's current color. This is the color defined by [member CPUParticles3D.color] or [member CPUParticles3D.color_initial_ramp], which is then multiplied by [member CPUParticles3D.color_ramp] over the particle's lifetime. - [b]Note:[/b] [method get_color] does not take the material albedo color or mesh's original vertex color into account. - - - - - - Returns the particle's lifetime percentage. This is close to [code]0.0[/code] when the particle is freshly spawned, and close to [code]1.0[/code] when the particle is about to expire. - [b]Note:[/b] If the particle is inactive ([method is_active] returns [code]false[/code]), [method get_phase] returns [code]0.0[/code]. - - - - - - Returns the particle's random seed (a 32-bit [i]unsigned[/i] integer) within the particle system. This can be used to uniquely identify a given particle within the particle system during its lifetime. - Since the seed is uniformly distributed, [method get_seed] can be used to selectively apply effects to certain particles only. This can be useful to improve performance by applying expensive effects to lower amounts of particles: - [codeblock] - # `particles` is an array of CPUParticle3D results from CPUParticles3D's `particles_updated` signal. - for particle in particles: - # The maximum value of a 32-bit unsigned integer is (2 ^ 32) - 1, which is roughly 4.2 billion. - if particle.get_seed() <= 2 << 31: # Lower than roughly 2.1 billion. - # Apply an effect to roughly 50% of particles here (the exact amount is random and varies constantly). - pass - [/codeblock] - [b]Note:[/b] When a particle respawns after expiring, it will generate and use a different seed. This means [method get_seed] cannot be used to identify a given particle after it respawns. - - - - - - Returns the particle's transform. If you're only interested in the particle's position, use [code]get_transform().origin[/code]. - [b]Note:[/b] If [member CPUParticles3D.local_coords] is [code]true[/code], you'll want to set nodes' [i]local[/i] transform when moving them to match particle positions. If [member CPUParticles3D.local_coords] is [code]false[/code], you'll want to set nodes' [i]global[/i] transform when moving them to match particle positions. - [b]Note:[/b] If the particle is inactive ([method is_active] returns [code]false[/code]), [method get_transform] returns an identity [code]Transform3D()[/code]. - - - - - - Returns the particle's speed in units per second on each axis. - [b]Note:[/b] If the particle is inactive ([method is_active] returns [code]false[/code]), [method get_velocity] returns [code]Vector3(0, 0, 0)[/code]. - - - - - - Returns [code]true[/code] if the particle is currently alive, [code]false[/code] otherwise. - Typically, all particles are active except in two scenarios: - - The particle system has just started and its [member CPUParticles3D.explosiveness] is not equal to [code]1.0[/code]. Therefore, there are less active particles than the [member CPUParticles3D.amount] configured. - - The particle system has just had [member CPUParticles3D.emitting] set to [code]false[/code]. After all particles have expired (plus some additional time), the signal will no longer be emitted. - - - - diff --git a/doc/classes/CPUParticles3D.xml b/doc/classes/CPUParticles3D.xml index 2c674293b3b..41ca102f8cf 100644 --- a/doc/classes/CPUParticles3D.xml +++ b/doc/classes/CPUParticles3D.xml @@ -7,6 +7,44 @@ CPU-based 3D particle node used to create a variety of particle systems and effects. See also [GPUParticles3D], which provides the same functionality with hardware acceleration, but may not run on older devices. Unlike [GPUParticles3D], the CPU is aware of particles' individual transforms. This means particle transforms can be used for any purpose in scripting, including gameplay-affecting elements. See [signal particles_updated] for more information. + CPUParticles3D can emit an [Array] of dictionaries with the [signal CPUParticles3D.particles_updated] signal. This can be used to make nodes (such as [Light3D]s, [AudioStreamPlayer3D]s or other [CPUParticles3D]s) follow particles individually for advanced effects. To avoid performance issues, it's recommended to only use this feature with low numbers of particles (typically a few dozen at most). + [b]Example of placing [OmniLight3D]s to follow particles automatically and change color over time:[/b] + [codeblock] + extends CPUParticles3D + + # Used to keep track of light nodes added as children more easily. + var lights = [] + + func _ready(): + for i in amount: + var light = OmniLight3D.new() + lights.push_back(light) + add_child(light) + + particles_updated.connect(_on_cpu_particles_3d_particles_updated) + + + func _on_cpu_particles_3d_particles_updated(particles): + # Only update light positions if all light nodes have been added first. + if lights.size() >= particles.size(): + for particle_idx in particles.size(): + lights[particle_idx].visible = particles[particle_idx].active + + # Change the light's color over the particle's lifetime. + lights[particle_idx].light_color.r = particles[particle_idx].phase + lights[particle_idx].light_color.g = 1.0 - particles[particle_idx].phase + lights[particle_idx].light_color.b = 1.0 - particles[particle_idx].phase + lights[particle_idx].light_energy = 2.0 - particles[particle_idx].phase * 2.0 + + # Increase the light's range over the particle's lifetime. + lights[particle_idx].omni_range = particles[particle_idx].phase * 5.0 + + # Move lights to follow particles. + if local_coords: + lights[particle_idx].position = particles[particle_idx].transform.origin + else: + lights[particle_idx].global_transform.origin = particles[particle_idx].transform.origin + [/codeblock] @@ -317,16 +355,32 @@ - + Emitted when one or more particles is about to expire on the next particle update. This can be used to make nodes (such as [Light3D]s, [AudioStreamPlayer3D]s or other [CPUParticles3D]s) spawn at individual particle expiration positions. + Each particle has an associated dictionary in the array parameter with the following keys: + - [code]color[/code]: The particle's current color. This is the color defined by [member CPUParticles3D.color] or [member CPUParticles3D.color_initial_ramp], which is then multiplied by [member CPUParticles3D.color_ramp] over the particle's lifetime. [code]color[/code] does [i]not[/i] take the material albedo color or mesh's original vertex color into account. If the particle is inactive ([code]active[/code] is [code]false[/code]), [code]color[/code] is [code]Color(0, 0, 0, 1)[/code]. + - [code]phase[/code]: The particle's lifetime ratio. This is close to [code]0.0[/code] when the particle is freshly spawned, and close to [code]1.0[/code] when the particle is about to expire. If the particle is inactive ([code]active[/code] is [code]false[/code]), [code]phase[/code] is [code]0.0[/code]. + - [code]seed[/code]: The particle's random seed (a 32-bit [i]unsigned[/i] integer) within the particle system. This can be used to uniquely identify a given particle within the particle system during its lifetime. Since the seed is uniformly distributed, [code]seed[/code] can be used to selectively apply effects to certain particles only. This can be useful to improve performance by applying expensive effects to lower amounts of particles: + [codeblock] + # `particles` is an array of CPUParticle3D results from CPUParticles3D's `particles_updated` signal. + for particle in particles: + # The maximum value of a 32-bit unsigned integer is (2 ^ 32) - 1, which is roughly 4.2 billion. + if particle.seed <= 2 << 31: # Lower than roughly 2.1 billion. + # Apply an effect to roughly 50% of particles here (the exact amount is random and varies constantly). + pass + [/codeblock] + When a particle respawns after expiring, it will generate and use a different seed. This means [code]seed[/code] cannot be used to identify a given particle after it respawns. If the particle is inactive ([code]active[/code] is [code]false[/code]), [code]seed[/code] is [code]0[/code]. + - [code]transform[/code]: The particle's transform. If you're only interested in the particle's position, use [code]transform.origin[/code]. If [member CPUParticles3D.local_coords] is [code]true[/code], you'll want to set nodes' [i]local[/i] transform when moving them to match particle positions. If [member CPUParticles3D.local_coords] is [code]false[/code], you'll want to set nodes' [i]global[/i] transform when moving them to match particle positions. If the particle is inactive ([code]active[/code] is [code]false[/code]), [code]transform[/code] is an identity [code]Transform3D()[/code]. + - [code]velocity[/code]: The particle's linear velocity (movement speed) in units per second on each axis. This doesn't take the node's movement into account if [member local_coords] is [code]true[/code]. If the particle is inactive ([code]active[/code] is [code]false[/code]), [code]velocity[/code] is [code]Vector3(0, 0, 0)[/code]. + - [code]active[/code]: [code]true[/code] if the particle is currently alive, [code]false[/code] otherwise. Typically, all particles are active except in two scenarios: (1) The particle system has just started and its [member CPUParticles3D.explosiveness] is not equal to [code]1.0[/code]. Therefore, there are less active particles than the [member CPUParticles3D.amount] configured. (2) The particle system has just had [member CPUParticles3D.emitting] set to [code]false[/code]. After all particles have expired (plus some additional time), the signal will no longer be emitted. [b]Note:[/b] Only emitted when the particle system is currently [member emitting] or when at least 1 particle is still active. - + - Emitted when one or more particles are updated (every [code]1.0 /[/code] [member fixed_fps] seconds, or every rendered frame if [member fixed_fps] is [code]0[/code]). This can be used to make nodes (such as [Light3D]s, [AudioStreamPlayer3D]s or other [CPUParticles3D]s) follow particles individually for advanced effects. See [CPUParticle3D] to access information on each individual particle. + Emitted when one or more particles are updated (every [code]1.0 /[/code] [member fixed_fps] seconds, or every rendered frame if [member fixed_fps] is [code]0[/code]). This can be used to make nodes (such as [Light3D]s, [AudioStreamPlayer3D]s or other [CPUParticles3D]s) follow particles individually for advanced effects. See [signal particles_expired]'s description for information on dictionary keys contained within the array parameter. [b]Note:[/b] Only emitted when the particle system is currently [member emitting] or when at least 1 particle is still active. diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp index 41453fb6f9d..6037fd5d900 100644 --- a/scene/3d/cpu_particles_3d.cpp +++ b/scene/3d/cpu_particles_3d.cpp @@ -679,7 +679,14 @@ void CPUParticles3D::_particles_process(double p_delta) { for (int i = 0; i < pcount; i++) { Particle &p = parray[i]; - Ref cpu_particle = memnew(CPUParticle3D); + Dictionary cpu_particle; + // These will be overridden if the particle is active. + cpu_particle["active"] = false; + cpu_particle["transform"] = Transform3D(); + cpu_particle["color"] = Color(); + cpu_particle["velocity"] = Vector3(); + cpu_particle["phase"] = 0.0f; + cpu_particle["seed"] = 0; if (!emitting && !p.active) { cpu_particles.set(i, cpu_particle); @@ -1151,12 +1158,12 @@ void CPUParticles3D::_particles_process(double p_delta) { // If we got down here, we got past all the `continue`s from inactive particles. // Therefore, the particle is active by definition. - cpu_particle->active = true; - cpu_particle->transform = p.transform; - cpu_particle->color = p.color; - cpu_particle->velocity = p.velocity; - cpu_particle->phase = p.time; - cpu_particle->seed = p.seed; + cpu_particle["active"] = true; + cpu_particle["transform"] = p.transform; + cpu_particle["color"] = p.color; + cpu_particle["velocity"] = p.velocity; + cpu_particle["phase"] = p.time; + cpu_particle["seed"] = p.seed; cpu_particles.set(i, cpu_particle); // Empirically determined to work at Fixed FPS set to 0 (depends on rendering framerate), @@ -1486,8 +1493,8 @@ void CPUParticles3D::_bind_methods() { ClassDB::bind_method(D_METHOD("restart"), &CPUParticles3D::restart); - ADD_SIGNAL(MethodInfo("particles_updated", PropertyInfo(Variant::ARRAY, "particles", PROPERTY_HINT_ARRAY_TYPE, "CPUParticle3D"))); - ADD_SIGNAL(MethodInfo("particles_expired", PropertyInfo(Variant::ARRAY, "particles", PROPERTY_HINT_ARRAY_TYPE, "CPUParticle3D"))); + ADD_SIGNAL(MethodInfo("particles_updated", PropertyInfo(Variant::ARRAY, "particles", PROPERTY_HINT_ARRAY_TYPE, "Dictionary"))); + ADD_SIGNAL(MethodInfo("particles_expired", PropertyInfo(Variant::ARRAY, "particles", PROPERTY_HINT_ARRAY_TYPE, "Dictionary"))); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting"); ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount"); @@ -1752,38 +1759,3 @@ CPUParticles3D::~CPUParticles3D() { ERR_FAIL_NULL(RenderingServer::get_singleton()); RS::get_singleton()->free(multimesh); } - -// CPUParticle3D - -bool CPUParticle3D::is_active() const { - return active; -} - -Transform3D CPUParticle3D::get_transform() const { - return transform; -} - -Color CPUParticle3D::get_color() const { - return color; -} - -Vector3 CPUParticle3D::get_velocity() const { - return velocity; -} - -float CPUParticle3D::get_phase() const { - return phase; -} - -uint32_t CPUParticle3D::get_seed() const { - return seed; -} - -void CPUParticle3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("is_active"), &CPUParticle3D::is_active); - ClassDB::bind_method(D_METHOD("get_transform"), &CPUParticle3D::get_transform); - ClassDB::bind_method(D_METHOD("get_color"), &CPUParticle3D::get_color); - ClassDB::bind_method(D_METHOD("get_velocity"), &CPUParticle3D::get_velocity); - ClassDB::bind_method(D_METHOD("get_phase"), &CPUParticle3D::get_phase); - ClassDB::bind_method(D_METHOD("get_seed"), &CPUParticle3D::get_seed); -} diff --git a/scene/3d/cpu_particles_3d.h b/scene/3d/cpu_particles_3d.h index 4d4247fbe54..d2e21906ef6 100644 --- a/scene/3d/cpu_particles_3d.h +++ b/scene/3d/cpu_particles_3d.h @@ -320,31 +320,4 @@ VARIANT_ENUM_CAST(CPUParticles3D::Parameter) VARIANT_ENUM_CAST(CPUParticles3D::ParticleFlags) VARIANT_ENUM_CAST(CPUParticles3D::EmissionShape) -/** - * Higher-level version of the Particle struct. Unlike the Particle struct, - * CPUParticle3D is exposed to the scripting API for use by the `particles_updated` signal. - */ -class CPUParticle3D : public RefCounted { - GDCLASS(CPUParticle3D, RefCounted); - - friend class CPUParticles3D; - - bool active = false; - Transform3D transform; - Color color; - Vector3 velocity; - float phase = 0.0f; - uint32_t seed = 0; - -protected: - static void _bind_methods(); - - bool is_active() const; - Transform3D get_transform() const; - Color get_color() const; - Vector3 get_velocity() const; - float get_phase() const; - uint32_t get_seed() const; -}; - #endif // CPU_PARTICLES_3D_H diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 3118ae53838..fdb2b4909f1 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -543,7 +543,6 @@ void register_scene_types() { GDREGISTER_CLASS(GPUParticlesAttractorSphere3D); GDREGISTER_CLASS(GPUParticlesAttractorVectorField3D); GDREGISTER_CLASS(CPUParticles3D); - GDREGISTER_CLASS(CPUParticle3D); GDREGISTER_CLASS(Marker3D); GDREGISTER_CLASS(RootMotionView);