283 lines
7.5 KiB
GDScript
283 lines
7.5 KiB
GDScript
@tool
|
|
extends RefCounted
|
|
|
|
## Curated particle effect blueprints.
|
|
##
|
|
## Each preset returns {main, process, draw}. The handler applies them
|
|
## through the normal write path (one undo action wraps all spawns).
|
|
|
|
|
|
## Each preset has {main, process, draw}. `draw` configures the StandardMaterial3D
|
|
## attached to the auto-created QuadMesh in draw_pass_1 (GPU 3D only); if
|
|
## omitted, the handler falls back to a sensible billboard-particles default.
|
|
## `blend_mode: "add"` is what makes fire/magic/explosion glow — without
|
|
## additive blending, additively-layered particles just stack to gray.
|
|
const _PRESETS := {
|
|
"fire": {
|
|
"main": {
|
|
"amount": 80,
|
|
"lifetime": 1.2,
|
|
"one_shot": false,
|
|
"explosiveness": 0.0,
|
|
"preprocess": 0.5,
|
|
"local_coords": false,
|
|
},
|
|
"process": {
|
|
"emission_shape": "sphere",
|
|
"emission_sphere_radius": 0.3,
|
|
"direction": {"x": 0.0, "y": 1.0, "z": 0.0},
|
|
"spread": 15.0,
|
|
"initial_velocity_min": 2.0,
|
|
"initial_velocity_max": 4.0,
|
|
"gravity": {"x": 0.0, "y": 1.0, "z": 0.0}, # buoyancy
|
|
"scale_min": 0.4,
|
|
"scale_max": 0.8,
|
|
"color_ramp": {
|
|
"stops": [
|
|
{"time": 0.0, "color": [1.0, 1.0, 0.9, 1.0]},
|
|
{"time": 0.3, "color": [1.0, 0.6, 0.1, 1.0]},
|
|
{"time": 0.7, "color": [0.8, 0.1, 0.05, 0.7]},
|
|
{"time": 1.0, "color": [0.2, 0.05, 0.05, 0.0]},
|
|
]
|
|
},
|
|
},
|
|
"draw": {"blend_mode": "add"},
|
|
},
|
|
"smoke": {
|
|
"main": {
|
|
"amount": 40,
|
|
"lifetime": 3.0,
|
|
"one_shot": false,
|
|
"explosiveness": 0.0,
|
|
"local_coords": false,
|
|
},
|
|
"process": {
|
|
"emission_shape": "sphere",
|
|
"emission_sphere_radius": 0.4,
|
|
"direction": {"x": 0.0, "y": 1.0, "z": 0.0},
|
|
"spread": 20.0,
|
|
"initial_velocity_min": 0.5,
|
|
"initial_velocity_max": 1.5,
|
|
"gravity": {"x": 0.0, "y": 0.2, "z": 0.0},
|
|
"scale_min": 0.6,
|
|
"scale_max": 1.4,
|
|
"color_ramp": {
|
|
"stops": [
|
|
{"time": 0.0, "color": [0.3, 0.3, 0.3, 0.0]},
|
|
{"time": 0.25, "color": [0.35, 0.35, 0.35, 0.7]},
|
|
{"time": 0.75, "color": [0.2, 0.2, 0.2, 0.5]},
|
|
{"time": 1.0, "color": [0.1, 0.1, 0.1, 0.0]},
|
|
]
|
|
},
|
|
},
|
|
# Smoke uses regular alpha blending so it darkens the background.
|
|
"draw": {"blend_mode": "mix"},
|
|
},
|
|
"spark_burst": {
|
|
"main": {
|
|
"amount": 60,
|
|
"lifetime": 0.8,
|
|
"one_shot": true,
|
|
"explosiveness": 1.0,
|
|
"local_coords": false,
|
|
},
|
|
"process": {
|
|
"emission_shape": "point",
|
|
"direction": {"x": 0.0, "y": 1.0, "z": 0.0},
|
|
"spread": 180.0,
|
|
"initial_velocity_min": 5.0,
|
|
"initial_velocity_max": 12.0,
|
|
"gravity": {"x": 0.0, "y": -9.8, "z": 0.0},
|
|
"scale_min": 0.05,
|
|
"scale_max": 0.12,
|
|
"color": {"r": 1.0, "g": 0.9, "b": 0.2, "a": 1.0},
|
|
},
|
|
"draw": {
|
|
"blend_mode": "add",
|
|
"emission_enabled": true,
|
|
"emission": {"r": 1.0, "g": 0.8, "b": 0.2, "a": 1.0},
|
|
"emission_energy_multiplier": 2.0,
|
|
},
|
|
},
|
|
"magic_swirl": {
|
|
"main": {
|
|
"amount": 120,
|
|
"lifetime": 2.0,
|
|
"one_shot": false,
|
|
"explosiveness": 0.0,
|
|
"local_coords": false,
|
|
},
|
|
"process": {
|
|
"emission_shape": "ring",
|
|
"emission_ring_radius": 0.8,
|
|
"emission_ring_inner_radius": 0.6,
|
|
"emission_ring_height": 0.0,
|
|
"direction": {"x": 0.0, "y": 1.0, "z": 0.0},
|
|
"spread": 30.0,
|
|
"initial_velocity_min": 1.0,
|
|
"initial_velocity_max": 2.0,
|
|
"gravity": {"x": 0.0, "y": 0.0, "z": 0.0},
|
|
"angular_velocity_min": 90.0,
|
|
"angular_velocity_max": 180.0,
|
|
"scale_min": 0.1,
|
|
"scale_max": 0.2,
|
|
"color_ramp": {
|
|
"stops": [
|
|
{"time": 0.0, "color": [0.4, 0.9, 1.0, 0.0]},
|
|
{"time": 0.3, "color": [0.5, 0.7, 1.0, 1.0]},
|
|
{"time": 0.7, "color": [1.0, 0.4, 0.9, 1.0]},
|
|
{"time": 1.0, "color": [0.8, 0.2, 0.7, 0.0]},
|
|
]
|
|
},
|
|
},
|
|
"draw": {"blend_mode": "add"},
|
|
},
|
|
"rain": {
|
|
"main": {
|
|
"amount": 500,
|
|
"lifetime": 1.5,
|
|
"one_shot": false,
|
|
"explosiveness": 0.0,
|
|
"local_coords": false,
|
|
},
|
|
"process": {
|
|
"emission_shape": "box",
|
|
"emission_box_extents": {"x": 10.0, "y": 0.1, "z": 10.0},
|
|
"direction": {"x": 0.0, "y": -1.0, "z": 0.0},
|
|
"spread": 2.0,
|
|
"initial_velocity_min": 15.0,
|
|
"initial_velocity_max": 18.0,
|
|
"gravity": {"x": 0.0, "y": -2.0, "z": 0.0},
|
|
"scale_min": 0.02,
|
|
"scale_max": 0.04,
|
|
"color": {"r": 0.7, "g": 0.85, "b": 1.0, "a": 0.5},
|
|
},
|
|
# Rain drops render as streaks; fixed_y aligns them vertically.
|
|
"draw": {"billboard_mode": "fixed_y", "blend_mode": "mix"},
|
|
},
|
|
"explosion": {
|
|
"main": {
|
|
"amount": 200,
|
|
"lifetime": 1.5,
|
|
"one_shot": true,
|
|
"explosiveness": 1.0,
|
|
"local_coords": false,
|
|
},
|
|
"process": {
|
|
"emission_shape": "sphere",
|
|
"emission_sphere_radius": 0.1,
|
|
"direction": {"x": 0.0, "y": 1.0, "z": 0.0},
|
|
"spread": 180.0,
|
|
"initial_velocity_min": 6.0,
|
|
"initial_velocity_max": 10.0,
|
|
"gravity": {"x": 0.0, "y": -4.0, "z": 0.0},
|
|
"scale_min": 0.3,
|
|
"scale_max": 0.7,
|
|
"color_ramp": {
|
|
"stops": [
|
|
{"time": 0.0, "color": [1.0, 0.95, 0.5, 1.0]},
|
|
{"time": 0.2, "color": [1.0, 0.4, 0.1, 1.0]},
|
|
{"time": 0.7, "color": [0.3, 0.15, 0.1, 0.7]},
|
|
{"time": 1.0, "color": [0.1, 0.1, 0.1, 0.0]},
|
|
]
|
|
},
|
|
},
|
|
"draw": {
|
|
"blend_mode": "add",
|
|
"emission_enabled": true,
|
|
"emission": {"r": 1.0, "g": 0.5, "b": 0.1, "a": 1.0},
|
|
"emission_energy_multiplier": 1.5,
|
|
},
|
|
},
|
|
"lightning": {
|
|
# Short, bright, electric-blue spark burst. One-shot — call
|
|
# particle_restart to re-trigger. Pairs well with a scene-wide flash.
|
|
"main": {
|
|
"amount": 40,
|
|
"lifetime": 0.35,
|
|
"one_shot": true,
|
|
"explosiveness": 1.0,
|
|
"local_coords": false,
|
|
},
|
|
"process": {
|
|
"emission_shape": "box",
|
|
"emission_box_extents": {"x": 0.1, "y": 1.5, "z": 0.1},
|
|
"direction": {"x": 0.0, "y": -1.0, "z": 0.0},
|
|
"spread": 8.0,
|
|
"initial_velocity_min": 18.0,
|
|
"initial_velocity_max": 28.0,
|
|
"gravity": {"x": 0.0, "y": 0.0, "z": 0.0},
|
|
"scale_min": 0.08,
|
|
"scale_max": 0.18,
|
|
"color_ramp": {
|
|
"stops": [
|
|
{"time": 0.0, "color": [1.0, 1.0, 1.0, 1.0]},
|
|
{"time": 0.2, "color": [0.6, 0.85, 1.0, 1.0]},
|
|
{"time": 0.6, "color": [0.3, 0.5, 1.0, 0.9]},
|
|
{"time": 1.0, "color": [0.1, 0.2, 0.7, 0.0]},
|
|
]
|
|
},
|
|
},
|
|
"draw": {
|
|
"blend_mode": "add",
|
|
"emission_enabled": true,
|
|
"emission": {"r": 0.5, "g": 0.8, "b": 1.0, "a": 1.0},
|
|
"emission_energy_multiplier": 4.0,
|
|
},
|
|
},
|
|
}
|
|
|
|
|
|
static func list() -> Array:
|
|
return _PRESETS.keys()
|
|
|
|
|
|
static func has(preset_name: String) -> bool:
|
|
return _PRESETS.has(preset_name)
|
|
|
|
|
|
## Return deep-copied {main, process, draw} blueprint with overrides merged in.
|
|
## Overrides may include top-level "main" / "process" / "draw" dicts, or bare
|
|
## keys that get routed based on which group they belong to.
|
|
static func build(preset_name: String, overrides: Dictionary) -> Variant:
|
|
if not _PRESETS.has(preset_name):
|
|
return null
|
|
var entry: Dictionary = _PRESETS[preset_name].duplicate(true)
|
|
var main: Dictionary = entry.get("main", {})
|
|
var process: Dictionary = entry.get("process", {})
|
|
var draw: Dictionary = entry.get("draw", {})
|
|
for key in overrides:
|
|
var val = overrides[key]
|
|
if key == "main" and val is Dictionary:
|
|
for k in val:
|
|
main[k] = val[k]
|
|
elif key == "process" and val is Dictionary:
|
|
for k in val:
|
|
process[k] = val[k]
|
|
elif key == "draw" and val is Dictionary:
|
|
for k in val:
|
|
draw[k] = val[k]
|
|
elif _MAIN_KEYS.has(key):
|
|
main[key] = val
|
|
else:
|
|
process[key] = val
|
|
entry["main"] = main
|
|
entry["process"] = process
|
|
entry["draw"] = draw
|
|
return entry
|
|
|
|
|
|
const _MAIN_KEYS := {
|
|
"amount": true,
|
|
"lifetime": true,
|
|
"one_shot": true,
|
|
"explosiveness": true,
|
|
"preprocess": true,
|
|
"speed_scale": true,
|
|
"randomness": true,
|
|
"fixed_fps": true,
|
|
"emitting": true,
|
|
"local_coords": true,
|
|
"interp_to_end": true,
|
|
}
|