Add scene-authored FX library and bull ability effects
An Fx autoload + one_shot_effect base so every effect is an inspector-editable .tscn (the systems/fx/ authoring dir): blood/slam/spark/flash primitives and the bull's kick/dash/slam/roll + movement effects (bull_*.tscn), fired via Fx.spawn() / Fx.attach(). Plus a preview gallery + smoke test, and bull_impact_fx.gd for the coded, non-particle slam rings + kick flash-pop. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CCyaJPTHCH9EDthpf4A5Sj
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
extends SceneTree
|
||||
## Seeds the placeholder effect .tscn (and the gallery scene) once, headless. Those .tscn are the
|
||||
## source of truth Siim edits in the inspector — this script only builds their first-draft defaults
|
||||
## (ported from the old code-built particles). Re-run only to reset a placeholder from scratch:
|
||||
## godot --headless --script tools/seed_fx_placeholders.gd
|
||||
|
||||
const _EFFECT_SCRIPT := "res://systems/fx/one_shot_effect.gd"
|
||||
const _GALLERY_SCRIPT := "res://systems/fx/effect_gallery.gd"
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
_seed_blood_burst()
|
||||
_seed_slam_dust()
|
||||
_seed_spark()
|
||||
_seed_hit_flash()
|
||||
_seed_gallery()
|
||||
print("Seeded FX placeholder scenes into res://systems/fx/")
|
||||
quit()
|
||||
|
||||
|
||||
# ── shared builders ─────────────────────────────────────────────────────────
|
||||
func _unshaded() -> StandardMaterial3D:
|
||||
var m := StandardMaterial3D.new()
|
||||
m.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
m.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
m.vertex_color_use_as_albedo = true
|
||||
m.cull_mode = BaseMaterial3D.CULL_DISABLED
|
||||
return m
|
||||
|
||||
|
||||
func _sphere(radius: float) -> SphereMesh:
|
||||
var s := SphereMesh.new()
|
||||
s.radius = radius
|
||||
s.height = radius * 2.0
|
||||
s.radial_segments = 4
|
||||
s.rings = 2
|
||||
s.material = _unshaded()
|
||||
return s
|
||||
|
||||
|
||||
func _ramp(c0: Color, c1: Color) -> Gradient:
|
||||
var g := Gradient.new()
|
||||
g.set_color(0, c0)
|
||||
g.set_color(1, c1)
|
||||
return g
|
||||
|
||||
|
||||
func _curve(points: Array) -> Curve:
|
||||
var c := Curve.new()
|
||||
for pt: Vector2 in points:
|
||||
c.add_point(pt)
|
||||
return c
|
||||
|
||||
|
||||
func _save(p: CPUParticles3D, path: String) -> void:
|
||||
p.set_script(load(_EFFECT_SCRIPT))
|
||||
var packed := PackedScene.new()
|
||||
packed.pack(p)
|
||||
ResourceSaver.save(packed, path)
|
||||
p.free()
|
||||
|
||||
|
||||
# ── the placeholders (defaults ported from the old code-built particles) ─────
|
||||
func _seed_blood_burst() -> void:
|
||||
var p := CPUParticles3D.new()
|
||||
p.name = "blood_burst"
|
||||
p.mesh = _sphere(0.07)
|
||||
p.color_ramp = _ramp(Color(1.0, 0.02, 0.02, 1.0), Color(0.25, 0.0, 0.0, 0.0))
|
||||
p.scale_amount_curve = _curve([Vector2(0.0, 1.0), Vector2(0.6, 0.5), Vector2(1.0, 0.0)])
|
||||
p.emitting = false
|
||||
p.one_shot = true
|
||||
p.explosiveness = 0.9
|
||||
p.amount = 25
|
||||
p.lifetime = 1.1
|
||||
p.randomness = 0.4
|
||||
p.local_coords = false
|
||||
p.direction = Vector3(0.0, 1.0, 0.0)
|
||||
p.spread = 80.0
|
||||
p.gravity = Vector3(0.0, -7.0, 0.0)
|
||||
p.initial_velocity_min = 4.0
|
||||
p.initial_velocity_max = 11.0
|
||||
p.scale_amount_min = 0.5
|
||||
p.scale_amount_max = 1.6
|
||||
_save(p, "res://systems/fx/blood_burst.tscn")
|
||||
|
||||
|
||||
func _seed_slam_dust() -> void:
|
||||
var p := CPUParticles3D.new()
|
||||
p.name = "slam_dust"
|
||||
p.mesh = _sphere(0.16)
|
||||
p.color_ramp = _ramp(Color(0.72, 0.60, 0.40, 0.9), Color(0.55, 0.45, 0.30, 0.0))
|
||||
p.scale_amount_curve = _curve([Vector2(0.0, 0.4), Vector2(0.35, 1.0), Vector2(1.0, 0.0)])
|
||||
p.emitting = false
|
||||
p.one_shot = true
|
||||
p.explosiveness = 1.0
|
||||
p.amount = 36
|
||||
p.lifetime = 0.9
|
||||
p.randomness = 0.5
|
||||
p.local_coords = false
|
||||
p.emission_shape = CPUParticles3D.EMISSION_SHAPE_SPHERE
|
||||
p.emission_sphere_radius = 0.5
|
||||
p.direction = Vector3(0.0, 1.0, 0.0)
|
||||
p.spread = 75.0
|
||||
p.gravity = Vector3(0.0, -6.0, 0.0)
|
||||
p.initial_velocity_min = 2.5
|
||||
p.initial_velocity_max = 6.5
|
||||
p.scale_amount_min = 0.6
|
||||
p.scale_amount_max = 1.4
|
||||
_save(p, "res://systems/fx/slam_dust.tscn")
|
||||
|
||||
|
||||
func _seed_spark() -> void:
|
||||
var p := CPUParticles3D.new()
|
||||
p.name = "spark"
|
||||
p.mesh = _sphere(0.03)
|
||||
p.color_ramp = _ramp(Color(1.0, 0.95, 0.6, 1.0), Color(1.0, 0.55, 0.1, 0.0))
|
||||
p.scale_amount_curve = _curve([Vector2(0.0, 1.0), Vector2(1.0, 0.0)])
|
||||
p.emitting = false
|
||||
p.one_shot = true
|
||||
p.explosiveness = 1.0
|
||||
p.amount = 14
|
||||
p.lifetime = 0.35
|
||||
p.randomness = 0.6
|
||||
p.local_coords = false
|
||||
p.direction = Vector3(0.0, 1.0, 0.0)
|
||||
p.spread = 45.0
|
||||
p.gravity = Vector3(0.0, -9.0, 0.0)
|
||||
p.initial_velocity_min = 3.0
|
||||
p.initial_velocity_max = 7.0
|
||||
p.scale_amount_min = 0.4
|
||||
p.scale_amount_max = 1.0
|
||||
_save(p, "res://systems/fx/spark.tscn")
|
||||
|
||||
|
||||
func _seed_hit_flash() -> void:
|
||||
var p := CPUParticles3D.new()
|
||||
p.name = "hit_flash"
|
||||
p.mesh = _sphere(0.05)
|
||||
p.color_ramp = _ramp(Color(1.0, 1.0, 1.0, 1.0), Color(1.0, 1.0, 1.0, 0.0))
|
||||
p.scale_amount_curve = _curve([Vector2(0.0, 0.2), Vector2(0.3, 1.2), Vector2(1.0, 0.0)])
|
||||
p.emitting = false
|
||||
p.one_shot = true
|
||||
p.explosiveness = 1.0
|
||||
p.amount = 6
|
||||
p.lifetime = 0.18
|
||||
p.randomness = 0.2
|
||||
p.local_coords = false
|
||||
p.spread = 180.0
|
||||
p.gravity = Vector3.ZERO
|
||||
p.initial_velocity_min = 0.5
|
||||
p.initial_velocity_max = 1.5
|
||||
p.scale_amount_min = 0.8
|
||||
p.scale_amount_max = 1.4
|
||||
_save(p, "res://systems/fx/hit_flash.tscn")
|
||||
|
||||
|
||||
func _seed_gallery() -> void:
|
||||
var root_node := Node3D.new()
|
||||
root_node.name = "EffectGallery"
|
||||
|
||||
var cam := Camera3D.new()
|
||||
cam.name = "Camera3D"
|
||||
cam.transform = Transform3D(Basis.from_euler(Vector3(deg_to_rad(-18.0), 0.0, 0.0)), Vector3(0.0, 2.0, 4.5))
|
||||
root_node.add_child(cam)
|
||||
cam.owner = root_node
|
||||
|
||||
var light := DirectionalLight3D.new()
|
||||
light.name = "Sun"
|
||||
light.rotation_degrees = Vector3(-50.0, -30.0, 0.0)
|
||||
root_node.add_child(light)
|
||||
light.owner = root_node
|
||||
|
||||
var ground := MeshInstance3D.new()
|
||||
ground.name = "Ground"
|
||||
var plane := PlaneMesh.new()
|
||||
plane.size = Vector2(12.0, 12.0)
|
||||
var gmat := StandardMaterial3D.new()
|
||||
gmat.albedo_color = Color(0.62, 0.53, 0.36)
|
||||
plane.material = gmat
|
||||
ground.mesh = plane
|
||||
root_node.add_child(ground)
|
||||
ground.owner = root_node
|
||||
|
||||
var ui := CanvasLayer.new()
|
||||
ui.name = "UI"
|
||||
root_node.add_child(ui)
|
||||
ui.owner = root_node
|
||||
var label := Label.new()
|
||||
label.name = "Label"
|
||||
label.position = Vector2(24.0, 20.0)
|
||||
label.text = "FX gallery"
|
||||
label.add_theme_font_size_override("font_size", 28)
|
||||
ui.add_child(label)
|
||||
label.owner = root_node
|
||||
|
||||
root_node.set_script(load(_GALLERY_SCRIPT))
|
||||
var packed := PackedScene.new()
|
||||
packed.pack(root_node)
|
||||
ResourceSaver.save(packed, "res://systems/fx/effect_gallery.tscn")
|
||||
root_node.free()
|
||||
@@ -0,0 +1 @@
|
||||
uid://csbp6mkg257uy
|
||||
Reference in New Issue
Block a user