GPUParticles2D/3D w Godot 4 — Kompletny przewodnik

1. Wprowadzenie

Godot 4 używa węzłów GPUParticles2D i GPUParticles3D jako domyślnych węzłów systemu cząsteczkowego. Działają one w całości na GPU, dzięki czemu są znacznie szybsze od swoich odpowiedników procesorowych przy dużej liczbie cząsteczek. Stary ParticlesMaterial z Godot 3 został zastąpiony przez ParticleProcessMaterial, który daje większą kontrolę nad zachowaniem cząsteczek.

CPUParticles2D i CPUParticles3D nadal istnieją jako węzły awaryjne dla urządzeń bez obsługi obliczeń na GPU lub gdy musisz uzyskać dostęp do danych poszczególnych cząsteczek z poziomu GDScript. W większości przypadków zalecanym wyborem są cząsteczki GPU.

2. GPUParticles2D vs CPUParticles2D

Cecha GPUParticles2D CPUParticles2D
Przetwarzanie GPU (compute shader) CPU (wątek główny)
Maks. cząsteczek 100 000+ wydajnie ~1 000-5 000 praktyczny limit
Sub-emittery Tak Nie
Smugi (trails) Tak Nie
Dostęp do pojedynczej cząsteczki Nie (tylko GPU) Tak (z GDScript)
Tryb kompatybilności Wymaga Vulkan/Metal/D3D12 Działa wszędzie
Typ materiału ParticleProcessMaterial Wbudowane właściwości (bez materiału)
Wskazówka: Domyślnie używaj GPUParticles2D. Przełącz się na CPUParticles2D tylko wtedy, gdy potrzebujesz obsługi renderera Compatibility, musisz odczytywać pozycje cząsteczek w GDScript lub kierujesz produkt na bardzo stary sprzęt.

3. Podstawowa konfiguracja

Aby stworzyć prosty efekt cząsteczkowy, dodaj do sceny węzeł GPUParticles2D i przypisz mu ParticleProcessMaterial. Oto minimalna konfiguracja w 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)

Kluczowe właściwości samego węzła GPUParticles2D:

  • amountLiczba cząsteczek żywych w danej chwili (domyślnie: 8)
  • lifetimeJak długo żyje każda cząsteczka w sekundach (domyślnie: 1.0)
  • one_shotEmituj raz, potem zatrzymaj (domyślnie: false)
  • explosiveness0.0 = ciągły strumień, 1.0 = wszystkie cząsteczki naraz
  • randomnessLosowe przesunięcie czasu emisji (0.0-1.0)
  • speed_scaleMnożnik prędkości symulacji
  • emittingCzy system aktualnie emituje

4. ParticleProcessMaterial w szczegółach

ParticleProcessMaterial steruje tym, jak poszczególne cząsteczki zachowują się przez cały swój czas życia. Wszystkie właściwości kierunkowe używają Vector3, nawet dla cząsteczek 2D (składowa Z jest po prostu ignorowana).

Kierunek i prędkość

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

Prędkość kątowa

Sprawia, że cząsteczki obracają się podczas ruchu. Przydatne dla odłamków, konfetti czy liści.

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

Skala w czasie życia

Użyj CurveTexture, aby zmieniać rozmiar cząsteczki w trakcie jej życia:

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

Tłumienie i atraktor

Tłumienie spowalnia cząsteczki w czasie, symulując opór powietrza. Atraktory (osobne węzły) przyciągają cząsteczki do wybranego punktu.

mat.damping_min = 5.0
mat.damping_max = 10.0

5. Gradienty kolorów

Właściwość color_ramp w ParticleProcessMaterial przyjmuje GradientTexture1D, aby płynnie zmieniać kolor cząsteczki w trakcie jej życia. Jest to niezbędne dla realistycznych efektów ognia, dymu i magii.

Przykład: gradient ognia

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
Wskazówka: Zawsze kończ swój gradient wartością alpha = 0, aby cząsteczki płynnie zanikały, zamiast nagle znikać.

Dla cząsteczek o jednolitym kolorze zamiast gradientu możesz ustawić także płaską właściwość color:

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

6. Kształty emisji

Kształt emisji określa, gdzie powstają cząsteczki. Ustawia się go za pomocą emission_shape w ParticleProcessMaterial.

Point

Wszystkie cząsteczki powstają w punkcie początkowym węzła. Kształt domyślny.

mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_POINT

Sphere / Sphere Surface

Cząsteczki powstają losowo wewnątrz sfery (lub tylko na jej powierzchni).

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

Cząsteczki powstają losowo wewnątrz prostopadłościennej objętości.

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

Ring

Cząsteczki powstają w kształcie pierścienia (donutu), definiowanego przez promień wewnętrzny/zewnętrzny oraz wysokość.

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
Wskazówka: Połącz EMISSION_SHAPE_BOX z szerokim rozmiarem X i wąskim Y, aby tworzyć efekty przy podłożu, takie jak smugi kurzu czy cząsteczki kroków.

7. Praktyczne przepisy

Ogień

Pomarańczowo-czerwony gradient, prędkość skierowana w górę, niewielkie rozproszenie, malejąca skala w czasie życia.

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

Dym

Szary gradient, powolny ruch w górę, duże rozproszenie, niska wartość alpha dla mglistego wyglądu.

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

Deszcz

Niebiesko-białe smugi, silna grawitacja w dół, wąskie rozproszenie, duża liczba cząsteczek.

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

Iskry

Żółto-pomarańczowe, wybuchowy wyrzut, wysoka prędkość początkowa, krótki czas życia.

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

Śnieg

Białe cząsteczki, powolne, łagodne opadanie, szerokie rozproszenie, niewielki dryf poziomy.

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. Cząsteczki 3D (GPUParticles3D)

GPUParticles3D działa identycznie jak GPUParticles2D, ale operuje w przestrzeni 3D. Używany jest ten sam ParticleProcessMaterial. Najważniejsze różnice:

  • Wszystkie 3 osie Vector3 są aktywne (X, Y, Z)
  • Kształty emisji działają w pełnym 3D (sfery są prawdziwymi sferami, boksy są objętościami)
  • Możesz użyć draw_pass_1 z QuadMesh lub własną siatką dla cząsteczek typu billboard
  • Cząsteczki z siatką mogą używać StandardMaterial3D lub 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)
Wskazówka: Dla cząsteczek z siatką (odłamki, łuski, kawałki) przypisz własną siatkę .tres do draw_pass_1 i zastosuj na niej StandardMaterial3D. Każda cząsteczka zostanie wyrenderowana jako kopia tej siatki.

9. Sub-emittery

Godot 4 obsługuje sub-emittery dla efektów cząsteczka-na-cząsteczkę. Sub-emitter to kolejny węzeł GPUParticles2D/GPUParticles3D, który emituje cząsteczki w miejscu każdej cząsteczki nadrzędnej. Jest to idealne do:

  • Iskier odlatujących od cząsteczek ognia
  • Smug dymu za wybuchami fajerwerków
  • Kropli rozprysku przy kolizji
  • Wtórnych eksplozji

Aby skonfigurować sub-emittery:

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

Tryby sub-emittera:

  • SUB_EMITTER_CONSTANTEmituje w sposób ciągły z każdej cząsteczki nadrzędnej
  • SUB_EMITTER_AT_ENDEmituje, gdy cząsteczka nadrzędna umiera
  • SUB_EMITTER_AT_COLLISIONEmituje, gdy cząsteczka nadrzędna zderza się
Ostrzeżenie: Sub-emittery działają wyłącznie z GPUParticles, a nie z CPUParticles. Każdy sub-emitter zwiększa obciążenie GPU, dlatego utrzymuj niską liczbę cząsteczek sub-emitterów (4-8 na cząsteczkę nadrzędną).

10. Wskazówki wydajnościowe

  • Kompromis między liczbą a czasem życia: Łączna liczba widocznych cząsteczek = amount. Jeśli zwiększysz lifetime, cząsteczki kumulują się dłużej. Zmniejsz amount, aby to zrównoważyć, lub zwiększ go, by uzyskać gęstsze efekty.
  • Używaj Visibility Range (3D): Ustaw visibility_range_end na GPUParticles3D, aby odległe systemy cząsteczkowe przestały być renderowane. To duży zysk w otwartych światach.
  • Stałe FPS: Ustaw fixed_fps na węźle cząsteczek (np. 30), aby ograniczyć aktualizacje symulacji. Cząsteczki nadal będą płynnie interpolowane, ale zużyją mniej czasu GPU.
  • One-shot dla wyrzutów: Dla efektów przypominających eksplozje użyj one_shot = true wraz z explosiveness = 1.0. Węzeł można wtedy zwolnić po zakończeniu czasu życia.
  • Używaj LOD dla 3D: Połącz visibility_range_begin/end z wieloma węzłami cząsteczek na różnych poziomach szczegółowości, aby uzyskać efekty cząsteczkowe z poziomem detali (LOD).
  • Unikaj overdraw: Wiele nakładających się półprzezroczystych cząsteczek = kosztowny fill rate. Zmniejsz amount i zamiast tego użyj większych cząsteczek o niższej wartości alpha.

11. Migracja z Godot 3 do 4

Jeśli migrujesz projekt z Godot 3 do Godot 4, oto najważniejsze zmiany dotyczące cząsteczek:

Godot 3 Godot 4 Uwagi
Particles2D GPUParticles2D Zmieniona nazwa
Particles (3D) GPUParticles3D Zmieniona nazwa
ParticlesMaterial ParticleProcessMaterial Zmieniona nazwa wraz z nowymi funkcjami
CPUParticles2D CPUParticles2D Bez zmian
CPUParticles CPUParticles3D Zmieniona nazwa dla jasności
No sub-emitters sub_emitter_mode Nowość w Godot 4
No trails trail_enabled Nowość w Godot 4
flag_align_y particle_flag_align_y Zmieniona nazwa właściwości
Wskazówka: Konwerter projektów Godota automatycznie obsługuje większość zmian nazw. Jednak wszelkie odwołania do ParticlesMaterial w GDScript trzeba ręcznie zaktualizować na ParticleProcessMaterial.

Automatyzuj efekty cząsteczkowe z MCP Pro

Chcesz, aby AI tworzyła dla Ciebie efekty cząsteczkowe? Godot MCP Pro zawiera dedykowane narzędzia do cząsteczek z gotowymi ustawieniami dla ognia, dymu, deszczu, śniegu, iskier i innych. Pełna kontrola nad materiałami, gradientami i kształtami emisji w ramach jednej rozmowy.

  • create_particles
  • set_particle_material
  • set_particle_color_gradient
  • apply_particle_preset
  • get_particle_info
Zdobądź MCP Pro — $15