Godot 4 的 GPUParticles2D/3D — 完整指南

1. 簡介

Godot 4 使用 GPUParticles2DGPUParticles3D 作為預設的粒子系統節點。它們完全在 GPU 上執行,因此在大量粒子的情況下明顯比 CPU 版本更快。Godot 3 中舊有的 ParticlesMaterial 已被 ParticleProcessMaterial 取代,後者對粒子行為提供了更多控制。

CPUParticles2DCPUParticles3D 仍然存在,作為缺乏 GPU 運算支援的裝置的後備節點,或當你需要從 GDScript 存取個別粒子資料時使用。在大多數情況下,GPU 粒子是建議的選擇。

2. GPUParticles2D vs. CPUParticles2D

特性 GPUParticles2D CPUParticles2D
處理方式 GPU(運算著色器) CPU(主執行緒)
最大粒子數 100,000+ 仍高效 約 1,000-5,000 的實用上限
Sub-Emitter 支援 不支援
拖尾 (Trails) 支援 不支援
逐粒子存取 不支援(僅 GPU) 支援(從 GDScript)
相容模式 需要 Vulkan/Metal/D3D12 到處都能運作
材質類型 ParticleProcessMaterial 內建屬性(無需材質)
提示: 預設請使用 GPUParticles2D。只有在你需要相容 (Compatibility) 渲染器支援、需要在 GDScript 中讀取粒子位置,或針對非常老舊的硬體時,才切換到 CPUParticles2D

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. 色彩漸層

ParticleProcessMaterial 上的 color_ramp 屬性接受一個 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. 發射形狀

發射形狀決定粒子在何處生成。它透過 ParticleProcessMaterial 上的 emission_shape 設定。

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

煙霧

灰色漸層、緩慢的上升運動、大範圍擴散,以及低 Alpha 值以呈現輕飄的外觀。

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。主要差異:

  • Vector3 的全部 3 個軸都會生效(X、Y、Z)
  • 發射形狀在完整的 3D 中運作(球體是真正的球,方框是體積)
  • 你可以將 draw_pass_1 搭配 QuadMesh 或自訂網格使用,來製作看板 (Billboard) 粒子
  • 網格粒子可以使用 StandardMaterial3DORMMaterial3D
# 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)
提示: 對於網格粒子(碎片、彈殼、碎塊),請將自訂的 .tres 網格指派給 draw_pass_1,並對其套用 StandardMaterial3D。每個粒子都會渲染為該網格的副本。

9. Sub-Emitter

Godot 4 支援 Sub-Emitter,用於粒子產生粒子的效果。Sub-Emitter 是另一個 GPUParticles2D/GPUParticles3D 節點,會在每個父粒子的位置發射粒子。它非常適合用於:

  • 從火焰粒子飛散出的火花
  • 煙火爆炸後方的煙霧尾跡
  • 碰撞時的飛濺水滴
  • 二次爆炸

以下是設定 Sub-Emitter 的方式:

# 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 模式:

  • SUB_EMITTER_CONSTANT從每個父粒子連續發射
  • SUB_EMITTER_AT_END當父粒子消亡時發射
  • SUB_EMITTER_AT_COLLISION當父粒子碰撞時發射
警告: Sub-Emitter 僅能與 GPUParticles 搭配運作,無法用於 CPUParticles。每個 Sub-Emitter 都會增加 GPU 負載,因此請將 Sub-Emitter 的粒子數保持在較低水準(每個父粒子 4-8 個)。

10. 效能技巧

  • 數量與生命週期的取捨: 總可見粒子數 = amount。如果你增加 lifetime,粒子會累積更久。可減少 amount 來補償,或增加它以獲得更密集的效果。
  • 使用可見範圍 (Visibility Range)(3D): 在 GPUParticles3D 上設定 visibility_range_end,讓遠處的粒子系統停止渲染。這對開放世界是一大優化。
  • 固定 FPS: 在粒子節點上設定 fixed_fps(例如 30),以限制模擬更新次數。粒子仍會平滑插值,但佔用更少的 GPU 時間。
  • 用 One-Shot 製作噴發: 對於爆炸類的效果,使用 one_shot = true 搭配 explosiveness = 1.0。之後可在生命週期結束後釋放該節點。
  • 在 3D 中使用 LOD: visibility_range_begin/end 與多個位於不同細節層級的粒子節點組合,以實現細節層次 (LOD) 粒子效果。
  • 避免過度繪製 (Overdraw): 大量彼此重疊的半透明粒子 = 昂貴的填充率。請減少 amount,改用較大且 Alpha 值較低的粒子。

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 的專案轉換工具會自動處理大多數的重新命名。不過,GDScript 中所有的 ParticlesMaterial 參照都需要手動更新為 ParticleProcessMaterial

使用 MCP Pro 自動化粒子效果

想讓 AI 為你建立粒子效果嗎?Godot MCP Pro 包含專用的粒子工具,並附有火焰、煙霧、雨、雪、火花等預設。在單一對話中完全掌控材質、漸層與發射形狀。

  • create_particles
  • set_particle_material
  • set_particle_color_gradient
  • apply_particle_preset
  • get_particle_info
取得 MCP Pro — $15