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