268 lines
9.4 KiB
GDScript
268 lines
9.4 KiB
GDScript
extends Node3D
|
||
class_name SpotlightAnimator
|
||
|
||
## Disco-Spotlight-System mit Audio-Reaktivität, Farbwechsel und Strobe
|
||
## Version mit AudioManager Singleton (OPTIMIERT)
|
||
|
||
@export_group("Spotlight Configuration")
|
||
@export_range(1, 20) var spotlight_count: int = 6
|
||
@export_range(1.0, 50.0) var placement_radius: float = 10.0
|
||
@export_range(0.0, 50.0) var spotlight_height: float = 5.0
|
||
@export_range(1.0, 100.0) var light_range: float = 20.0
|
||
@export_range(5.0, 90.0) var spot_angle: float = 25.0
|
||
|
||
@export_group("Color Settings")
|
||
@export var color_palette_1: Array[Color] = [Color.RED, Color.ORANGE, Color.MAGENTA]
|
||
@export var color_palette_2: Array[Color] = [Color.BLUE, Color.CYAN, Color.DEEP_SKY_BLUE]
|
||
@export var use_random_palettes: bool = true
|
||
@export var color_change_speed: float = 2.0
|
||
|
||
@export_group("Light Settings")
|
||
@export_range(0.1, 100.0) var light_energy: float = 15.0
|
||
@export_range(0.0, 10.0) var base_emission_strength: float = 2.0
|
||
|
||
@export_group("Spotlight Direction")
|
||
@export_range(-90.0, 90.0) var spotlight_pitch: float = -45.0
|
||
@export_range(-180.0, 180.0) var spotlight_yaw_offset: float = 0.0
|
||
|
||
@export_group("Animation")
|
||
@export var rotate_spotlights: bool = true
|
||
@export_range(-10.0, 10.0) var rotation_speed: float = 1.0
|
||
@export var vertical_movement: bool = true
|
||
@export_range(0.1, 5.0) var vertical_speed: float = 0.5
|
||
@export_range(0.0, 45.0) var vertical_amplitude: float = 15.0
|
||
|
||
@export_group("Audio Reactivity")
|
||
@export var audio_player: AudioStreamPlayer3D
|
||
@export var audio_reactive: bool = true
|
||
@export_enum("Bass", "Mitten", "Höhen", "Alle") var frequency_range: int = 0
|
||
@export var audio_affects_intensity: bool = true
|
||
@export_range(0.0, 100.0) var min_energy: float = 5.0
|
||
@export_range(0.0, 100.0) var max_energy: float = 40.0
|
||
@export var audio_affects_rotation: bool = true
|
||
@export_range(-10.0, 10.0) var min_rotation_speed: float = 0.5
|
||
@export_range(-10.0, 10.0) var max_rotation_speed: float = 4.0
|
||
@export var audio_affects_color: bool = true
|
||
@export_range(0.1, 5.0) var audio_sensitivity: float = 1.5
|
||
@export_range(0.0, 1.0) var audio_smoothing: float = 0.2
|
||
|
||
@export_group("Strobe Effect")
|
||
@export var strobe_enabled: bool = false
|
||
@export_range(1.0, 30.0) var strobe_speed: float = 12.0
|
||
@export_range(0.0, 100.0) var strobe_energy: float = 30.0
|
||
@export_range(0.0, 1.0) var strobe_threshold: float = 0.3
|
||
@export var async_strobe: bool = true
|
||
|
||
# Interne Variablen
|
||
var spotlight_nodes: Array[Node3D] = []
|
||
var spotlight_colors: Array[Array] = []
|
||
var current_color_indices: Array[int] = []
|
||
var color_transition_timers: Array[float] = []
|
||
var initial_rotations: Array[float] = []
|
||
var base_spotlight_rotations: Array[Vector3] = []
|
||
var current_y_rotation: Array[float] = []
|
||
var strobe_phase_offsets: Array[float] = []
|
||
|
||
# Audio (vereinfacht!)
|
||
var current_audio_magnitude: float = 0.0
|
||
var target_audio_magnitude: float = 0.0
|
||
var strobe_timer: float = 0.0
|
||
|
||
func _ready():
|
||
randomize()
|
||
_create_spotlights()
|
||
|
||
# Setup Audio mit AudioManager Singleton
|
||
if audio_reactive and audio_player:
|
||
if AudioManager.setup(audio_player):
|
||
print("Spotlight '%s': Nutzt AudioManager für Audio-Reaktivität" % name)
|
||
else:
|
||
push_error("Spotlight '%s': AudioManager-Setup fehlgeschlagen!" % name)
|
||
audio_reactive = false
|
||
|
||
func _process(delta):
|
||
# Hole Audio-Daten vom Manager (bereits berechnet!)
|
||
if audio_reactive and AudioManager.is_playing():
|
||
_update_audio_reactivity(delta)
|
||
|
||
if audio_affects_color:
|
||
_update_color_transitions(delta)
|
||
|
||
_animate_spotlights(delta)
|
||
_apply_final_rotations(delta)
|
||
|
||
func _create_spotlights():
|
||
var available_palettes = [color_palette_1, color_palette_2]
|
||
|
||
for i in range(spotlight_count):
|
||
var spotlight = _create_single_spotlight(i, available_palettes)
|
||
spotlight_nodes.append(spotlight)
|
||
add_child(spotlight)
|
||
|
||
var angle = (float(i) / spotlight_count) * TAU
|
||
initial_rotations.append(angle)
|
||
current_y_rotation.append(0.0)
|
||
current_color_indices.append(0)
|
||
color_transition_timers.append(randf_range(0.0, color_change_speed))
|
||
strobe_phase_offsets.append(randf_range(0.0, TAU) if async_strobe else 0.0)
|
||
|
||
_set_spotlight_base_direction(spotlight, angle)
|
||
base_spotlight_rotations.append(spotlight.rotation)
|
||
|
||
print("Spotlight Animator '%s': %d Spotlights erstellt" % [name, spotlight_count])
|
||
|
||
func _create_single_spotlight(index: int, palettes: Array) -> Node3D:
|
||
var spotlight_node = Node3D.new()
|
||
spotlight_node.name = "Spotlight_%d" % index
|
||
|
||
# Position im Kreis
|
||
var angle = (float(index) / spotlight_count) * TAU
|
||
var x = cos(angle) * placement_radius
|
||
var z = sin(angle) * placement_radius
|
||
spotlight_node.position = Vector3(x, spotlight_height, z)
|
||
|
||
# Farbpalette zuweisen
|
||
var palette: Array[Color] = []
|
||
if use_random_palettes:
|
||
palette = palettes[randi() % palettes.size()].duplicate()
|
||
else:
|
||
palette = palettes[index % palettes.size()].duplicate()
|
||
spotlight_colors.append(palette)
|
||
|
||
# SpotLight3D erstellen
|
||
var spot = SpotLight3D.new()
|
||
spot.name = "SpotLight"
|
||
spot.light_color = palette[0]
|
||
spot.light_energy = light_energy
|
||
spot.spot_range = light_range
|
||
spot.spot_angle = spot_angle
|
||
spot.shadow_enabled = true
|
||
spotlight_node.add_child(spot)
|
||
|
||
return spotlight_node
|
||
|
||
func _create_emission_texture(color: Color) -> GradientTexture2D:
|
||
var gradient = Gradient.new()
|
||
gradient.set_color(0, color)
|
||
gradient.set_color(1, Color(color.r, color.g, color.b, 0.0))
|
||
|
||
var gradient_texture = GradientTexture2D.new()
|
||
gradient_texture.gradient = gradient
|
||
gradient_texture.fill = GradientTexture2D.FILL_RADIAL
|
||
gradient_texture.width = 256
|
||
gradient_texture.height = 256
|
||
|
||
return gradient_texture
|
||
|
||
func _set_spotlight_base_direction(spotlight: Node3D, _circle_angle: float):
|
||
var target_pos = Vector3(0, 0, 0)
|
||
spotlight.look_at(target_pos, Vector3.UP)
|
||
var base_y_rotation = spotlight.rotation.y
|
||
spotlight.rotation = Vector3.ZERO
|
||
spotlight.rotation.y = base_y_rotation + deg_to_rad(spotlight_yaw_offset)
|
||
spotlight.rotation.x = deg_to_rad(spotlight_pitch)
|
||
|
||
## OPTIMIERT: Nutzt Rohdaten vom AudioManager, verarbeitet aber lokal
|
||
func _update_audio_reactivity(delta):
|
||
# Hole RAW Magnitude vom Manager (ohne Sensitivity/Smoothing)
|
||
var raw_magnitude = AudioManager.get_raw_magnitude(frequency_range)
|
||
|
||
# Lokale Verarbeitung mit eigenen Parametern für individuelles Verhalten
|
||
target_audio_magnitude = clamp(raw_magnitude * audio_sensitivity, 0.0, 1.0)
|
||
current_audio_magnitude = lerp(current_audio_magnitude, target_audio_magnitude, 1.0 - audio_smoothing)
|
||
|
||
if strobe_enabled:
|
||
_apply_strobe_effect(delta)
|
||
else:
|
||
_apply_audio_visual_effects()
|
||
|
||
func _apply_audio_visual_effects():
|
||
for spotlight in spotlight_nodes:
|
||
var spot = spotlight.get_node("SpotLight") as SpotLight3D
|
||
if spot and audio_affects_intensity:
|
||
var energy = lerp(min_energy, max_energy, current_audio_magnitude)
|
||
spot.light_energy = energy
|
||
|
||
func _apply_strobe_effect(delta):
|
||
strobe_timer += delta * strobe_speed
|
||
var magnitude_normalized = current_audio_magnitude
|
||
|
||
for i in range(spotlight_nodes.size()):
|
||
var spotlight = spotlight_nodes[i]
|
||
if not is_instance_valid(spotlight):
|
||
continue
|
||
|
||
var phase = strobe_timer + strobe_phase_offsets[i]
|
||
var light_on = sin(phase) > 0.0 and magnitude_normalized > strobe_threshold
|
||
|
||
var spot = spotlight.get_node("SpotLight") as SpotLight3D
|
||
if spot:
|
||
if light_on:
|
||
spot.light_energy = strobe_energy
|
||
spot.visible = true
|
||
else:
|
||
spot.light_energy = 0.0
|
||
spot.visible = false
|
||
|
||
func _update_color_transitions(delta):
|
||
for i in range(spotlight_nodes.size()):
|
||
color_transition_timers[i] -= delta
|
||
|
||
if color_transition_timers[i] <= 0.0:
|
||
# Farbwechsel triggern (verstärkt bei starkem Beat)
|
||
var change_speed = color_change_speed
|
||
if audio_reactive:
|
||
change_speed = lerp(color_change_speed * 2.0, color_change_speed * 0.5, current_audio_magnitude)
|
||
|
||
color_transition_timers[i] = change_speed
|
||
|
||
# Nächste Farbe
|
||
current_color_indices[i] = (current_color_indices[i] + 1) % spotlight_colors[i].size()
|
||
var new_color = spotlight_colors[i][current_color_indices[i]]
|
||
|
||
# Farbe auf Spotlight anwenden
|
||
var spotlight = spotlight_nodes[i]
|
||
var spot = spotlight.get_node("SpotLight") as SpotLight3D
|
||
if spot:
|
||
spot.light_color = new_color
|
||
|
||
func _animate_spotlights(delta):
|
||
var current_rotation_speed = rotation_speed
|
||
|
||
if audio_reactive and audio_affects_rotation:
|
||
current_rotation_speed = lerp(min_rotation_speed, max_rotation_speed, current_audio_magnitude)
|
||
|
||
if rotate_spotlights or (audio_reactive and audio_affects_rotation):
|
||
for i in range(spotlight_nodes.size()):
|
||
current_y_rotation[i] += current_rotation_speed * delta
|
||
|
||
func _apply_final_rotations(_delta):
|
||
for i in range(spotlight_nodes.size()):
|
||
var spotlight = spotlight_nodes[i]
|
||
var base_rot = base_spotlight_rotations[i]
|
||
var final_rotation = base_rot
|
||
|
||
final_rotation.y = base_rot.y + current_y_rotation[i]
|
||
|
||
var pitch_offset = 0.0
|
||
if vertical_movement:
|
||
var time_offset = initial_rotations[i]
|
||
pitch_offset += sin(Time.get_ticks_msec() * 0.001 * vertical_speed + time_offset) * vertical_amplitude
|
||
|
||
final_rotation.x = base_rot.x + deg_to_rad(pitch_offset)
|
||
spotlight.rotation = final_rotation
|
||
|
||
# === Public Methods ===
|
||
|
||
func enable_strobe(enable: bool):
|
||
strobe_enabled = enable
|
||
if enable:
|
||
audio_affects_intensity = false
|
||
print("Stroboskop aktiviert – Audio-Intensity deaktiviert")
|
||
else:
|
||
print("Stroboskop deaktiviert – Audio-Intensity kann wieder genutzt werden")
|
||
|
||
func set_color_palette(spotlight_index: int, new_palette: Array[Color]):
|
||
if spotlight_index >= 0 and spotlight_index < spotlight_colors.size():
|
||
spotlight_colors[spotlight_index] = new_palette
|