GPUParticles2D/3D в Godot 4 — Полное руководство

1. Введение

Godot 4 использует GPUParticles2D и GPUParticles3D в качестве стандартных узлов системы частиц. Они полностью работают на GPU, что делает их значительно быстрее их аналогов на CPU при большом количестве частиц. Старый ParticlesMaterial из Godot 3 был заменён на ParticleProcessMaterial, который даёт больше контроля над поведением частиц.

CPUParticles2D и CPUParticles3D по-прежнему существуют как резервные узлы для устройств без поддержки GPU-вычислений или когда вам нужно обращаться к данным отдельных частиц из GDScript. Для большинства случаев рекомендуется выбирать частицы на GPU.

2. GPUParticles2D против CPUParticles2D

Характеристика GPUParticles2D CPUParticles2D
Обработка GPU (compute-шейдер) CPU (основной поток)
Макс. частиц 100 000+ эффективно ~1 000-5 000 практический предел
Суб-эмиттеры Да Нет
Трейлы Да Нет
Доступ к отдельным частицам Нет (только GPU) Да (из GDScript)
Режим совместимости Требует Vulkan/Metal/D3D12 Работает везде
Тип материала ParticleProcessMaterial Встроенные свойства (без материала)
Совет: Используйте GPUParticles2D по умолчанию. Переключайтесь на CPUParticles2D только если вам нужна поддержка рендерера Compatibility, требуется читать позиции частиц в GDScript или вы ориентируетесь на очень старое оборудование.

3. Базовая настройка

Чтобы создать базовый эффект частиц, добавьте в сцену узел GPUParticles2D и назначьте ему ParticleProcessMaterial. Вот минимальная настройка в 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)

Ключевые свойства самого узла GPUParticles2D:

  • amountКоличество одновременно живущих частиц (по умолчанию: 8)
  • lifetimeКак долго живёт каждая частица в секундах (по умолчанию: 1.0)
  • one_shotИспустить один раз и остановиться (по умолчанию: false)
  • explosiveness0.0 = непрерывный поток, 1.0 = все частицы сразу
  • randomnessСлучайное смещение времени эмиссии (0.0-1.0)
  • speed_scaleМножитель скорости симуляции
  • emittingИспускает ли система частицы в данный момент

4. ParticleProcessMaterial в деталях

ParticleProcessMaterial управляет тем, как ведут себя отдельные частицы на протяжении их жизни. Все свойства направления используют Vector3, даже для 2D-частиц (компонента Z просто игнорируется).

Направление и скорость

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

Угловая скорость

Вращает частицы во время движения. Полезно для обломков, конфетти или листьев.

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

Масштаб на протяжении жизни

Используйте CurveTexture, чтобы изменять размер частицы на протяжении её жизни:

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

Затухание и аттрактор

Затухание замедляет частицы со временем, имитируя сопротивление воздуха. Аттракторы (отдельные узлы) притягивают частицы к точке.

mat.damping_min = 5.0
mat.damping_max = 10.0

5. Цветовые градиенты

Свойство color_ramp у ParticleProcessMaterial принимает GradientTexture1D для плавного изменения цвета частиц на протяжении их жизни. Это необходимо для реалистичных эффектов огня, дыма и магии.

Пример градиента для огня

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
Совет: Всегда завершайте градиент значением alpha = 0, чтобы частицы плавно исчезали, а не пропадали резко.

Для частиц однородного цвета вместо градиента можно задать плоское свойство color:

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

6. Формы эмиссии

Форма эмиссии определяет, где появляются частицы. Задаётся через emission_shape у ParticleProcessMaterial.

Point

Все частицы появляются в начале координат узла. Форма по умолчанию.

mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_POINT

Sphere / Sphere Surface

Частицы появляются случайным образом внутри сферы (или только на её поверхности).

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

Частицы появляются случайным образом внутри прямоугольного объёма.

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

Ring

Частицы появляются в форме кольца (пончика), определяемой внутренним/внешним радиусом и высотой.

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
Совет: Сочетайте EMISSION_SHAPE_BOX с широкой протяжённостью по X и узкой по Y, чтобы создавать эффекты на уровне земли, такие как пылевые шлейфы или частицы шагов.

7. Практические рецепты

Огонь

Оранжево-красный градиент, скорость вверх, лёгкий разброс, уменьшение масштаба на протяжении жизни.

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

Дым

Серый градиент, медленное движение вверх, большой разброс, низкая альфа для лёгкого дымчатого вида.

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

Дождь

Сине-белые полосы, сильная гравитация вниз, узкий разброс, большое количество частиц.

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

Искры

Жёлто-оранжевые, взрывной выброс, высокая начальная скорость, короткая жизнь.

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

Снег

Белые частицы, медленное плавное падение, широкий разброс, лёгкий горизонтальный дрейф.

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-частицы (GPUParticles3D)

GPUParticles3D работает идентично GPUParticles2D, но действует в 3D-пространстве. Используется тот же ParticleProcessMaterial. Ключевые отличия:

  • Все 3 оси Vector3 активны (X, Y, Z)
  • Формы эмиссии работают в полном 3D (сферы — настоящие сферы, боксы — объёмы)
  • Вы можете использовать draw_pass_1 с QuadMesh или собственным мешем для billboard-частиц
  • Меш-частицы могут использовать StandardMaterial3D или 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)
Совет: Для меш-частиц (обломки, гильзы, куски) назначьте draw_pass_1 собственный меш .tres и примените к нему StandardMaterial3D. Каждая частица будет отрисовываться как копия этого меша.

9. Суб-эмиттеры

Godot 4 поддерживает суб-эмиттеры для эффектов частиц поверх частиц. Суб-эмиттер — это ещё один узел GPUParticles2D/GPUParticles3D, который испускает частицы в позиции каждой родительской частицы. Это идеально подходит для:

  • Искр, разлетающихся от частиц огня
  • Дымовых шлейфов за взрывами фейерверков
  • Капель брызг при столкновении
  • Вторичных взрывов

Чтобы настроить суб-эмиттеры:

# 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_CONSTANTНепрерывно испускать от каждой родительской частицы
  • SUB_EMITTER_AT_ENDИспустить, когда родительская частица умирает
  • SUB_EMITTER_AT_COLLISIONИспустить, когда родительская частица сталкивается
Внимание: Суб-эмиттеры работают только с GPUParticles, но не с CPUParticles. Каждый суб-эмиттер добавляет нагрузку на GPU, поэтому держите количество частиц суб-эмиттеров низким (4-8 на родительскую частицу).

10. Советы по производительности

  • Компромисс количества и времени жизни: Всего видимых частиц = amount. Если вы увеличиваете lifetime, частицы накапливаются дольше. Уменьшите amount для компенсации или увеличьте его для более плотных эффектов.
  • Используйте Visibility Range (3D): Задайте visibility_range_end у GPUParticles3D, чтобы удалённые системы частиц перестали отрисовываться. Это большой выигрыш для открытых миров.
  • Фиксированный FPS: Задайте fixed_fps у узла частиц (например, 30), чтобы ограничить обновления симуляции. Частицы по-прежнему будут плавно интерполироваться, но потребуют меньше времени GPU.
  • One-shot для всплесков: Для взрывоподобных эффектов используйте one_shot = true с explosiveness = 1.0. Узел затем можно освободить после окончания времени жизни.
  • Используйте LOD для 3D: Сочетайте visibility_range_begin/end с несколькими узлами частиц на разных уровнях детализации для LOD-эффектов частиц (Level of Detail).
  • Избегайте overdraw: Множество перекрывающихся полупрозрачных частиц = дорогая fill rate. Уменьшите amount и вместо этого используйте более крупные частицы с меньшей альфой.

11. Миграция с Godot 3 на 4

Если вы переносите проект с Godot 3 на Godot 4, вот ключевые изменения, касающиеся частиц:

Godot 3 Godot 4 Примечания
Particles2D GPUParticles2D Переименован
Particles (3D) GPUParticles3D Переименован
ParticlesMaterial ParticleProcessMaterial Переименован с новыми возможностями
CPUParticles2D CPUParticles2D Без изменений
CPUParticles CPUParticles3D Переименован для ясности
No sub-emitters sub_emitter_mode Новое в Godot 4
No trails trail_enabled Новое в Godot 4
flag_align_y particle_flag_align_y Свойство переименовано
Совет: Конвертер проектов Godot обрабатывает большинство переименований автоматически. Однако любые ссылки на ParticlesMaterial в GDScript нужно будет вручную обновить на ParticleProcessMaterial.

Автоматизируйте эффекты частиц с MCP Pro

Хотите, чтобы ИИ создавал эффекты частиц за вас? Godot MCP Pro включает специальные инструменты для частиц с пресетами для огня, дыма, дождя, снега, искр и не только. Полный контроль над материалами, градиентами и формами эмиссии из одного разговора.

  • create_particles
  • set_particle_material
  • set_particle_color_gradient
  • apply_particle_preset
  • get_particle_info
Получить MCP Pro — $15