Quick answer: The most common cause is a missing ParticleProcessMaterial. GPUParticles2D requires a process material to define particle behavior (velocity, gravity, color, etc.). Without this material, the node has no instructions for how to create or move particles, so nothing emits.
Here is how to fix Godot particles not emitting. You added a GPUParticles2D node to your scene, set emitting to true, and nothing appears. No particles, no errors, just an empty node. This is one of the most frustrating first experiences with Godot’s particle system because the node gives you almost no feedback about what is missing. The fix is straightforward once you know what the node requires to function.
The Symptom
Your GPUParticles2D (or GPUParticles3D) node is in the scene tree with emitting set to true. When you run the scene, no particles are visible. The node appears as a small orange dot in the editor with a warning icon, but it is easy to miss the warning among other editor indicators.
The warning icon is actually the most important clue: it tells you that the node is missing required configuration. If you hover over the warning, Godot tells you exactly what is missing, but many developers overlook this because they expect particles to “just work” with default settings like they do in some other engines.
A variant of this problem is particles that emit but are invisible. In this case, the particle system is running (you might see the editor’s particle count indicator updating), but nothing renders. This points to a different issue: either the particle draw pass is missing a material, the visibility rect is too small, or the particles are spawning at a scale or color that makes them invisible.
Another variant is particles that appear in the editor preview but not at runtime, or particles that work on desktop but not in web exports. These are GPU compatibility issues — the web renderer has limitations on particle features, and some shader features used by ParticleProcessMaterial may not be available on all platforms.
The Process Material Requirement
GPUParticles2D and GPUParticles3D are GPU-driven systems. Unlike CPUParticles2D (which has all its properties built into the node), GPU particles delegate all behavior to a ParticleProcessMaterial resource. This material defines how particles move, how they change color and size over their lifetime, how gravity affects them, and every other aspect of particle behavior.
Without a ParticleProcessMaterial assigned to the process_material property, the GPU has no shader program to execute for the particles. The node creates particle instances in memory but has no instructions for positioning or rendering them, so nothing appears.
# Create a basic particle system in code
extends Node2D
func _ready():
var particles = GPUParticles2D.new()
add_child(particles)
# This is the critical step - assign a process material
var material = ParticleProcessMaterial.new()
material.direction = Vector3(0, -1, 0) # Emit upward (Y is inverted in 2D)
material.initial_velocity_min = 50.0
material.initial_velocity_max = 100.0
material.gravity = Vector3(0, 98, 0) # Pull particles down
particles.process_material = material
# Configure the particle node itself
particles.amount = 32
particles.lifetime = 1.5
particles.emitting = true
In the editor, the fix is simpler: select the GPUParticles2D node, find the Process Material property in the Inspector, click it, and choose New ParticleProcessMaterial. Once assigned, particles will immediately appear in the editor viewport.
Note that ParticleProcessMaterial uses Vector3 for direction and gravity even in 2D. This is because the GPU particle shader works in 3D space internally. For 2D particles, the Z component is typically left at 0, and only X and Y matter.
Configuring Particle Behavior
After assigning the process material, you may find that particles appear but do not behave as expected. Here are the most important ParticleProcessMaterial properties and what they do:
Direction and Spread. The direction property sets the base direction particles are emitted in. spread (in degrees) controls how much random variation is applied around that direction. A spread of 0 means all particles go in the exact same direction. A spread of 180 means particles can go in any direction within a hemisphere.
Initial Velocity. initial_velocity_min and initial_velocity_max define the speed range at which particles are spawned. If both are zero, particles appear at the emitter position and do not move (unless gravity pulls them). This is the second most common “particles are not working” issue after the missing material.
Gravity. The gravity vector applies constant acceleration to all particles. For 2D, set the Y component to a positive value (e.g., 98) to pull particles downward, simulating real gravity. For upward effects like fire or smoke, use a negative Y value or set initial velocity upward with no gravity.
Scale and Color. Scale and color can change over a particle’s lifetime using curve and gradient resources. If the scale curve starts at zero, particles will be invisible at spawn. If the color gradient starts with alpha 0, particles will be transparent. Check these curves if particles emit but you cannot see them.
# Configure a fire-like particle effect
var material = ParticleProcessMaterial.new()
material.direction = Vector3(0, -1, 0) # Emit upward
material.spread = 15.0 # Slight spread
material.initial_velocity_min = 40.0
material.initial_velocity_max = 80.0
material.gravity = Vector3(0, -20, 0) # Slight upward pull
material.scale_min = 0.5
material.scale_max = 1.5
# Color gradient: yellow to orange to transparent
var gradient = Gradient.new()
gradient.set_color(0, Color(1, 1, 0.3, 1)) # Bright yellow
gradient.add_point(0.5, Color(1, 0.4, 0, 0.8)) # Orange
gradient.set_color(1, Color(0.5, 0, 0, 0)) # Transparent red
var gradient_tex = GradientTexture1D.new()
gradient_tex.gradient = gradient
material.color_ramp = gradient_tex
Emitting Property and One-Shot Mode
The emitting property controls whether the particle system is actively spawning new particles. When set to false, no new particles are created, but existing particles continue to live out their lifetime. This is the expected behavior for stopping an effect gracefully.
The one_shot property changes the emission behavior: instead of continuous emission, the system emits all particles at once and then sets emitting to false automatically. This is useful for explosions, impact effects, or any effect that should play once and stop.
# Trigger a one-shot particle effect
@onready var explosion = $GPUParticles2D
func _ready():
explosion.one_shot = true
explosion.emitting = false # Don't emit on scene load
func explode():
explosion.emitting = true # Emit once, then auto-stops
# If you need to restart the same one-shot effect later:
explosion.restart() # Reset and emit again
A common issue with one-shot particles is trying to emit them again after they have already fired. Setting emitting = true on a one-shot system that has already completed may not restart it. Use restart() instead, which resets the system’s internal timer and re-emits all particles.
The amount property defines how many particles exist at any given time, not how many are emitted per second. The emission rate is determined by amount / lifetime. If amount is 32 and lifetime is 2 seconds, the system emits 16 particles per second. Increasing the amount increases both the total visible particles and the emission rate proportionally.
Visibility Rect and Draw Pass
GPUParticles2D has a visibility_rect property that defines the bounding box for culling. If particles move outside this rectangle, they are still processed by the GPU but may not be rendered. If your particles seem to disappear at the edges of the emitter, the visibility rect is too small.
# Set a large visibility rect for far-traveling particles
func _ready():
$GPUParticles2D.visibility_rect = Rect2(-500, -500, 1000, 1000)
You can also enable Debug > Visible Collision Shapes which also shows particle visibility rects as blue outlines. If the blue outline is small and your particles travel far from the emitter, expand the rect.
The Draw Pass section of GPUParticles2D defines the mesh used to render each particle. By default, a simple quad is used, but you can assign custom meshes for 3D-style particles. If the draw pass mesh is missing or has no material with a visible texture, particles will be invisible even though they are being processed.
For GPUParticles3D specifically, you need a draw pass mesh with a proper material. Unlike 2D particles which render as textured quads by default, 3D particles with no draw pass mesh configured will produce no visible output.
If your particles work in the editor but not in web exports, consider switching to CPUParticles2D. Web exports use the Compatibility renderer, which has limited GPU particle support. CPUParticles2D works identically across all platforms. You can convert a GPUParticles2D to CPUParticles2D by right-clicking the node in the scene tree and selecting the conversion option.
"Every single time someone tells me their particles are not working, the answer is the same: assign a ParticleProcessMaterial. That node does nothing without it. I wish it had better default behavior, but it does not."
Related Issues
If your particles emit but cause performance problems, our guide on game performance on low-end hardware covers particle count optimization. For particles that render above or below the wrong layers, check canvas layer rendering order. If your particle shaders produce visual artifacts or compiler errors, debugging shader errors walks through the diagnostic process for GPU-driven effects.
No ParticleProcessMaterial means no particles. Assign the material first.