10 November 2024

Particle systems in Godot introduction (Part I) - The sakura flowers

Flowers falling from sakura tree
In previous articles, we worked on a Japanese scene, using shaders to give the water reflection and movement, as well as to animate the tree. In this article, we’ll set shaders aside and introduce a deceptively simple tool that, when used well, can create multiple effects. This tool is called particle systems.

A particle system is a component that emits a continuous stream of particles. Different effects can be achieved by adjusting the number of particles, their speed, direction, or visual appearance. My idea is to add three particle systems to the Japanese scene from the previous article: one to simulate cherry blossom petals falling, another to visually represent the wind, and another to create a rain effect.

In this article, we’ll see how to implement the falling cherry blossom petals. This is a simple example, but I think it’s useful to understand particle systems and to get comfortable using them. Also, with a few variations, this example can be adapted to similar effects, like autumn leaves falling.

As in previous examples, it’s important to download the project on my GitHub to follow along with the explanations. Make sure to download the exact commit; otherwise, you might see code that differs from this article.

The effect we want is for the cherry blossoms to fall softly from the top of the tree. To preserve the scene’s zen character, we’ll keep the number of petals low, although we’ll see how to increase it. In fact, we’ll subtly increase it when the wind’s strength rises. We’ll also see how to move the particle system in sync with the movement of the tree's canopy.

Configuring the Particle System

In Godot, a particle system is just another node, called GPUParticles2D (with an equivalent node in 3D). Since our system will be linked to the tree, it makes sense for the particle system node to be a child of the tree node.

Once we create the particle system node under the tree node, we can begin configuring it.

Particle Emitter Node
Particle Emitter Node

A particle emitter has two levels of configuration: node level and material level.

The most basic settings are configured at the node level:

Particle emitter node configuration
Particle emitter node configuration


Emitting: Activates the particle emitter. If unchecked, the emitter will stop working.

Amount: Controls the number of particles emitted per unit of time. I set it to 1 for a subtle effect, but if you want to see the emitter in full effect, try setting it to 5 or 10.

Amount Ratio: Multiplies the value of Amount in each cycle, letting us vary the emitter’s flow without restarting it. Changing the Amount via script would restart the emitter, which looks unrealistic. But modifying Amount Ratio allows adjusting the flow without interruption.

Time--> Lifetime: Determines how long each particle remains visible, from birth to disappearance. I set it to 3 so the petals reach the ground before disappearing.

Drawing--> Visibility Rect: Specifies a rectangle within which the particles are visible. When particles leave this rectangle, they stop being visible.

Texture: Allows us to assign a sprite to give the particles a visual appearance. Instead of an individual sprite, I used a sprite sheet to generate an animation.

A sprite sheet is a single image composed of multiple individual images, useful for organizing all images in a few files rather than many individual ones. Engines are optimized for sprite sheets, which is more efficient than loading multiple individual image files.

Sakura flowers sprite sheet
Sakura flowers sprite sheet

You can make an sprite sheet gathering individual images into a single one, using an editor like Gimp or an specific tool like Texture Packer. I've used that one. Keep in mind thet, if you want to use your sprite sheet for an animation, you must sort horizontally the individual images and distribute then at even distances into the sprite sheet file. 

To animate the sprite, configure the node at the material level by assigning a ParticleProcessMaterial resource to the Process Material field. Set Animation --> Speed to 1 and Animation --> Offset (min to 0, max to 1). This makes the sprite sheet animate at a constant speed as each particle falls. Each particle starts from a different image in the sprite sheet, creating variety and an organic look.

For variety in particle size, I set the Scale parameter to a random size between 0.03 and 0.05. I also set Lifetime Randomness to 0.07 to make particle lifetimes vary slightly.

Since this is a 2D scene, I disabled Z-axis movement using Disable Z. For the shape of the particle generator, I used a sphere instead of the default cube because the tree canopy resembles a sphere. The sphere radius is set in Emission Shape Radius, and I offset the sphere slightly to the right to match the tree’s asymmetrical canopy shape.

To make the particles fall, I set a gravity value for the Y-axis in Process Material --> Accelerations--> Gravity. After some testing, 50 felt like the best value.

Moving the Particle System

The configured system behaves as a spherical volume in which particles randomly appear and fall due to gravity. If our tree were static, we could stop here, but it’s not. In previous chapters, we implemented wind-induced tree sway. For realism, the particle system’s volume should move along with the canopy.

Instead of making the particle system a child of the tree (which won’t move as it’s only visually animated by a shader), we’ll add a script to the particle system to move it in sync with the canopy’s movement

The particle system’s script, located at Scripts/LeafEmitter.cs, exports four fields for configuring from the inspector:

LeafEmitters.cs exported fields.
LeafEmitters.cs exported fields.

minimumFlowerAmount: Number of petals emitted when the tree is at rest.

OscillationMultiplier: Defines the maximum amplitude of the particle system’s oscillatory movement.

WindStrength: Wind strength.

HorizontalOffset: Offsets the oscillation direction.

LeafEmitter.cs process method
LeafEmitter.cs process method

The algorithm executed in each frame is simple: it calculates the oscillation (line 55) and uses it to shift the particle system’s horizontal position (line 57).

The method get_oscillation() calculates the oscillation:

LeafEmitter.cs get_oscillation() method
LeafEmitter.cs get_oscillation() method

The get_oscillation() method uses the same algorithm as the tree shader to define the movement of the upper vertices of the texture.

Shader synchronization with particle system

To synchronize the shader with the particle system, we can use a script located on the tree that has access to both the tree shader and the particle emitter script.

The script is located at Scripts/Tree.cs.

The configuration fields it exports to the inspector are as follows:

Exported Fields of Tree.cs
Exported Fields of Tree.cs

The trick here is that these fields are actually properties that apply modifications to both the tree oscillation shader and the LeafEmitter particle emitter script.

For example, the WindStrength property in Tree.cs works like this:

Tree.cs WindStrength property
Tree.cs WindStrength property

When a value is requested for this property, it returns the value set in the tree's oscillation shader (line 20). On the other hand, when you set a value, the property applies it to the shader (line 27) as well as to the particle emitter script (line 30).

The other two properties, HorizontalOffset and MaximumOscillationAmplitude, function similarly.

This way, any changes we make to the properties of Tree.cs are automatically replicated in the equivalent properties of LeafEmitter.

However, this method synchronizes all oscillation properties except for one: time. To ensure both Tree and LeafEmitter use the same time reference for their oscillations, we’ll manage the time count ourselves and pass it to both the tree shader and the particle emitter script.

The time count is handled in the _Process() method of the tree script:

Process() Method in Tree.cs
Process() Method in Tree.cs

In line 102 of the method, we simply add the time elapsed since the last frame (delta).

Time Property in Tree.cs
Time Property in Tree.cs

The Time property is responsible for updating both the shader and the particle emitter. Line 83 updates the shader, while line 84 updates the particle emitter.

With the same algorithm, parameters, and time variable, both the tree and the particle emitter can oscillate in sync. In the screenshot below, I have selected the particle emitter node, with its position marked by a cross. Notice how it moves in rhythm with the tree.

Sakura tree oscillation synced with particle emitter movement
Sakura tree oscillation synced with particle emitter movement

This concludes our introduction to particle emitters. In future articles, I will explain how to implement a rain effect, as well as a visual representation of the wind.