extends Node3D ## Disco-Laser-System mit Audio- und Strobe-Reaktivität ## Version mit AudioManager Singleton (OPTIMIERT) @export_group("Laser Configuration") @export_range(1, 20) var laser_count: int = 4 @export_range(1.0, 50.0) var placement_radius: float = 10.0 @export_range(0.0, 50.0) var laser_height: float = 8.0 @export_range(1.0, 100.0) var beam_length: float = 20.0 @export_range(0.01, 1.0) var beam_thickness: float = 0.01 @export_group("Light Settings") @export_range(0.1, 100.0) var light_energy: float = 10.0 @export_range(1.0, 100.0) var light_range: float = 25.0 @export_range(3.5, 90.0) var spot_angle: float = 5.0 @export var laser_colors: Array[Color] = [ Color.RED, Color.BLUE, Color.GREEN, Color.MAGENTA, Color.CYAN, Color.YELLOW ] @export_group("Laser Direction") @export_range(-90.0, 90.0) var laser_pitch: float = -30.0 @export_range(-180.0, 180.0) var laser_yaw_offset: float = 0.0 @export var audio_affects_direction: bool = false @export_range(-90.0, 90.0) var min_audio_pitch: float = -45.0 @export_range(-90.0, 90.0) var max_audio_pitch: float = 0.0 @export_group("Animation") @export var rotate_lasers: 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 = 20.0 @export_group("Beam Visual") @export_range(0.0, 10.0) var beam_emission_strength: float = 3.0 @export_range(0.0, 1.0) var beam_transparency: float = 0.3 @export_group("Audio Reactivity") @export var audio_player: AudioStreamPlayer3D @export var audio_reactive: bool = false @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 = 30.0 @export var audio_affects_rotation: bool = false @export_range(-3.0, 10.0) var min_rotation_speed: float = 0.5 @export_range(-8.0, 10.0) var max_rotation_speed: float = 3.0 @export var audio_affects_emission: bool = true @export_range(0.0, 20.0) var min_beam_emission: float = 1.0 @export_range(0.0, 20.0) var max_beam_emission: float = 8.0 @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(0.1, 30.0) var strobe_speed: float = 12.0 @export_range(0.0, 100.0) var strobe_energy: float = 25.0 @export_range(0.0, 1.0) var strobe_threshold: float = 0.5 # Interne Variablen var laser_nodes: Array[Node3D] = [] var initial_rotations: Array[float] = [] var base_laser_rotations: Array[Vector3] = [] var current_y_rotation: Array[float] = [] # Audio (vereinfacht - nutzt AudioManager!) var current_audio_magnitude: float = 0.0 var target_audio_magnitude: float = 0.0 # Strobe var strobe_timer: float = 0.0 var strobe_phase_offsets: Array[float] = [] func _ready(): randomize() _create_lasers() # Setup Audio mit AudioManager Singleton if audio_reactive and audio_player: if AudioManager.setup(audio_player): print("Laser '%s': Nutzt AudioManager für Audio-Reaktivität" % name) else: push_error("Laser '%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) _animate_lasers(delta) _apply_final_rotations(delta) func _create_lasers(): for i in range(laser_count): var laser = _create_single_laser(i) laser_nodes.append(laser) add_child(laser) var angle = (float(i) / laser_count) * TAU initial_rotations.append(angle) current_y_rotation.append(0.0) _set_laser_base_direction(laser, angle) base_laser_rotations.append(laser.rotation) strobe_phase_offsets.append(randf_range(0.0, TAU)) print("Disco Laser System '%s': %d Laser erstellt" % [name, laser_count]) func _create_single_laser(index: int) -> Node3D: var laser_node = Node3D.new() laser_node.name = "Laser_%d" % index var angle = (float(index) / laser_count) * TAU var x = cos(angle) * placement_radius var z = sin(angle) * placement_radius laser_node.position = Vector3(x, laser_height, z) var color = laser_colors[index % laser_colors.size()] var spot = SpotLight3D.new() spot.name = "SpotLight" spot.light_color = color spot.light_energy = light_energy spot.spot_range = light_range spot.spot_angle = spot_angle spot.shadow_enabled = false laser_node.add_child(spot) var beam = _create_beam_mesh(color) laser_node.add_child(beam) return laser_node func _set_laser_base_direction(laser: Node3D, _circle_angle: float): var target_pos = Vector3(0, laser_height * 0.5, 0) laser.look_at(target_pos, Vector3.UP) var base_y_rotation = laser.rotation.y laser.rotation = Vector3.ZERO laser.rotation.y = base_y_rotation + deg_to_rad(laser_yaw_offset) laser.rotation.x = deg_to_rad(laser_pitch) func _create_beam_mesh(color: Color) -> MeshInstance3D: var mesh_instance = MeshInstance3D.new() mesh_instance.name = "BeamMesh" var cylinder = CylinderMesh.new() cylinder.top_radius = beam_thickness cylinder.bottom_radius = beam_thickness * 0.8 cylinder.height = beam_length mesh_instance.mesh = cylinder mesh_instance.position = Vector3(0, 0, -beam_length * 0.5) mesh_instance.rotation.x = deg_to_rad(90) var material = StandardMaterial3D.new() material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED material.albedo_color = color material.emission_enabled = true material.emission = color material.emission_energy_multiplier = beam_emission_strength material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA material.albedo_color.a = 1.0 - beam_transparency material.blend_mode = BaseMaterial3D.BLEND_MODE_ADD material.cull_mode = BaseMaterial3D.CULL_DISABLED mesh_instance.material_override = material return mesh_instance ## 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 laser in laser_nodes: if audio_affects_intensity: var energy = lerp(min_energy, max_energy, current_audio_magnitude) var spot = laser.get_node("SpotLight") as SpotLight3D if spot: spot.light_energy = energy if audio_affects_emission: var emission = lerp(min_beam_emission, max_beam_emission, current_audio_magnitude) var beam = laser.get_node("BeamMesh") as MeshInstance3D if beam and beam.material_override: var mat = beam.material_override as StandardMaterial3D mat.emission_energy_multiplier = emission func _apply_strobe_effect(delta): strobe_timer += delta * strobe_speed var magnitude_normalized = current_audio_magnitude for i in range(laser_nodes.size()): var laser = laser_nodes[i] if not is_instance_valid(laser): continue var phase = strobe_timer + strobe_phase_offsets[i] var light_on = sin(phase) > 0.0 and magnitude_normalized > strobe_threshold var spot = laser.get_node_or_null("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 var beam = laser.get_node_or_null("BeamMesh") as MeshInstance3D if beam: if light_on: beam.visible = true if beam.material_override and beam.material_override is StandardMaterial3D: var mat = beam.material_override as StandardMaterial3D mat.emission_energy_multiplier = max_beam_emission else: beam.visible = false func _animate_lasers(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_lasers or (audio_reactive and audio_affects_rotation): for i in range(laser_nodes.size()): current_y_rotation[i] += current_rotation_speed * delta func _apply_final_rotations(_delta): for i in range(laser_nodes.size()): var laser = laser_nodes[i] var base_rot = base_laser_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 if audio_reactive and audio_affects_direction: var audio_pitch = lerp(min_audio_pitch, max_audio_pitch, current_audio_magnitude) pitch_offset += audio_pitch - laser_pitch final_rotation.x = base_rot.x + deg_to_rad(pitch_offset) laser.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")