GPUParticles2D/3D no Godot 4 — Guia Completo

1. Introdução

O Godot 4 usa GPUParticles2D e GPUParticles3D como os nós padrão do sistema de partículas. Eles são executados inteiramente na GPU, o que os torna significativamente mais rápidos do que seus equivalentes de CPU para grandes quantidades de partículas. O antigo ParticlesMaterial do Godot 3 foi substituído pelo ParticleProcessMaterial, que oferece mais controle sobre o comportamento das partículas.

CPUParticles2D e CPUParticles3D ainda existem como nós de fallback para dispositivos sem suporte a compute na GPU ou quando você precisa acessar dados de partículas individuais a partir do GDScript. Na maioria dos casos, as partículas de GPU são a escolha recomendada.

2. GPUParticles2D vs. CPUParticles2D

Recurso GPUParticles2D CPUParticles2D
Processamento GPU (compute shader) CPU (thread principal)
Máximo de partículas 100.000+ com eficiência ~1.000-5.000 como limite prático
Sub-emissores Sim Não
Rastros (trails) Sim Não
Acesso por partícula Não (apenas GPU) Sim (a partir do GDScript)
Modo de compatibilidade Requer Vulkan/Metal/D3D12 Funciona em qualquer lugar
Tipo de material ParticleProcessMaterial Propriedades integradas (sem material)
Dica: Use GPUParticles2D por padrão. Mude para CPUParticles2D apenas se precisar de suporte ao renderizador Compatibility, precisar ler posições de partículas no GDScript ou tiver como alvo hardware muito antigo.

3. Configuração básica

Para criar um efeito de partículas básico, adicione um nó GPUParticles2D à sua cena e atribua a ele um ParticleProcessMaterial. Veja uma configuração mínima em 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)

Propriedades importantes do próprio nó GPUParticles2D:

  • amountNúmero de partículas vivas a qualquer momento (padrão: 8)
  • lifetimePor quanto tempo cada partícula vive, em segundos (padrão: 1.0)
  • one_shotEmite uma vez e depois para (padrão: false)
  • explosiveness0.0 = fluxo contínuo, 1.0 = todas as partículas de uma vez
  • randomnessDeslocamento aleatório no tempo de emissão (0.0-1.0)
  • speed_scaleMultiplicador da velocidade da simulação
  • emittingSe o sistema está emitindo no momento

4. ParticleProcessMaterial em detalhes

O ParticleProcessMaterial controla como cada partícula se comporta ao longo de sua vida. Todas as propriedades direcionais usam Vector3, mesmo para partículas 2D (o componente Z é simplesmente ignorado).

Direção e velocidade

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

Velocidade angular

Faz as partículas girarem enquanto se movem. Útil para destroços, confetes ou folhas.

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

Escala ao longo da vida

Use uma CurveTexture para alterar o tamanho da partícula ao longo de sua vida:

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

Amortecimento e atrator

O amortecimento (damping) desacelera as partículas com o tempo, simulando resistência do ar. Atratores (nós separados) puxam as partículas em direção a um ponto.

mat.damping_min = 5.0
mat.damping_max = 10.0

5. Gradientes de cor

A propriedade color_ramp do ParticleProcessMaterial aceita uma GradientTexture1D para alterar suavemente a cor da partícula ao longo de sua vida. Isso é essencial para efeitos realistas de fogo, fumaça e magia.

Exemplo: gradiente de fogo

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
Dica: Sempre termine seu gradiente com alfa = 0 para que as partículas desapareçam suavemente em vez de sumirem de repente.

Você também pode definir uma propriedade color fixa em vez de um gradiente para partículas de cor uniforme:

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

6. Formas de emissão

A forma de emissão determina onde as partículas surgem. Ela é definida por emission_shape no ParticleProcessMaterial.

Point

Todas as partículas surgem na origem do nó. É a forma padrão.

mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_POINT

Sphere / Sphere Surface

As partículas surgem aleatoriamente dentro de uma esfera (ou apenas em sua superfície).

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

As partículas surgem aleatoriamente dentro de um volume retangular.

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

Ring

As partículas surgem em uma forma de anel (rosca), definida por raio interno/externo e altura.

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
Dica: Combine EMISSION_SHAPE_BOX com uma extensão X larga e Y estreita para criar efeitos rentes ao chão, como trilhas de poeira ou partículas de passos.

7. Receitas práticas

Fogo

Gradiente laranja-avermelhado, velocidade para cima, leve dispersão, escala diminuindo ao longo da vida.

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

Fumaça

Gradiente cinza, movimento lento para cima, grande dispersão, alfa baixo para uma aparência esfumaçada.

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

Chuva

Riscos branco-azulados, forte gravidade para baixo, dispersão estreita, alta contagem de partículas.

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

Faíscas

Amarelo-alaranjado, explosão repentina, alta velocidade inicial, vida curta.

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

Partículas brancas, descida lenta e suave, ampla dispersão, leve deriva horizontal.

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. Partículas 3D (GPUParticles3D)

O GPUParticles3D funciona de forma idêntica ao GPUParticles2D, mas opera no espaço 3D. O mesmo ParticleProcessMaterial é usado. As principais diferenças:

  • Todos os 3 eixos de Vector3 ficam ativos (X, Y, Z)
  • As formas de emissão funcionam em 3D completo (esferas são esferas reais, caixas são volumes)
  • Você pode usar draw_pass_1 com um QuadMesh ou uma malha personalizada para partículas em billboard
  • Partículas de malha podem usar StandardMaterial3D ou 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)
Dica: Para partículas de malha (destroços, cápsulas, fragmentos), atribua uma malha .tres personalizada a draw_pass_1 e aplique um StandardMaterial3D a ela. Cada partícula será renderizada como uma cópia dessa malha.

9. Sub-emissores

O Godot 4 oferece suporte a sub-emissores para efeitos de partícula sobre partícula. Um sub-emissor é outro nó GPUParticles2D/GPUParticles3D que emite partículas na posição de cada partícula pai. Isso é ideal para:

  • Faíscas saindo de partículas de fogo
  • Rastros de fumaça atrás de explosões de fogos de artifício
  • Gotículas de respingo na colisão
  • Explosões secundárias

Para configurar sub-emissores:

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

Modos de sub-emissor:

  • SUB_EMITTER_CONSTANTEmite continuamente de cada partícula pai
  • SUB_EMITTER_AT_ENDEmite quando a partícula pai morre
  • SUB_EMITTER_AT_COLLISIONEmite quando a partícula pai colide
Aviso: Sub-emissores só funcionam com GPUParticles, não com CPUParticles. Cada sub-emissor adiciona sobrecarga à GPU, então mantenha baixa a contagem de partículas dos sub-emissores (4-8 por partícula pai).

10. Dicas de desempenho

  • Compromisso entre quantidade e vida: Total de partículas visíveis = amount. Se você aumentar lifetime, as partículas se acumulam por mais tempo. Reduza amount para compensar, ou aumente-o para efeitos mais densos.
  • Use o Visibility Range (3D): Defina visibility_range_end no GPUParticles3D para que sistemas de partículas distantes deixem de ser renderizados. Isso traz um grande ganho para mundos abertos.
  • FPS fixo: Defina fixed_fps no nó de partículas (por exemplo, 30) para limitar as atualizações da simulação. As partículas ainda interpolam suavemente, mas usam menos tempo de GPU.
  • One-shot para rajadas: Para efeitos do tipo explosão, use one_shot = true com explosiveness = 1.0. O nó pode então ser liberado após o fim da vida.
  • Use LOD para 3D: Combine visibility_range_begin/end com vários nós de partículas em diferentes níveis de detalhe para efeitos de partículas com Level of Detail (LOD).
  • Evite overdraw: Muitas partículas semitransparentes sobrepostas = fill rate caro. Reduza amount e use partículas maiores com alfa mais baixo no lugar.

11. Migração do Godot 3 para o 4

Se você está migrando um projeto do Godot 3 para o Godot 4, aqui estão as principais mudanças relativas às partículas:

Godot 3 Godot 4 Observações
Particles2D GPUParticles2D Renomeado
Particles (3D) GPUParticles3D Renomeado
ParticlesMaterial ParticleProcessMaterial Renomeado com novos recursos
CPUParticles2D CPUParticles2D Inalterado
CPUParticles CPUParticles3D Renomeado para maior clareza
No sub-emitters sub_emitter_mode Novo no Godot 4
No trails trail_enabled Novo no Godot 4
flag_align_y particle_flag_align_y Propriedade renomeada
Dica: O conversor de projetos do Godot cuida da maioria das renomeações automaticamente. No entanto, quaisquer referências a ParticlesMaterial no GDScript precisarão ser atualizadas manualmente para ParticleProcessMaterial.

Automatize efeitos de partículas com o MCP Pro

Quer que a IA crie efeitos de partículas para você? O Godot MCP Pro inclui ferramentas dedicadas de partículas com presets para fogo, fumaça, chuva, neve, faíscas e muito mais. Controle total sobre materiais, gradientes e formas de emissão a partir de uma única conversa.

  • create_particles
  • set_particle_material
  • set_particle_color_gradient
  • apply_particle_preset
  • get_particle_info
Obter o MCP Pro — $15