GPUParticles2D/3D in Godot 4 — Guida completa

1. Introduzione

Godot 4 usa GPUParticles2D e GPUParticles3D come nodi predefiniti per i sistemi di particelle. Vengono eseguiti interamente sulla GPU e per questo sono nettamente più veloci delle controparti CPU quando le particelle sono numerose. Il vecchio ParticlesMaterial di Godot 3 è stato sostituito da ParticleProcessMaterial, che offre maggiore controllo sul comportamento delle particelle.

CPUParticles2D e CPUParticles3D esistono ancora come nodi di fallback per i dispositivi privi di supporto GPU compute o quando devi accedere ai dati delle singole particelle da GDScript. Nella maggior parte dei casi le particelle GPU sono la scelta consigliata.

2. GPUParticles2D vs. CPUParticles2D

Caratteristica GPUParticles2D CPUParticles2D
Elaborazione GPU (compute shader) CPU (thread principale)
Particelle max 100.000+ in modo efficiente ~1.000-5.000 limite pratico
Sub-emitter No
Trail No
Accesso per particella No (solo GPU) Sì (da GDScript)
Modalità compatibilità Richiede Vulkan/Metal/D3D12 Funziona ovunque
Tipo di materiale ParticleProcessMaterial Proprietà integrate (nessun materiale)
Suggerimento: Usa GPUParticles2D come impostazione predefinita. Passa a CPUParticles2D solo se hai bisogno del supporto per il renderer Compatibility, devi leggere le posizioni delle particelle in GDScript o punti a hardware molto datato.

3. Configurazione di base

Per creare un semplice effetto particellare, aggiungi un nodo GPUParticles2D alla scena e assegnagli un ParticleProcessMaterial. Ecco una configurazione minima in GDScript:

# Create a GPUParticles2D node via code
var particles = GPUParticles2D.new()
particles.amount = 50
particles.lifetime = 2.0
particles.explosiveness = 0.0  # 0 = continuous, 1 = all at once
particles.randomness = 0.5

# Create and assign the material
var mat = ParticleProcessMaterial.new()
mat.direction = Vector3(0, -1, 0)  # Note: uses Vector3 even in 2D
mat.initial_velocity_min = 50.0
mat.initial_velocity_max = 100.0
mat.gravity = Vector3(0, 98, 0)

particles.process_material = mat
add_child(particles)

Proprietà chiave sul nodo GPUParticles2D stesso:

  • amountNumero di particelle vive in ogni istante (predefinito: 8)
  • lifetimeDurata di vita di ogni particella in secondi (predefinito: 1.0)
  • one_shotEmette una volta e poi si ferma (predefinito: false)
  • explosiveness0.0 = flusso continuo, 1.0 = tutte le particelle in una volta
  • randomnessOffset casuale sul timing di emissione (0.0-1.0)
  • speed_scaleMoltiplicatore della velocità di simulazione
  • emittingSe il sistema sta emettendo in questo momento

4. ParticleProcessMaterial in dettaglio

ParticleProcessMaterial controlla il comportamento delle singole particelle nel corso della loro vita. Tutte le proprietà direzionali usano Vector3, anche per le particelle 2D (la componente Z viene semplicemente ignorata).

Direzione & velocità

var mat = ParticleProcessMaterial.new()

# Direction: normalized vector for initial particle heading
mat.direction = Vector3(0, -1, 0)  # Upward in 2D (Y is flipped)

# Spread: cone angle in degrees (0 = straight line, 180 = hemisphere)
mat.spread = 45.0

# Initial velocity: particles spawn with speed in this range
mat.initial_velocity_min = 100.0
mat.initial_velocity_max = 200.0

# Gravity: constant acceleration applied every frame
mat.gravity = Vector3(0, 98, 0)  # Pulls downward in 2D

Velocità angolare

Fa ruotare le particelle durante il movimento. Utile per detriti, coriandoli o foglie.

mat.angular_velocity_min = -180.0  # degrees per second
mat.angular_velocity_max = 180.0

Scala nel corso della vita

Usa una CurveTexture per variare la dimensione delle particelle durante la loro vita:

var curve = Curve.new()
curve.add_point(Vector2(0.0, 1.0))  # Full size at birth
curve.add_point(Vector2(0.5, 1.2))  # Slightly larger at midlife
curve.add_point(Vector2(1.0, 0.0))  # Shrink to nothing at death

var curve_tex = CurveTexture.new()
curve_tex.curve = curve
mat.scale_curve = curve_tex

Smorzamento & attrattore

Lo smorzamento rallenta le particelle nel tempo, simulando la resistenza dell'aria. Gli attrattori (nodi separati) attirano le particelle verso un punto.

mat.damping_min = 5.0
mat.damping_max = 10.0

5. Gradienti di colore

La proprietà color_ramp del ParticleProcessMaterial accetta una GradientTexture1D per variare in modo fluido il colore delle particelle durante la loro vita. È indispensabile per effetti realistici di fuoco, fumo e magia.

Esempio: gradiente di fuoco

var gradient = Gradient.new()
gradient.colors = PackedColorArray([
    Color(1.0, 1.0, 1.0, 1.0),    # White (hot core)
    Color(1.0, 1.0, 0.2, 1.0),    # Yellow
    Color(1.0, 0.5, 0.0, 1.0),    # Orange
    Color(0.8, 0.1, 0.0, 0.8),    # Red
    Color(0.2, 0.0, 0.0, 0.0),    # Dark red, transparent (fade out)
])
gradient.offsets = PackedFloat32Array([0.0, 0.15, 0.4, 0.7, 1.0])

var grad_tex = GradientTexture1D.new()
grad_tex.gradient = gradient
mat.color_ramp = grad_tex
Suggerimento: Concludi sempre il gradiente con alpha = 0, così le particelle svaniscono gradualmente invece di sparire di colpo.

Per particelle di colore uniforme, invece di un gradiente puoi anche impostare una proprietà color piatta:

mat.color = Color(0.2, 0.6, 1.0, 0.8)  # Semi-transparent blue

6. Forme di emissione

La forma di emissione determina dove nascono le particelle. Si imposta tramite emission_shape sul ParticleProcessMaterial.

Point

Tutte le particelle nascono dall'origine del nodo. È la forma predefinita.

mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_POINT

Sphere / Sphere Surface

Le particelle nascono casualmente all'interno di una sfera (o solo sulla sua superficie).

mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_SPHERE
mat.emission_sphere_radius = 50.0

# Surface only:
mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_SPHERE_SURFACE
mat.emission_sphere_radius = 50.0

Box

Le particelle nascono casualmente all'interno di un volume rettangolare.

mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_BOX
mat.emission_box_extents = Vector3(100, 10, 0)  # Wide and thin

Ring

Le particelle nascono in una forma ad anello (ciambella), definita da raggio interno/esterno e altezza.

mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_RING
mat.emission_ring_radius = 80.0       # Outer radius
mat.emission_ring_inner_radius = 60.0 # Inner radius (hole)
mat.emission_ring_height = 0.0        # Flat ring
mat.emission_ring_axis = Vector3(0, 0, 1)  # Ring normal
Suggerimento: Combina EMISSION_SHAPE_BOX con un'ampia estensione X e una Y stretta per creare effetti a livello del suolo, come scie di polvere o particelle dei passi.

7. Ricette pratiche

Fuoco

Gradiente arancione-rosso, velocità verso l'alto, leggera dispersione, scala decrescente nel corso della vita.

func create_fire() -> GPUParticles2D:
    var p = GPUParticles2D.new()
    p.amount = 40
    p.lifetime = 0.8
    p.randomness = 0.3

    var mat = ParticleProcessMaterial.new()
    mat.direction = Vector3(0, -1, 0)
    mat.spread = 15.0
    mat.initial_velocity_min = 40.0
    mat.initial_velocity_max = 80.0
    mat.gravity = Vector3(0, -20, 0)  # Slight upward pull
    mat.damping_min = 5.0
    mat.damping_max = 10.0

    # Color ramp: white -> yellow -> orange -> red -> transparent
    var grad = Gradient.new()
    grad.colors = PackedColorArray([
        Color(1, 1, 1, 1), Color(1, 1, 0.2, 1),
        Color(1, 0.4, 0, 0.9), Color(0.6, 0.1, 0, 0.4),
        Color(0.2, 0, 0, 0)
    ])
    grad.offsets = PackedFloat32Array([0.0, 0.1, 0.35, 0.7, 1.0])
    var grad_tex = GradientTexture1D.new()
    grad_tex.gradient = grad
    mat.color_ramp = grad_tex

    # Scale: shrink over time
    var curve = Curve.new()
    curve.add_point(Vector2(0, 0.8))
    curve.add_point(Vector2(0.3, 1.0))
    curve.add_point(Vector2(1, 0.0))
    var curve_tex = CurveTexture.new()
    curve_tex.curve = curve
    mat.scale_curve = curve_tex

    p.process_material = mat
    return p

Fumo

Gradiente grigio, movimento lento verso l'alto, ampia dispersione, alpha basso per un aspetto vaporoso.

func create_smoke() -> GPUParticles2D:
    var p = GPUParticles2D.new()
    p.amount = 25
    p.lifetime = 3.0
    p.randomness = 0.5

    var mat = ParticleProcessMaterial.new()
    mat.direction = Vector3(0, -1, 0)
    mat.spread = 35.0
    mat.initial_velocity_min = 10.0
    mat.initial_velocity_max = 30.0
    mat.gravity = Vector3(0, -5, 0)
    mat.damping_min = 2.0
    mat.damping_max = 5.0
    mat.angular_velocity_min = -30.0
    mat.angular_velocity_max = 30.0

    var grad = Gradient.new()
    grad.colors = PackedColorArray([
        Color(0.6, 0.6, 0.6, 0.0), Color(0.5, 0.5, 0.5, 0.3),
        Color(0.4, 0.4, 0.4, 0.2), Color(0.3, 0.3, 0.3, 0.0)
    ])
    grad.offsets = PackedFloat32Array([0.0, 0.15, 0.6, 1.0])
    var grad_tex = GradientTexture1D.new()
    grad_tex.gradient = grad
    mat.color_ramp = grad_tex

    # Scale: grow over time (smoke expands)
    var curve = Curve.new()
    curve.add_point(Vector2(0, 0.3))
    curve.add_point(Vector2(0.5, 1.0))
    curve.add_point(Vector2(1, 1.5))
    var curve_tex = CurveTexture.new()
    curve_tex.curve = curve
    mat.scale_curve = curve_tex

    p.process_material = mat
    return p

Pioggia

Strisce bianco-azzurre, forte gravità verso il basso, dispersione stretta, alto numero di particelle.

func create_rain() -> GPUParticles2D:
    var p = GPUParticles2D.new()
    p.amount = 200
    p.lifetime = 1.0

    # Use a box emission to cover the screen width
    var mat = ParticleProcessMaterial.new()
    mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_BOX
    mat.emission_box_extents = Vector3(600, 0, 0)
    mat.direction = Vector3(0.05, 1, 0)  # Slightly angled
    mat.spread = 3.0
    mat.initial_velocity_min = 400.0
    mat.initial_velocity_max = 500.0
    mat.gravity = Vector3(0, 200, 0)

    var grad = Gradient.new()
    grad.colors = PackedColorArray([
        Color(0.7, 0.8, 1.0, 0.6), Color(0.7, 0.85, 1.0, 0.4),
        Color(0.7, 0.85, 1.0, 0.0)
    ])
    grad.offsets = PackedFloat32Array([0.0, 0.8, 1.0])
    var grad_tex = GradientTexture1D.new()
    grad_tex.gradient = grad
    mat.color_ramp = grad_tex

    # Stretch particles to look like streaks
    mat.scale_min = 0.5
    mat.scale_max = 0.5

    p.process_material = mat
    return p

Scintille

Giallo-arancio, esplosione improvvisa, alta velocità iniziale, breve durata di vita.

func create_sparks() -> GPUParticles2D:
    var p = GPUParticles2D.new()
    p.amount = 30
    p.lifetime = 0.5
    p.one_shot = true
    p.explosiveness = 1.0  # All at once

    var mat = ParticleProcessMaterial.new()
    mat.direction = Vector3(0, -1, 0)
    mat.spread = 180.0  # Full sphere
    mat.initial_velocity_min = 150.0
    mat.initial_velocity_max = 300.0
    mat.gravity = Vector3(0, 200, 0)  # Fall quickly
    mat.damping_min = 10.0
    mat.damping_max = 20.0

    var grad = Gradient.new()
    grad.colors = PackedColorArray([
        Color(1, 1, 0.8, 1), Color(1, 0.7, 0.1, 1),
        Color(1, 0.3, 0, 0.5), Color(0.5, 0.1, 0, 0)
    ])
    grad.offsets = PackedFloat32Array([0.0, 0.2, 0.6, 1.0])
    var grad_tex = GradientTexture1D.new()
    grad_tex.gradient = grad
    mat.color_ramp = grad_tex

    # Scale: start small, stay small
    mat.scale_min = 0.3
    mat.scale_max = 0.6

    p.process_material = mat
    return p

Neve

Particelle bianche, discesa lenta e delicata, ampia dispersione, leggera deriva orizzontale.

func create_snow() -> GPUParticles2D:
    var p = GPUParticles2D.new()
    p.amount = 100
    p.lifetime = 5.0
    p.randomness = 0.8

    var mat = ParticleProcessMaterial.new()
    mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_BOX
    mat.emission_box_extents = Vector3(500, 0, 0)
    mat.direction = Vector3(0.1, 1, 0)
    mat.spread = 10.0
    mat.initial_velocity_min = 20.0
    mat.initial_velocity_max = 40.0
    mat.gravity = Vector3(0, 10, 0)
    mat.damping_min = 1.0
    mat.damping_max = 3.0
    mat.angular_velocity_min = -45.0
    mat.angular_velocity_max = 45.0

    var grad = Gradient.new()
    grad.colors = PackedColorArray([
        Color(1, 1, 1, 0), Color(1, 1, 1, 0.8),
        Color(1, 1, 1, 0.7), Color(1, 1, 1, 0)
    ])
    grad.offsets = PackedFloat32Array([0.0, 0.05, 0.8, 1.0])
    var grad_tex = GradientTexture1D.new()
    grad_tex.gradient = grad
    mat.color_ramp = grad_tex

    mat.scale_min = 0.3
    mat.scale_max = 0.8

    p.process_material = mat
    return p

8. Particelle 3D (GPUParticles3D)

GPUParticles3D funziona in modo identico a GPUParticles2D, ma opera nello spazio 3D. Viene usato lo stesso ParticleProcessMaterial. Le differenze principali:

  • Tutti e 3 gli assi di Vector3 sono attivi (X, Y, Z)
  • Le forme di emissione funzionano in pieno 3D (le sfere sono vere sfere, le box sono volumi)
  • Puoi usare draw_pass_1 con un QuadMesh o una mesh personalizzata per particelle billboard
  • Le particelle mesh possono usare StandardMaterial3D o ORMMaterial3D
# 3D fire torch example
var particles_3d = GPUParticles3D.new()
particles_3d.amount = 60
particles_3d.lifetime = 1.0

var mat = ParticleProcessMaterial.new()
mat.direction = Vector3(0, 1, 0)  # Upward in 3D
mat.spread = 20.0
mat.initial_velocity_min = 1.0
mat.initial_velocity_max = 2.0
mat.gravity = Vector3(0, -0.5, 0)  # Slight counter-gravity

# Emission from a small sphere
mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_SPHERE
mat.emission_sphere_radius = 0.2

particles_3d.process_material = mat

# Use a QuadMesh as the particle shape
var quad = QuadMesh.new()
quad.size = Vector2(0.3, 0.3)
particles_3d.draw_pass_1 = quad

# Billboard mode: particles always face the camera
mat.billboard_mode = BaseMaterial3D.BILLBOARD_ENABLED

add_child(particles_3d)
Suggerimento: Per le particelle mesh (detriti, bossoli, frammenti) assegna una tua mesh .tres a draw_pass_1 e applicavi un StandardMaterial3D. Ogni particella verrà renderizzata come copia di quella mesh.

9. Sub-emitter

Godot 4 supporta i sub-emitter per effetti particella-su-particella. Un sub-emitter è un altro nodo GPUParticles2D/GPUParticles3D che emette particelle alla posizione di ciascuna particella genitore. È ideale per:

  • Scintille che schizzano via dalle particelle di fuoco
  • Scie di fumo dietro le esplosioni di fuochi d'artificio
  • Gocce di schizzo alla collisione
  • Esplosioni secondarie

Come configurare i sub-emitter:

# 1. Create the sub-emitter node (must be a sibling or child)
var sub_sparks = GPUParticles2D.new()
sub_sparks.amount = 4
sub_sparks.lifetime = 0.3
sub_sparks.explosiveness = 1.0
sub_sparks.emitting = false  # Controlled by parent
var sub_mat = ParticleProcessMaterial.new()
sub_mat.direction = Vector3(0, -1, 0)
sub_mat.spread = 180.0
sub_mat.initial_velocity_min = 50.0
sub_mat.initial_velocity_max = 100.0
sub_sparks.process_material = sub_mat
add_child(sub_sparks)

# 2. Reference it from the parent material
var parent_mat: ParticleProcessMaterial = fire_particles.process_material
parent_mat.sub_emitter_mode = ParticleProcessMaterial.SUB_EMITTER_AT_END
# Also: SUB_EMITTER_AT_COLLISION, SUB_EMITTER_CONSTANT

# 3. Set the sub_emitter property on the parent node
fire_particles.sub_emitter = fire_particles.get_path_to(sub_sparks)

Modalità dei sub-emitter:

  • SUB_EMITTER_CONSTANTEmette in continuazione da ogni particella genitore
  • SUB_EMITTER_AT_ENDEmette quando la particella genitore muore
  • SUB_EMITTER_AT_COLLISIONEmette quando la particella genitore collide
Attenzione: I sub-emitter funzionano solo con GPUParticles, non con CPUParticles. Ogni sub-emitter aumenta il carico della GPU, quindi mantieni basso il numero di particelle dei sub-emitter (4-8 per particella genitore).

10. Consigli sulle prestazioni

  • Compromesso quantità vs. durata di vita: Particelle visibili totali = amount. Se aumenti lifetime, le particelle si accumulano più a lungo. Riduci amount per compensare, oppure aumentalo per effetti più densi.
  • Usa il Visibility Range (3D): Imposta visibility_range_end su GPUParticles3D affinché i sistemi di particelle lontani smettano di essere renderizzati. È un grande vantaggio per i mondi aperti.
  • FPS fissi: Imposta fixed_fps sul nodo delle particelle (es. 30) per limitare gli aggiornamenti della simulazione. Le particelle continuano a interpolare in modo fluido, ma consumano meno tempo GPU.
  • One-shot per le esplosioni: Per gli effetti di tipo esplosivo usa one_shot = true con explosiveness = 1.0. Il nodo può poi essere liberato al termine della durata di vita.
  • Usa il LOD per il 3D: Combina visibility_range_begin/end con più nodi di particelle a diversi livelli di dettaglio per effetti particellari Level of Detail (LOD).
  • Evita l'overdraw: Molte particelle semitrasparenti sovrapposte = fill rate costoso. Riduci amount e usa invece particelle più grandi con alpha inferiore.

11. Migrazione da Godot 3 a 4

Se stai migrando un progetto da Godot 3 a Godot 4, ecco le modifiche principali per le particelle:

Godot 3 Godot 4 Note
Particles2D GPUParticles2D Rinominato
Particles (3D) GPUParticles3D Rinominato
ParticlesMaterial ParticleProcessMaterial Rinominato con nuove funzionalità
CPUParticles2D CPUParticles2D Invariato
CPUParticles CPUParticles3D Rinominato per chiarezza
No sub-emitters sub_emitter_mode Nuovo in Godot 4
No trails trail_enabled Nuovo in Godot 4
flag_align_y particle_flag_align_y Proprietà rinominata
Suggerimento: Il convertitore di progetti di Godot gestisce automaticamente la maggior parte delle rinominazioni. Tuttavia, tutti i riferimenti a ParticlesMaterial in GDScript devono essere aggiornati manualmente a ParticleProcessMaterial.

Automatizza gli effetti particellari con MCP Pro

Vuoi che l'IA crei gli effetti particellari al posto tuo? Godot MCP Pro include strumenti dedicati alle particelle con preset per fuoco, fumo, pioggia, neve, scintille e altro ancora. Pieno controllo su materiali, gradienti e forme di emissione da un'unica conversazione.

  • create_particles
  • set_particle_material
  • set_particle_color_gradient
  • apply_particle_preset
  • get_particle_info
Ottieni MCP Pro — $15