GPUParticles2D/3D en Godot 4 — Guía completa

1. Introducción

Godot 4 usa GPUParticles2D y GPUParticles3D como nodos de sistema de partículas predeterminados. Se ejecutan por completo en la GPU, lo que los hace considerablemente más rápidos que sus equivalentes en CPU cuando hay grandes cantidades de partículas. El antiguo ParticlesMaterial de Godot 3 ha sido reemplazado por ParticleProcessMaterial, que ofrece más control sobre el comportamiento de las partículas.

CPUParticles2D y CPUParticles3D siguen existiendo como nodos de reserva para dispositivos que carecen de soporte de cómputo en GPU o cuando necesitas acceder a los datos de partículas individuales desde GDScript. Para la mayoría de los casos de uso, las partículas de GPU son la opción recomendada.

2. GPUParticles2D vs CPUParticles2D

Característica GPUParticles2D CPUParticles2D
Procesamiento GPU (compute shader) CPU (hilo principal)
Máximo de partículas 100.000+ de forma eficiente Límite práctico de ~1.000-5.000
Sub-emisores No
Estelas (trails) No
Acceso por partícula No (solo GPU) Sí (desde GDScript)
Modo de compatibilidad Requiere Vulkan/Metal/D3D12 Funciona en todas partes
Tipo de material ParticleProcessMaterial Propiedades integradas (sin material)
Consejo: Usa GPUParticles2D por defecto. Cambia a CPUParticles2D solo si necesitas soporte del renderizador Compatibility, necesitas leer las posiciones de las partículas en GDScript o apuntas a hardware muy antiguo.

3. Configuración básica

Para crear un efecto de partículas básico, añade un nodo GPUParticles2D a tu escena y asígnale un ParticleProcessMaterial. Aquí tienes una configuración mínima en 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)

Propiedades clave del propio nodo GPUParticles2D:

  • amountNúmero de partículas vivas en cualquier momento (predeterminado: 8)
  • lifetimeCuánto vive cada partícula en segundos (predeterminado: 1.0)
  • one_shotEmitir una vez y detenerse (predeterminado: false)
  • explosiveness0.0 = flujo continuo, 1.0 = todas las partículas a la vez
  • randomnessDesplazamiento aleatorio del tiempo de emisión (0.0-1.0)
  • speed_scaleMultiplicador de la velocidad de simulación
  • emittingSi el sistema está emitiendo actualmente

4. ParticleProcessMaterial en profundidad

ParticleProcessMaterial controla cómo se comportan las partículas individuales a lo largo de su vida. Todas las propiedades direccionales usan Vector3, incluso para partículas 2D (el componente Z simplemente se ignora).

Dirección y velocidad

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

Velocidad angular

Hace girar las partículas mientras se mueven. Útil para escombros, confeti u hojas.

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

Escala a lo largo de la vida

Usa una CurveTexture para cambiar el tamaño de la partícula a lo largo de su 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

Amortiguación y atractor

La amortiguación ralentiza las partículas con el tiempo, simulando la resistencia del aire. Los atractores (nodos independientes) atraen las partículas hacia un punto.

mat.damping_min = 5.0
mat.damping_max = 10.0

5. Gradientes de color

La propiedad color_ramp de ParticleProcessMaterial acepta una GradientTexture1D para cambiar suavemente el color de la partícula a lo largo de su vida. Esto es esencial para efectos realistas de fuego, humo y magia.

Ejemplo de gradiente de fuego

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
Consejo: Termina siempre tu gradiente con alpha = 0 para que las partículas se atenúen suavemente en lugar de desaparecer de golpe.

También puedes establecer una propiedad color plana en lugar de un gradiente para partículas de color uniforme:

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

6. Formas de emisión

La forma de emisión determina dónde aparecen las partículas. Se establece mediante emission_shape en ParticleProcessMaterial.

Point

Todas las partículas aparecen en el origen del nodo. Forma predeterminada.

mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_POINT

Sphere / Sphere Surface

Las partículas aparecen aleatoriamente dentro de una esfera (o solo en su 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

Las partículas aparecen aleatoriamente dentro de un volumen rectangular.

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

Ring

Las partículas aparecen en una forma de anillo (dónut), definida por el radio interior/exterior y la 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
Consejo: Combina EMISSION_SHAPE_BOX con una extensión X amplia y una Y estrecha para crear efectos a nivel del suelo, como estelas de polvo o partículas de pisadas.

7. Recetas prácticas

Fuego

Gradiente naranja-rojo, velocidad hacia arriba, ligera dispersión, escala decreciente a lo largo de la 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

Humo

Gradiente gris, movimiento lento hacia arriba, gran dispersión, alpha bajo para un aspecto tenue.

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

Lluvia

Trazos azul-blanco, gravedad fuerte hacia abajo, dispersión estrecha, alto número 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

Chispas

Amarillo-naranja, ráfaga explosiva, alta velocidad inicial, vida corta.

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

Nieve

Partículas blancas, descenso lento y suave, amplia dispersión, ligera 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)

GPUParticles3D funciona de forma idéntica a GPUParticles2D, pero opera en el espacio 3D. Se usa el mismo ParticleProcessMaterial. Diferencias clave:

  • Los 3 ejes de Vector3 están activos (X, Y, Z)
  • Las formas de emisión funcionan en 3D completo (las esferas son esferas reales, las cajas son volúmenes)
  • Puedes usar draw_pass_1 con un QuadMesh o una malla personalizada para partículas billboard
  • Las partículas de malla pueden usar StandardMaterial3D u 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)
Consejo: Para partículas de malla (escombros, casquillos, fragmentos), asigna una malla .tres personalizada a draw_pass_1 y aplícale un StandardMaterial3D. Cada partícula se renderizará como una copia de esa malla.

9. Sub-emisores

Godot 4 admite sub-emisores para efectos de partícula sobre partícula. Un sub-emisor es otro nodo GPUParticles2D/GPUParticles3D que emite partículas en la posición de cada partícula padre. Es ideal para:

  • Chispas que saltan de las partículas de fuego
  • Estelas de humo tras las explosiones de fuegos artificiales
  • Gotas de salpicadura al colisionar
  • Explosiones secundarias

Para configurar los sub-emisores:

# 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-emisor:

  • SUB_EMITTER_CONSTANTEmite continuamente desde cada partícula padre
  • SUB_EMITTER_AT_ENDEmite cuando la partícula padre muere
  • SUB_EMITTER_AT_COLLISIONEmite cuando la partícula padre colisiona
Advertencia: Los sub-emisores solo funcionan con GPUParticles, no con CPUParticles. Cada sub-emisor añade carga a la GPU, así que mantén bajo el número de partículas de los sub-emisores (4-8 por partícula padre).

10. Consejos de rendimiento

  • Compromiso entre cantidad y vida: El total de partículas visibles = amount. Si aumentas lifetime, las partículas se acumulan durante más tiempo. Reduce amount para compensar, o auméntalo para efectos más densos.
  • Usa Visibility Range (3D): Establece visibility_range_end en GPUParticles3D para que los sistemas de partículas lejanos dejen de renderizarse. Es una gran ventaja para los mundos abiertos.
  • FPS fijos: Establece fixed_fps en el nodo de partículas (p. ej., 30) para limitar las actualizaciones de simulación. Las partículas seguirán interpolándose con suavidad, pero usarán menos tiempo de GPU.
  • One-shot para ráfagas: Para efectos tipo explosión, usa one_shot = true con explosiveness = 1.0. Luego puedes liberar el nodo una vez que termina la vida.
  • Usa LOD para 3D: Combina visibility_range_begin/end con varios nodos de partículas en distintos niveles de detalle para efectos de partículas con Nivel de Detalle (LOD).
  • Evita el overdraw: Muchas partículas semitransparentes superpuestas = una fill rate costosa. Reduce amount y usa en su lugar partículas más grandes con menor alpha.

11. Migración de Godot 3 a 4

Si estás migrando un proyecto de Godot 3 a Godot 4, estos son los cambios clave para las partículas:

Godot 3 Godot 4 Notas
Particles2D GPUParticles2D Renombrado
Particles (3D) GPUParticles3D Renombrado
ParticlesMaterial ParticleProcessMaterial Renombrado con nuevas funciones
CPUParticles2D CPUParticles2D Sin cambios
CPUParticles CPUParticles3D Renombrado para mayor claridad
Sin sub-emisores sub_emitter_mode Nuevo en Godot 4
Sin estelas trail_enabled Nuevo en Godot 4
flag_align_y particle_flag_align_y Propiedad renombrada
Consejo: El conversor de proyectos de Godot gestiona la mayoría de los renombramientos automáticamente. Sin embargo, cualquier referencia a ParticlesMaterial en GDScript deberá actualizarse manualmente a ParticleProcessMaterial.

Automatiza efectos de partículas con MCP Pro

¿Quieres que la IA cree efectos de partículas por ti? Godot MCP Pro incluye herramientas de partículas dedicadas con presets para fuego, humo, lluvia, nieve, chispas y mucho más. Control total sobre materiales, gradientes y formas de emisión desde una sola conversación.

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