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:
Floppyrj45
2026-04-19 18:09:21 +02:00
parent cafdb7d27e
commit a8341355e3
12 changed files with 318 additions and 58 deletions

View File

@@ -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: