Godot 4のGPUParticles2D/3D 完全ガイド

1. はじめに

Godot 4ではGPUParticles2DGPUParticles3Dがデフォルトのパーティクルシステムノードです。GPU上で完全に実行されるため、大量のパーティクルでもCPU版より大幅に高速です。Godot 3のParticlesMaterialParticleProcessMaterialに置き換えられ、パーティクルの動作をより細かく制御できるようになりました。

CPUParticles2DCPUParticles3Dは、GPUコンピュートをサポートしないデバイス向け、またはGDScriptから個々のパーティクルデータにアクセスする必要がある場合のフォールバックとして引き続き利用可能です。ほとんどのケースではGPUパーティクルが推奨されます。

2. GPUParticles2D vs CPUParticles2D

特徴 GPUParticles2D CPUParticles2D
処理 GPU(コンピュートシェーダー) CPU(メインスレッド)
最大パーティクル数 100,000+でも高速 実用的には1,000-5,000が限界
サブエミッター 対応 非対応
トレイル 対応 非対応
個別パーティクルアクセス 不可(GPU専用) 可能(GDScriptから)
互換モード Vulkan/Metal/D3D12が必要 どこでも動作
マテリアル型 ParticleProcessMaterial 組み込みプロパティ(マテリアル不要)
Tip: デフォルトでは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)
  • explosiveness — 0.0 = 連続放出、1.0 = 全パーティクルを一度に放出
  • randomness — 放出タイミングのランダムオフセット(0.0-1.0)
  • speed_scale — シミュレーション速度の倍率
  • emitting — 現在放出中かどうか

4. ParticleProcessMaterial 詳解

ParticleProcessMaterialは個々のパーティクルのライフタイム中の動作を制御します。2Dパーティクルでも方向プロパティはすべてVector3を使用します(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. カラーグラデーション

ParticleProcessMaterialcolor_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
Tip: グラデーションの最後は必ずアルファ=0にして、パーティクルが突然消えるのではなく滑らかにフェードアウトするようにしましょう。

均一な色のパーティクルにはグラデーションの代わりにフラットなcolorプロパティも設定できます:

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

6. エミッション形状

エミッション形状はパーティクルの出現位置を決定します。ParticleProcessMaterialemission_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
Tip: 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を使用します。主な違い:

  • Vector3の3軸すべてが有効(X, Y, Z)
  • エミッション形状が完全な3Dで動作(球は本物の球体、ボックスはボリューム)
  • draw_pass_1QuadMeshやカスタムメッシュを使ってビルボードパーティクルを作成可能
  • メッシュパーティクルには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)
Tip: メッシュパーティクル(破片、薬莢など)には、カスタム.tresメッシュをdraw_pass_1に割り当て、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. パフォーマンスのコツ

  • 数量 vs ライフタイムのトレードオフ: 同時表示パーティクル数 = amountlifetimeを増やすとパーティクルが長く残ります。補正のためamountを減らすか、より密度の高いエフェクトにするため増やしてください。
  • Visibility Range(3D)を使う: GPUParticles3Dにvisibility_range_endを設定すると、遠くのパーティクルシステムがレンダリングされなくなります。オープンワールドでは大きな効果があります。
  • 固定FPS: パーティクルノードにfixed_fpsを設定(例: 30)するとシミュレーション更新が制限されます。パーティクルは滑らかに補間されますがGPU使用量は減ります。
  • バースト用のOne-shot: 爆発エフェクトにはone_shot = trueexplosiveness = 1.0を使用します。ライフタイム終了後にノードを解放できます。
  • 3DでのLOD活用: visibility_range_begin/endを複数の異なる詳細レベルのパーティクルノードと組み合わせて、LOD(Level of Detail)パーティクルエフェクトを実現できます。
  • オーバードローを避ける: 半透明パーティクルが大量に重なる = フィルレートのコスト増大。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 プロパティ名変更
Tip: 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