Renderer, Oberfläche, Eingabe, Sound-Modul, Simulationstest und Docker-Setup (Port 18093)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-02 22:16:41 +01:00
1 parent 14e481cffb
commit db417b3af8
13 files changed
+1930 -19

No files matched your search

+98
View File
@@ -0,0 +1,98 @@
// Sound-Wiedergabe. Die Spiellogik meldet Ereignisse (Sfx), hier werden sie abgespielt.
// Klänge werden später generiert und per `SoundBank::set` (WAV-Bytes) registriert.
// Ohne registrierten Klang bleibt ein Ereignis stumm.
use crate::game::{Sfx, SfxEvent, SFX_ALL};
use crate::render::View;
use macroquad::audio::{load_sound_from_bytes, play_sound, PlaySoundParams, Sound};
use macroquad::prelude::*;
pub struct SoundBank {
sounds: Vec<Option<Sound>>,
last: Vec<f64>,
pub volume: f32,
pub muted: bool,
}
impl SoundBank {
pub fn new() -> SoundBank {
SoundBank { sounds: vec![None; SFX_ALL.len()], last: vec![-1.0; SFX_ALL.len()], volume: 0.7, muted: false }
}
// Klang für ein Ereignis registrieren (WAV- oder OGG-Bytes)
#[allow(dead_code)]
pub async fn set(&mut self, sfx: Sfx, bytes: &[u8]) -> bool {
match load_sound_from_bytes(bytes).await {
Ok(s) => {
self.sounds[sfx.index()] = Some(s);
true
}
Err(_) => false,
}
}
// Hier werden später die prozedural erzeugten Klänge registriert.
pub async fn generate_all(&mut self) {}
pub fn change_volume(&mut self, d: f32) {
self.volume = (self.volume + d).clamp(0.0, 1.0);
}
// Mindestabstand zwischen zwei gleichen Klängen (gegen Klangbrei)
fn min_gap(sfx: Sfx) -> f64 {
match sfx {
Sfx::Dig | Sfx::GoldDig | Sfx::MeleeHit | Sfx::Shoot | Sfx::GateHit | Sfx::CoreHit => 0.08,
Sfx::Claim | Sfx::Deposit | Sfx::DigDone => 0.12,
_ => 0.03,
}
}
pub fn play(&mut self, ev: SfxEvent, view: &View, now: f64) {
if self.muted || self.volume <= 0.0 {
return;
}
let i = ev.sfx.index();
let Some(sound) = &self.sounds[i] else { return };
if now - self.last[i] < Self::min_gap(ev.sfx) {
return;
}
// Lautstärke nach Entfernung zur Bildschirmmitte
let mut vol = self.volume;
if let Some((wx, wy)) = ev.pos {
let (cx, cy) = view.world(screen_width() * 0.5, screen_height() * 0.5);
let d = ((wx - cx).powi(2) + (wy - cy).powi(2)).sqrt();
let hear = screen_width().max(screen_height()) / view.zoom * 0.6;
let f = 1.0 - ((d - hear * 0.5) / hear).clamp(0.0, 1.0);
if f <= 0.05 {
return;
}
vol *= f;
}
self.last[i] = now;
play_sound(sound, PlaySoundParams { looped: false, volume: vol });
}
}
// Hilfsfunktion für die spätere Generierung: Samples (-1..1, mono) -> WAV (16 bit PCM)
#[allow(dead_code)]
pub fn wav_from_samples(samples: &[f32], rate: u32) -> Vec<u8> {
let data_len = (samples.len() * 2) as u32;
let mut out = Vec::with_capacity(44 + data_len as usize);
out.extend_from_slice(b"RIFF");
out.extend_from_slice(&(36 + data_len).to_le_bytes());
out.extend_from_slice(b"WAVEfmt ");
out.extend_from_slice(&16u32.to_le_bytes());
out.extend_from_slice(&1u16.to_le_bytes()); // PCM
out.extend_from_slice(&1u16.to_le_bytes()); // mono
out.extend_from_slice(&rate.to_le_bytes());
out.extend_from_slice(&(rate * 2).to_le_bytes());
out.extend_from_slice(&2u16.to_le_bytes());
out.extend_from_slice(&16u16.to_le_bytes());
out.extend_from_slice(b"data");
out.extend_from_slice(&data_len.to_le_bytes());
for &s in samples {
let v = (s.clamp(-1.0, 1.0) * 32767.0) as i16;
out.extend_from_slice(&v.to_le_bytes());
}
out
}