feat(particles): bubble trail behind dolphin + block break burst
- BubbleTrail.gd: GPUParticles3D, auto-configured in _ready, set_intensity() API - BlockBreakParticles.gd: one_shot burst, emit_burst(pos, color) API - DolphinController.gd: bubble_trail onready + speed_factor hook in _update_movement - Dolphin.tscn: BubbleEmitterPoint (0,0,1.2) > BubbleTrail child added Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
38
scripts/dolphin/BlockBreakParticles.gd
Normal file
38
scripts/dolphin/BlockBreakParticles.gd
Normal file
@@ -0,0 +1,38 @@
|
||||
extends GPUParticles3D
|
||||
|
||||
func _ready() -> void:
|
||||
amount = 30
|
||||
lifetime = 0.8
|
||||
one_shot = true
|
||||
emitting = false
|
||||
local_coords = false
|
||||
|
||||
var mat := ParticleProcessMaterial.new()
|
||||
mat.direction = Vector3(0.0, 1.0, 0.0)
|
||||
mat.initial_velocity_min = 1.0
|
||||
mat.initial_velocity_max = 3.0
|
||||
mat.spread = 60.0
|
||||
mat.gravity = Vector3(0.0, -2.0, 0.0)
|
||||
mat.scale_min = 0.05
|
||||
mat.scale_max = 0.15
|
||||
process_material = mat
|
||||
|
||||
var box := BoxMesh.new()
|
||||
box.size = Vector3(0.08, 0.08, 0.08)
|
||||
|
||||
var burst_mat := StandardMaterial3D.new()
|
||||
burst_mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
burst_mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
burst_mat.albedo_color = Color(0.8, 0.7, 0.5, 0.9)
|
||||
box.surface_set_material(0, burst_mat)
|
||||
|
||||
draw_pass_1 = box
|
||||
|
||||
|
||||
func emit_burst(pos: Vector3, color: Color) -> void:
|
||||
global_position = pos
|
||||
if draw_pass_1 and draw_pass_1.surface_get_material(0):
|
||||
var mat := draw_pass_1.surface_get_material(0) as StandardMaterial3D
|
||||
if mat:
|
||||
mat.albedo_color = Color(color.r, color.g, color.b, 0.9)
|
||||
emitting = true
|
||||
52
scripts/dolphin/BubbleTrail.gd
Normal file
52
scripts/dolphin/BubbleTrail.gd
Normal file
@@ -0,0 +1,52 @@
|
||||
extends GPUParticles3D
|
||||
|
||||
func _ready() -> void:
|
||||
amount = 80
|
||||
lifetime = 1.5
|
||||
emitting = false
|
||||
local_coords = false
|
||||
|
||||
var mat := ParticleProcessMaterial.new()
|
||||
mat.direction = Vector3(0.0, 0.3, 0.0)
|
||||
mat.initial_velocity_min = 0.5
|
||||
mat.initial_velocity_max = 1.5
|
||||
mat.spread = 15.0
|
||||
mat.gravity = Vector3(0.0, 0.5, 0.0)
|
||||
mat.scale_min = 0.03
|
||||
mat.scale_max = 0.12
|
||||
|
||||
var scale_curve := Curve.new()
|
||||
scale_curve.add_point(Vector2(0.0, 0.2))
|
||||
scale_curve.add_point(Vector2(0.5, 1.0))
|
||||
scale_curve.add_point(Vector2(1.0, 0.0))
|
||||
var scale_curve_tex := CurveTexture.new()
|
||||
scale_curve_tex.curve = scale_curve
|
||||
mat.scale_curve = scale_curve_tex
|
||||
|
||||
mat.color = Color(1.0, 1.0, 1.0, 0.6)
|
||||
process_material = mat
|
||||
|
||||
var sphere := SphereMesh.new()
|
||||
sphere.radius = 0.05
|
||||
sphere.height = 0.1
|
||||
sphere.rings = 4
|
||||
sphere.radial_segments = 6
|
||||
|
||||
var bubble_mat := StandardMaterial3D.new()
|
||||
bubble_mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
bubble_mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
bubble_mat.albedo_color = Color(1.0, 1.0, 1.0, 0.55)
|
||||
bubble_mat.emission_enabled = true
|
||||
bubble_mat.emission = Color(0.8, 0.9, 1.0, 1.0)
|
||||
bubble_mat.emission_energy_multiplier = 0.3
|
||||
sphere.surface_set_material(0, bubble_mat)
|
||||
|
||||
draw_pass_1 = sphere
|
||||
|
||||
|
||||
func set_intensity(speed_factor: float) -> void:
|
||||
if speed_factor > 0.5:
|
||||
emitting = true
|
||||
amount_ratio = clamp(speed_factor / 1.5, 0.3, 1.0)
|
||||
else:
|
||||
emitting = false
|
||||
@@ -36,6 +36,9 @@ var _is_dead: bool = false
|
||||
@onready var _camera: Camera3D = $CameraPivot/SpringArm/Camera
|
||||
@onready var _dolphin_body: Node3D = $DolphinBody
|
||||
@onready var _echo_pulse: Node3D = $EcholocationPulse
|
||||
@onready var bubble_trail: Node = $BubbleEmitterPoint/BubbleTrail
|
||||
|
||||
var _turn_input: float = 0.0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -120,10 +123,7 @@ func _update_movement(delta: float) -> void:
|
||||
is_boosting = Input.is_action_pressed("boost")
|
||||
var speed: float = swim_speed * (boost_multiplier if is_boosting else 1.0)
|
||||
|
||||
var forward: Vector3 = -global_transform.basis.z
|
||||
var right: Vector3 = global_transform.basis.x
|
||||
|
||||
# Recompute forward/right using camera pitch for intuitive swim direction
|
||||
# forward/right using camera pitch for intuitive swim direction
|
||||
var cam_basis: Basis = _camera_pivot.global_transform.basis
|
||||
var swim_forward: Vector3 = -cam_basis.z
|
||||
var swim_right: Vector3 = cam_basis.x
|
||||
@@ -150,6 +150,13 @@ func _update_movement(delta: float) -> void:
|
||||
|
||||
move_and_slide()
|
||||
|
||||
var current_speed: float = velocity.length()
|
||||
var speed_factor: float = clamp(current_speed / swim_speed, 0.0, 1.5)
|
||||
if is_boosting:
|
||||
speed_factor *= 1.8
|
||||
if bubble_trail:
|
||||
bubble_trail.set_intensity(speed_factor)
|
||||
|
||||
|
||||
func _update_stats(delta: float) -> void:
|
||||
var changed: bool = false
|
||||
@@ -204,14 +211,26 @@ func _respawn() -> void:
|
||||
func _animate_body() -> void:
|
||||
if not is_instance_valid(_dolphin_body):
|
||||
return
|
||||
# Body bob
|
||||
_dolphin_body.position.y = sin(_time * 2.5) * 0.05
|
||||
# Tail wag via last child if present
|
||||
var child_count: int = _dolphin_body.get_child_count()
|
||||
if child_count > 1:
|
||||
var tail_node: Node3D = _dolphin_body.get_child(child_count - 1) as Node3D
|
||||
if tail_node != null:
|
||||
tail_node.rotation.y = sin(_time * 3.0) * 0.3
|
||||
|
||||
# Idle body bob
|
||||
_dolphin_body.position.y = sin(_time * 2.5) * 0.04
|
||||
|
||||
# Speed factor for animation intensity
|
||||
var spd: float = velocity.length()
|
||||
var speed_factor: float = clamp(spd / swim_speed, 0.0, 1.5)
|
||||
|
||||
# Track turn input for body lean
|
||||
var strafe: float = 0.0
|
||||
if Input.is_action_pressed("strafe_right"):
|
||||
strafe += 1.0
|
||||
if Input.is_action_pressed("strafe_left"):
|
||||
strafe -= 1.0
|
||||
_turn_input = lerp(_turn_input, strafe, 0.1)
|
||||
|
||||
# Drive procedural mesh builder if present
|
||||
var builder = _dolphin_body.get_node_or_null("DolphinMesh")
|
||||
if builder != null and builder.has_method("animate"):
|
||||
builder.animate(_time, speed_factor, is_boosting, _turn_input)
|
||||
|
||||
|
||||
func _do_raycast_break() -> void:
|
||||
|
||||
Reference in New Issue
Block a user