GPUParticles2D/3D in Godot 4 — Vollständiger Leitfaden

1. Einführung

Godot 4 verwendet GPUParticles2D und GPUParticles3D als standardmäßige Partikelsystem-Nodes. Diese laufen vollständig auf der GPU und sind daher bei großen Partikelmengen deutlich schneller als ihre CPU-Pendants. Das alte ParticlesMaterial aus Godot 3 wurde durch ParticleProcessMaterial ersetzt, das mehr Kontrolle über das Partikelverhalten bietet.

CPUParticles2D und CPUParticles3D existieren weiterhin als Fallback-Nodes für Geräte ohne GPU-Compute-Unterstützung oder wenn du aus GDScript auf einzelne Partikeldaten zugreifen musst. In den meisten Fällen sind GPU-Partikel die empfohlene Wahl.

2. GPUParticles2D vs. CPUParticles2D

Merkmal GPUParticles2D CPUParticles2D
Verarbeitung GPU (Compute Shader) CPU (Haupt-Thread)
Max. Partikel 100.000+ effizient ~1.000-5.000 praktisches Limit
Sub-Emitter Ja Nein
Trails Ja Nein
Zugriff pro Partikel Nein (nur GPU) Ja (aus GDScript)
Kompatibilitätsmodus Erfordert Vulkan/Metal/D3D12 Funktioniert überall
Materialtyp ParticleProcessMaterial Integrierte Eigenschaften (kein Material)
Tipp: Verwende standardmäßig GPUParticles2D. Wechsle nur dann zu CPUParticles2D, wenn du Unterstützung für den Compatibility-Renderer benötigst, Partikelpositionen in GDScript auslesen musst oder sehr alte Hardware anvisierst.

3. Grundlegende Einrichtung

Um einen einfachen Partikeleffekt zu erstellen, füge deiner Szene einen GPUParticles2D-Node hinzu und weise ihm ein ParticleProcessMaterial zu. Hier ist eine minimale Einrichtung 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)

Wichtige Eigenschaften des GPUParticles2D-Nodes selbst:

  • amountAnzahl der gleichzeitig lebenden Partikel (Standard: 8)
  • lifetimeWie lange jedes Partikel in Sekunden lebt (Standard: 1.0)
  • one_shotEinmal emittieren, dann stoppen (Standard: false)
  • explosiveness0.0 = kontinuierlicher Strom, 1.0 = alle Partikel auf einmal
  • randomnessZufälliger Versatz beim Emissions-Timing (0.0-1.0)
  • speed_scaleMultiplikator für die Simulationsgeschwindigkeit
  • emittingOb das System aktuell emittiert

4. ParticleProcessMaterial im Detail

ParticleProcessMaterial steuert, wie sich einzelne Partikel über ihre Lebensdauer verhalten. Alle Richtungseigenschaften verwenden Vector3, selbst bei 2D-Partikeln (die Z-Komponente wird einfach ignoriert).

Richtung & Geschwindigkeit

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

Winkelgeschwindigkeit

Lässt Partikel während der Bewegung rotieren. Nützlich für Trümmer, Konfetti oder Blätter.

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

Skalierung über die Lebensdauer

Verwende eine CurveTexture, um die Partikelgröße über ihre Lebensdauer zu verändern:

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

Dämpfung & Attraktor

Dämpfung verlangsamt Partikel im Laufe der Zeit und simuliert Luftwiderstand. Attraktoren (separate Nodes) ziehen Partikel zu einem Punkt.

mat.damping_min = 5.0
mat.damping_max = 10.0

5. Farbverläufe

Die Eigenschaft color_ramp am ParticleProcessMaterial akzeptiert eine GradientTexture1D, um die Partikelfarbe über die Lebensdauer sanft zu verändern. Das ist unverzichtbar für realistische Feuer-, Rauch- und Magieeffekte.

Beispiel: Feuer-Verlauf

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
Tipp: Beende deinen Verlauf immer mit Alpha = 0, damit Partikel sanft ausblenden, statt schlagartig zu verschwinden.

Für einfarbige Partikel kannst du statt eines Verlaufs auch eine flache color-Eigenschaft setzen:

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

6. Emissionsformen

Die Emissionsform bestimmt, wo Partikel entstehen. Sie wird über emission_shape am ParticleProcessMaterial festgelegt.

Point

Alle Partikel entstehen am Ursprung des Nodes. Standardform.

mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_POINT

Sphere / Sphere Surface

Partikel entstehen zufällig innerhalb einer Kugel (oder nur auf ihrer Oberfläche).

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

Partikel entstehen zufällig innerhalb eines rechteckigen Volumens.

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

Ring

Partikel entstehen in einer Ring- (Donut-)Form, definiert durch Innen-/Außenradius und Höhe.

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
Tipp: Kombiniere EMISSION_SHAPE_BOX mit einer breiten X-Ausdehnung und schmalem Y, um bodennahe Effekte wie Staubfahnen oder Schritt-Partikel zu erzeugen.

7. Praktische Rezepte

Feuer

Orange-roter Verlauf, Geschwindigkeit nach oben, leichte Streuung, über die Lebensdauer abnehmende Skalierung.

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

Rauch

Grauer Verlauf, langsame Aufwärtsbewegung, große Streuung, niedriger Alphawert für ein schleierhaftes Aussehen.

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

Regen

Blau-weiße Streifen, starke Abwärtsschwerkraft, schmale Streuung, hohe Partikelanzahl.

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

Funken

Gelb-orange, explosiver Ausbruch, hohe Anfangsgeschwindigkeit, kurze Lebensdauer.

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

Schnee

Weiße Partikel, langsames sanftes Herabsinken, breite Streuung, leichte horizontale Drift.

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. 3D-Partikel (GPUParticles3D)

GPUParticles3D funktioniert identisch zu GPUParticles2D, arbeitet aber im 3D-Raum. Es wird dasselbe ParticleProcessMaterial verwendet. Die wichtigsten Unterschiede:

  • Alle 3 Achsen von Vector3 sind aktiv (X, Y, Z)
  • Emissionsformen funktionieren in vollem 3D (Kugeln sind echte Kugeln, Boxen sind Volumen)
  • Du kannst draw_pass_1 mit einem QuadMesh oder einem eigenen Mesh für Billboard-Partikel verwenden
  • Mesh-Partikel können StandardMaterial3D oder ORMMaterial3D verwenden
# 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)
Tipp: Für Mesh-Partikel (Trümmer, Hülsen, Bruchstücke) weise draw_pass_1 ein eigenes .tres-Mesh zu und wende ein StandardMaterial3D darauf an. Jedes Partikel wird als Kopie dieses Meshes gerendert.

9. Sub-Emitter

Godot 4 unterstützt Sub-Emitter für Partikel-auf-Partikel-Effekte. Ein Sub-Emitter ist ein weiterer GPUParticles2D/GPUParticles3D-Node, der an der Position jedes Eltern-Partikels Partikel emittiert. Das ist ideal für:

  • Funken, die von Feuerpartikeln wegfliegen
  • Rauchfahnen hinter Feuerwerksexplosionen
  • Spritzwassertropfen bei Kollision
  • Sekundärexplosionen

So richtest du Sub-Emitter ein:

# 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)

Sub-Emitter-Modi:

  • SUB_EMITTER_CONSTANTEmittiert kontinuierlich von jedem Eltern-Partikel
  • SUB_EMITTER_AT_ENDEmittiert, wenn das Eltern-Partikel stirbt
  • SUB_EMITTER_AT_COLLISIONEmittiert, wenn das Eltern-Partikel kollidiert
Warnung: Sub-Emitter funktionieren nur mit GPUParticles, nicht mit CPUParticles. Jeder Sub-Emitter erhöht die GPU-Last, halte die Partikelanzahl von Sub-Emittern daher niedrig (4-8 pro Eltern-Partikel).

10. Performance-Tipps

  • Kompromiss Anzahl vs. Lebensdauer: Sichtbare Partikel gesamt = amount. Wenn du lifetime erhöhst, sammeln sich Partikel länger an. Reduziere amount zum Ausgleich oder erhöhe es für dichtere Effekte.
  • Visibility Range nutzen (3D): Setze visibility_range_end an GPUParticles3D, damit entfernte Partikelsysteme nicht mehr gerendert werden. Das ist ein großer Gewinn für offene Welten.
  • Feste FPS: Setze fixed_fps am Partikel-Node (z. B. 30), um Simulationsaktualisierungen zu begrenzen. Partikel interpolieren weiterhin flüssig, benötigen aber weniger GPU-Zeit.
  • One-Shot für Ausbrüche: Verwende für explosionsartige Effekte one_shot = true mit explosiveness = 1.0. Der Node kann dann nach Ablauf der Lebensdauer freigegeben werden.
  • LOD für 3D verwenden: Kombiniere visibility_range_begin/end mit mehreren Partikel-Nodes auf unterschiedlichen Detailstufen für Level-of-Detail-(LOD-)Partikeleffekte.
  • Overdraw vermeiden: Viele einander überlappende halbtransparente Partikel = teure Fill-Rate. Reduziere amount und verwende stattdessen größere Partikel mit niedrigerem Alphawert.

11. Migration von Godot 3 zu 4

Wenn du ein Projekt von Godot 3 zu Godot 4 migrierst, sind hier die wichtigsten Änderungen bei den Partikeln:

Godot 3 Godot 4 Hinweise
Particles2D GPUParticles2D Umbenannt
Particles (3D) GPUParticles3D Umbenannt
ParticlesMaterial ParticleProcessMaterial Umbenannt mit neuen Funktionen
CPUParticles2D CPUParticles2D Unverändert
CPUParticles CPUParticles3D Zur Klarheit umbenannt
No sub-emitters sub_emitter_mode Neu in Godot 4
No trails trail_enabled Neu in Godot 4
flag_align_y particle_flag_align_y Eigenschaft umbenannt
Tipp: Godots Projektkonverter übernimmt die meisten Umbenennungen automatisch. Alle ParticlesMaterial-Referenzen in GDScript müssen jedoch manuell auf ParticleProcessMaterial aktualisiert werden.

Partikeleffekte mit MCP Pro automatisieren

Willst du, dass die KI Partikeleffekte für dich erstellt? Godot MCP Pro enthält dedizierte Partikel-Tools mit Presets für Feuer, Rauch, Regen, Schnee, Funken und mehr. Volle Kontrolle über Materialien, Verläufe und Emissionsformen aus einem einzigen Gespräch.

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