250 lines
9.2 KiB
GDScript
250 lines
9.2 KiB
GDScript
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")
|