Skip to content

Commit d295832

Browse files
committed
Improve Particle node defaults when created in the editor
This aims to improve the particle system's usability by setting sensible defaults automatically. This should greatly reduce the number of clicks required to achieve a fully functional particle setup (with scale, color and flipbook animation support). For compatibility reasons, Particles created via code or in older versions of Godot are not affected.
1 parent b415857 commit d295832

6 files changed

Lines changed: 272 additions & 0 deletions

‎editor/scene/2d/particles_2d_editor_plugin.cpp‎

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "core/io/image_loader.h"
3434
#include "core/object/callable_mp.h"
3535
#include "core/os/os.h"
36+
#include "editor/editor_interface.h"
3637
#include "editor/editor_node.h"
3738
#include "editor/editor_string_names.h"
3839
#include "editor/editor_undo_redo_manager.h"
@@ -47,6 +48,42 @@
4748
#include "scene/resources/image_texture.h"
4849
#include "scene/resources/particle_process_material.h"
4950

51+
void GPUParticles2DEditorPlugin::_notification(int p_what) {
52+
switch (p_what) {
53+
case NOTIFICATION_READY: {
54+
EditorInterface::get_singleton()->connect("object_created_in_editor", callable_mp(this, &GPUParticles2DEditorPlugin::_object_created_in_editor));
55+
} break;
56+
}
57+
}
58+
59+
void GPUParticles2DEditorPlugin::_object_created_in_editor(Object *p_object) {
60+
GPUParticles2D *gpu_particles_2d = Object::cast_to<GPUParticles2D>(p_object);
61+
if (gpu_particles_2d) {
62+
// Increase the particle amount somewhat to make moving particle systems more visible out of the box.
63+
gpu_particles_2d->set_amount(30);
64+
65+
// Simulate particles at the render framerate to avoid issues with interpolation.
66+
// Enabling trails will internally force fixed FPS to 30 anyway (without changing the value of the property).
67+
gpu_particles_2d->set_fixed_fps(0);
68+
69+
gpu_particles_2d->set_process_material(get_material_for_object_created_in_editor());
70+
Ref<ParticleProcessMaterial> process_material = gpu_particles_2d->get_process_material();
71+
if (process_material.is_valid()) {
72+
// Set gravity according to the 2D project settings.
73+
const float default_gravity = GLOBAL_GET("physics/2d/default_gravity");
74+
const Vector2 default_gravity_vector = GLOBAL_GET("physics/2d/default_gravity_vector");
75+
process_material->set_gravity(Vector3(default_gravity_vector.x, default_gravity_vector.y, 0.0) * default_gravity);
76+
77+
// Emit particles upwards, so that they are visible even when the particles node is placed on the ground.
78+
// Adjust for the default gravity, so that particles are emitted upwards even when gravity is set to a high value.
79+
process_material->set_direction(Vector3(0, -1, 0));
80+
process_material->set_param_min(ParticleProcessMaterial::PARAM_INITIAL_LINEAR_VELOCITY, MAX(100.0, default_gravity * 0.4));
81+
}
82+
83+
gpu_particles_2d->set_texture(get_texture_for_object_created_in_editor());
84+
}
85+
}
86+
5087
void GPUParticles2DEditorPlugin::_menu_callback(int p_idx) {
5188
if (p_idx == MENU_GENERATE_VISIBILITY_RECT) {
5289
if (need_show_lifetime_dialog(generate_seconds)) {
@@ -674,6 +711,57 @@ GPUParticles2DEditorPlugin::GPUParticles2DEditorPlugin() {
674711
generate_visibility_rect->connect(SceneStringName(confirmed), callable_mp(this, &GPUParticles2DEditorPlugin::_generate_visibility_rect));
675712
}
676713

714+
void CPUParticles2DEditorPlugin::_notification(int p_what) {
715+
switch (p_what) {
716+
case NOTIFICATION_READY: {
717+
EditorInterface::get_singleton()->connect("object_created_in_editor", callable_mp(this, &CPUParticles2DEditorPlugin::_object_created_in_editor));
718+
} break;
719+
}
720+
}
721+
722+
void CPUParticles2DEditorPlugin::_object_created_in_editor(Object *p_object) {
723+
CPUParticles2D *cpu_particles_2d = Object::cast_to<CPUParticles2D>(p_object);
724+
if (cpu_particles_2d) {
725+
// Increase the particle amount somewhat to make moving particle systems more visible out of the box.
726+
cpu_particles_2d->set_amount(30);
727+
728+
// Reduce particle spread to make the particle node's rotation easier to notice.
729+
cpu_particles_2d->set_spread(5.0);
730+
731+
// Randomize initial particle rotation.
732+
cpu_particles_2d->set_param_min(CPUParticles2D::PARAM_ANGLE, 0.0);
733+
cpu_particles_2d->set_param_max(CPUParticles2D::PARAM_ANGLE, 360.0);
734+
735+
// Scale particles up and down as the lifetime progresses.
736+
Ref<Curve> curve = memnew(Curve);
737+
// The wind-up occurs significantly faster than the fade-out.
738+
curve->add_point(Vector2(0.0, 0.0));
739+
curve->add_point(Vector2(0.1, 1.0));
740+
curve->add_point(Vector2(1.0, 0.0));
741+
cpu_particles_2d->set_param_curve(CPUParticles2D::PARAM_SCALE, curve);
742+
743+
// Fade particles with transparency as the lifetime progresses.
744+
Ref<Gradient> gradient = memnew(Gradient);
745+
// The fade-in occurs significantly faster than the fade-out.
746+
gradient->set_color(0, Color(1, 1, 1, 0));
747+
gradient->set_color(1, Color(1, 1, 1, 0));
748+
gradient->add_point(0.1, Color(1, 1, 1, 1));
749+
cpu_particles_2d->set_color_ramp(gradient);
750+
751+
// Set gravity according to the 2d project settings.
752+
const float default_gravity = GLOBAL_GET("physics/2d/default_gravity");
753+
const Vector2 default_gravity_vector = GLOBAL_GET("physics/2d/default_gravity_vector");
754+
cpu_particles_2d->set_gravity(default_gravity_vector * default_gravity);
755+
756+
// Emit particles upwards, so that they are visible even when the particles node is placed on the ground.
757+
// Adjust for the default gravity, so that particles are emitted upwards even when gravity is set to a high value.
758+
cpu_particles_2d->set_direction(Vector2(0, -1));
759+
cpu_particles_2d->set_param_min(CPUParticles2D::PARAM_INITIAL_LINEAR_VELOCITY, MAX(100.0, default_gravity * 0.4));
760+
761+
cpu_particles_2d->set_texture(get_texture_for_object_created_in_editor();
762+
}
763+
}
764+
677765
Node *CPUParticles2DEditorPlugin::_convert_particles() {
678766
CPUParticles2D *particles = Object::cast_to<CPUParticles2D>(edited_node);
679767

‎editor/scene/2d/particles_2d_editor_plugin.h‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,10 @@ class GPUParticles2DEditorPlugin : public Particles2DEditorPlugin {
117117
SpinBox *generate_seconds = nullptr;
118118

119119
void _generate_visibility_rect();
120+
void _object_created_in_editor(Object *p_object);
120121

121122
protected:
123+
void _notification(int p_what);
122124
void _menu_callback(int p_idx) override;
123125
void _add_menu_options(PopupMenu *p_menu) override;
124126

@@ -133,7 +135,10 @@ class GPUParticles2DEditorPlugin : public Particles2DEditorPlugin {
133135
class CPUParticles2DEditorPlugin : public Particles2DEditorPlugin {
134136
GDCLASS(CPUParticles2DEditorPlugin, Particles2DEditorPlugin);
135137

138+
void _object_created_in_editor(Object *p_object);
139+
136140
protected:
141+
void _notification(int p_what);
137142
Node *_convert_particles() override;
138143

139144
void _generate_emission_mask() override;

‎editor/scene/3d/particles_3d_editor_plugin.cpp‎

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
#include "core/object/callable_mp.h"
3434
#include "core/os/os.h"
35+
#include "editor/editor_interface.h"
3536
#include "editor/editor_node.h"
3637
#include "editor/editor_undo_redo_manager.h"
3738
#include "editor/scene/scene_tree_editor.h"
@@ -41,9 +42,29 @@
4142
#include "scene/gui/box_container.h"
4243
#include "scene/gui/option_button.h"
4344
#include "scene/gui/spin_box.h"
45+
#include "scene/resources/3d/primitive_meshes.h"
4446
#include "scene/resources/image_texture.h"
4547
#include "scene/resources/particle_process_material.h"
4648

49+
Ref<Mesh> Particles3DEditorPlugin::get_mesh_for_node_created_in_editor() const {
50+
Ref<QuadMesh> quad_mesh = memnew(QuadMesh);
51+
quad_mesh->set_size(Vector2(0.1, 0.1));
52+
53+
// Create material to display billboarded texture that follows the color and scale ramps
54+
// defined in ParticleProcessMaterial.
55+
Ref<StandardMaterial3D> material = memnew(StandardMaterial3D);
56+
material->set_shading_mode(BaseMaterial3D::SHADING_MODE_UNSHADED);
57+
material->set_transparency(BaseMaterial3D::TRANSPARENCY_ALPHA);
58+
material->set_flag(BaseMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
59+
material->set_flag(BaseMaterial3D::FLAG_BILLBOARD_KEEP_SCALE, true);
60+
material->set_billboard_mode(BaseMaterial3D::BILLBOARD_PARTICLES);
61+
62+
material->set_texture(BaseMaterial3D::TEXTURE_ALBEDO, get_texture_for_object_created_in_editor());
63+
quad_mesh->set_material(material);
64+
65+
return quad_mesh;
66+
}
67+
4768
void Particles3DEditorPlugin::_generate_aabb() {
4869
double time = generate_seconds->get_value();
4970

@@ -316,6 +337,42 @@ Particles3DEditorPlugin::Particles3DEditorPlugin() {
316337
emission_dialog->connect(SceneStringName(confirmed), callable_mp(this, &Particles3DEditorPlugin::_generate_emission_points));
317338
}
318339

340+
void GPUParticles3DEditorPlugin::_notification(int p_what) {
341+
switch (p_what) {
342+
case NOTIFICATION_READY: {
343+
EditorInterface::get_singleton()->connect("object_created_in_editor", callable_mp(this, &GPUParticles3DEditorPlugin::_object_created_in_editor));
344+
} break;
345+
}
346+
}
347+
348+
void GPUParticles3DEditorPlugin::_object_created_in_editor(Object *p_object) {
349+
GPUParticles3D *gpu_particles_3d = Object::cast_to<GPUParticles3D>(p_object);
350+
if (gpu_particles_3d) {
351+
// Increase the particle amount somewhat to make moving particle systems more visible out of the box.
352+
gpu_particles_3d->set_amount(30);
353+
354+
// Simulate particles at the render framerate to avoid issues with interpolation.
355+
// Enabling trails will internally force fixed FPS to 30 anyway (without changing the value of the property).
356+
gpu_particles_3d->set_fixed_fps(0);
357+
358+
gpu_particles_3d->set_process_material(get_material_for_object_created_in_editor());
359+
Ref<ParticleProcessMaterial> process_material = gpu_particles_3d->get_process_material();
360+
if (process_material.is_valid()) {
361+
// Set gravity according to the 3D project settings.
362+
const float default_gravity = GLOBAL_GET("physics/3d/default_gravity");
363+
const Vector3 default_gravity_vector = GLOBAL_GET("physics/3d/default_gravity_vector");
364+
process_material->set_gravity(default_gravity_vector * default_gravity);
365+
366+
// Emit particles upwards, so that they are visible even when the particles node is placed on the ground.
367+
// Adjust for the default gravity, so that particles are emitted upwards even when gravity is set to a high value.
368+
process_material->set_direction(Vector3(0, 1, 0));
369+
process_material->set_param_min(ParticleProcessMaterial::PARAM_INITIAL_LINEAR_VELOCITY, MAX(1.0, default_gravity * 0.4));
370+
}
371+
372+
gpu_particles_3d->set_draw_pass_mesh(0, get_mesh_for_node_created_in_editor());
373+
}
374+
}
375+
319376
Node *GPUParticles3DEditorPlugin::_convert_particles() {
320377
GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(edited_node);
321378

@@ -417,6 +474,57 @@ GPUParticles3DEditorPlugin::GPUParticles3DEditorPlugin() {
417474
conversion_option_name = TTR("Convert to CPUParticles3D");
418475
}
419476

477+
void CPUParticles3DEditorPlugin::_notification(int p_what) {
478+
switch (p_what) {
479+
case NOTIFICATION_READY: {
480+
EditorInterface::get_singleton()->connect("object_created_in_editor", callable_mp(this, &CPUParticles3DEditorPlugin::_object_created_in_editor));
481+
} break;
482+
}
483+
}
484+
485+
void CPUParticles3DEditorPlugin::_object_created_in_editor(Object *p_object) {
486+
CPUParticles3D *cpu_particles_3d = Object::cast_to<CPUParticles3D>(p_object);
487+
if (cpu_particles_3d) {
488+
// Increase the particle amount somewhat to make moving particle systems more visible out of the box.
489+
cpu_particles_3d->set_amount(30);
490+
491+
// Reduce particle spread to make the particle node's rotation easier to notice.
492+
cpu_particles_3d->set_spread(5.0);
493+
494+
// Randomize initial particle rotation.
495+
cpu_particles_3d->set_param_min(CPUParticles3D::PARAM_ANGLE, 0.0);
496+
cpu_particles_3d->set_param_max(CPUParticles3D::PARAM_ANGLE, 360.0);
497+
498+
// Scale particles up and down as the lifetime progresses.
499+
Ref<Curve> curve = memnew(Curve);
500+
// The wind-up occurs significantly faster than the fade-out.
501+
curve->add_point(Vector2(0.0, 0.0));
502+
curve->add_point(Vector2(0.1, 1.0));
503+
curve->add_point(Vector2(1.0, 0.0));
504+
cpu_particles_3d->set_param_curve(CPUParticles3D::PARAM_SCALE, curve);
505+
506+
// Fade particles with transparency as the lifetime progresses.
507+
Ref<Gradient> gradient = memnew(Gradient);
508+
// The fade-in occurs significantly faster than the fade-out.
509+
gradient->set_color(0, Color(1, 1, 1, 0));
510+
gradient->set_color(1, Color(1, 1, 1, 0));
511+
gradient->add_point(0.1, Color(1, 1, 1, 1));
512+
cpu_particles_3d->set_color_ramp(gradient);
513+
514+
// Set gravity according to the 3D project settings.
515+
const float default_gravity = GLOBAL_GET("physics/3d/default_gravity");
516+
const Vector3 default_gravity_vector = GLOBAL_GET("physics/3d/default_gravity_vector");
517+
cpu_particles_3d->set_gravity(default_gravity_vector * default_gravity);
518+
519+
// Emit particles upwards, so that they are visible even when the particles node is placed on the ground.
520+
// Adjust for the default gravity, so that particles are emitted upwards even when gravity is set to a high value.
521+
cpu_particles_3d->set_direction(Vector3(0, 1, 0));
522+
cpu_particles_3d->set_param_min(CPUParticles3D::PARAM_INITIAL_LINEAR_VELOCITY, MAX(1.0, default_gravity * 0.4));
523+
524+
cpu_particles_3d->set_mesh(get_mesh_for_node_created_in_editor());
525+
}
526+
}
527+
420528
Node *CPUParticles3DEditorPlugin::_convert_particles() {
421529
CPUParticles3D *particles = Object::cast_to<CPUParticles3D>(edited_node);
422530

‎editor/scene/3d/particles_3d_editor_plugin.h‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ class Particles3DEditorPlugin : public ParticlesEditorPlugin {
5454
protected:
5555
Vector<Face3> geometry;
5656

57+
Ref<Mesh> get_mesh_for_node_created_in_editor() const;
58+
5759
virtual void _menu_callback(int p_idx) override;
5860
virtual void _add_menu_options(PopupMenu *p_menu) override;
5961

@@ -68,7 +70,11 @@ class Particles3DEditorPlugin : public ParticlesEditorPlugin {
6870
class GPUParticles3DEditorPlugin : public Particles3DEditorPlugin {
6971
GDCLASS(GPUParticles3DEditorPlugin, Particles3DEditorPlugin);
7072

73+
void _object_created_in_editor(Object *p_object);
74+
7175
protected:
76+
void _notification(int p_what);
77+
7278
Node *_convert_particles() override;
7379

7480
bool _can_generate_points() const override;
@@ -81,7 +87,11 @@ class GPUParticles3DEditorPlugin : public Particles3DEditorPlugin {
8187
class CPUParticles3DEditorPlugin : public Particles3DEditorPlugin {
8288
GDCLASS(CPUParticles3DEditorPlugin, Particles3DEditorPlugin);
8389

90+
void _object_created_in_editor(Object *p_object);
91+
8492
protected:
93+
void _notification(int p_what);
94+
8595
Node *_convert_particles() override;
8696

8797
bool _can_generate_points() const override { return true; }

‎editor/scene/particles_editor_plugin.cpp‎

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
#include "scene/gui/box_container.h"
3838
#include "scene/gui/menu_button.h"
3939
#include "scene/gui/spin_box.h"
40+
#include "scene/resources/gradient_texture.h"
41+
#include "scene/resources/particle_process_material.h"
4042

4143
void ParticlesEditorPlugin::_notification(int p_what) {
4244
switch (p_what) {
@@ -60,6 +62,61 @@ void ParticlesEditorPlugin::_notification(int p_what) {
6062
}
6163
}
6264

65+
Ref<ParticleProcessMaterial> ParticlesEditorPlugin::get_material_for_object_created_in_editor() const {
66+
Ref<ParticleProcessMaterial> particle_process_material;
67+
particle_process_material.instantiate();
68+
69+
// Reduce particle spread to make the particle node's rotation easier to notice.
70+
particle_process_material->set_spread(5.0);
71+
72+
// Randomize initial particle rotation.
73+
particle_process_material->set_param_min(ParticleProcessMaterial::PARAM_ANGLE, 0.0);
74+
particle_process_material->set_param_max(ParticleProcessMaterial::PARAM_ANGLE, 360.0);
75+
76+
// Scale particles up and down as the lifetime progresses.
77+
Ref<CurveTexture> curve_texture = memnew(CurveTexture);
78+
Ref<Curve> curve = memnew(Curve);
79+
// The wind-up occurs significantly faster than the fade-out.
80+
curve->add_point(Vector2(0.0, 0.0));
81+
curve->add_point(Vector2(0.1, 1.0));
82+
curve->add_point(Vector2(1.0, 0.0));
83+
curve_texture->set_curve(curve);
84+
particle_process_material->set_param_texture(ParticleProcessMaterial::PARAM_SCALE, curve_texture);
85+
86+
// Fade particles with transparency as the lifetime progresses.
87+
Ref<GradientTexture1D> gradient_texture = memnew(GradientTexture1D);
88+
Ref<Gradient> gradient = memnew(Gradient);
89+
// The fade-in occurs significantly faster than the fade-out.
90+
gradient->set_color(0, Color(1, 1, 1, 0));
91+
gradient->set_color(1, Color(1, 1, 1, 0));
92+
gradient->add_point(0.1, Color(1, 1, 1, 1));
93+
gradient_texture->set_gradient(gradient);
94+
particle_process_material->set_color_ramp(gradient_texture);
95+
96+
return particle_process_material;
97+
}
98+
99+
Ref<Texture2D> ParticlesEditorPlugin::get_texture_for_object_created_in_editor() const {
100+
// Create a solid square texture.
101+
// We use a square instead of a circle, so that billboarded particle rotation is visible.
102+
Ref<GradientTexture2D> texture = memnew(GradientTexture2D);
103+
// Use a low resolution, so that it displays at a comparable size between 2D and 3D.
104+
// Texture size defines each particle's size in 2D, but not in 3D.
105+
texture->set_width(12);
106+
texture->set_height(12);
107+
texture->set_fill(GradientTexture2D::FILL_SQUARE);
108+
texture->set_fill_from(Vector2(0.5, 0.5));
109+
texture->set_fill_to(Vector2(0.5, 0.01));
110+
Ref<Gradient> gradient = memnew(Gradient);
111+
gradient->set_color(0, Color(1, 1, 1));
112+
gradient->set_color(1, Color(1, 1, 1, 0));
113+
// Harden the gradient so the texture has a sharper (but still somewhat soft) falloff.
114+
gradient->set_offset(0, 0.75);
115+
texture->set_gradient(gradient);
116+
117+
return texture;
118+
}
119+
63120
bool ParticlesEditorPlugin::need_show_lifetime_dialog(SpinBox *p_seconds) {
64121
// Add one second to the default generation lifetime, since the progress is updated every second.
65122
p_seconds->set_value(MAX(1.0, std::trunc(edited_node->get("lifetime").operator double()) + 1.0));

‎editor/scene/particles_editor_plugin.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@ class ConfirmationDialog;
3737
class HBoxContainer;
3838
class MenuButton;
3939
class OptionButton;
40+
class ParticleProcessMaterial;
4041
class SceneTreeDialog;
4142
class SpinBox;
43+
class Texture2D;
4244

4345
class ParticlesEditorPlugin : public EditorPlugin {
4446
GDCLASS(ParticlesEditorPlugin, EditorPlugin);
@@ -60,6 +62,8 @@ class ParticlesEditorPlugin : public EditorPlugin {
6062

6163
void _notification(int p_what);
6264

65+
Ref<ParticleProcessMaterial> get_material_for_object_created_in_editor() const;
66+
Ref<Texture2D> get_texture_for_object_created_in_editor() const;
6367
bool need_show_lifetime_dialog(SpinBox *p_seconds);
6468
virtual void _menu_callback(int p_idx);
6569

0 commit comments

Comments
 (0)