Musica, vecchietti con sprite, nuove scene del paese e generatore degli sfondi

Lavoro della sessione precedente: colonna sonora (titolo, paese, ricordi) con muto e volume,
vecchietta e vecchietto sulla panchina, scene di Pieve, oratorio, Via de' Medici, Villa Adami,
salita, campeggio, Bosco ai Frati e viale dei cipressi, panorama dell'introduzione,
tools/genera.py per gli sfondi dalle foto.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UdeKd62opy9PKgpQXtVctF
This commit is contained in:
2026-10-23 12:59:57 +02:00
co-authored by Claude Opus 5.5
parent 5eeb634ceb
commit 33e2598718
45 changed files with 1556 additions and 78 deletions
+68
View File
@@ -0,0 +1,68 @@
// Colonna sonora: un brano alla volta, in loop, con dissolvenza quando cambia.
// Quale brano suona lo decidono i dati (story.music e scene.music). Si prova a partire subito; se il browser non lo
// permette prima di un gesto del giocatore, il brano riparte al primo clic o tasto (unlock).
// Il brano del momento: titolo, fine, oppure quello della scena (o quello comune del gioco).
export function trackFor(story, { screen, scene }) {
const m = story.music ?? {};
if (screen === 'title' || screen === 'intro') return m.title ?? null;
if (screen === 'ending') return m.ending ?? m.game ?? null;
return story.scenes[scene]?.music ?? m.game ?? null;
}
const FADE = 1.2;
export class Music {
constructor({ muted = false, volume = 0.6 } = {}) {
this.muted = muted;
this.volume = volume;
this.blocked = null;
this.want = null;
this.current = null; // { src, audio }
this.old = [];
}
// Chiamata a ogni fotogramma con il brano voluto: avvia, cambia e sfuma.
update(src, dt) {
this.want = src;
if (src !== this.current?.src) {
if (this.current) this.old.push(this.current.audio);
this.current = src ? { src, audio: this.start(src) } : null;
}
// Il brano in corso sale (o scende) verso il volume scelto; quelli vecchi sfumano fino a zero.
const step = dt / FADE, target = this.muted ? 0 : this.volume;
if (this.current) {
const a = this.current.audio;
a.volume = a.volume < target ? Math.min(target, a.volume + step) : Math.max(target, a.volume - step);
}
for (const a of this.old) a.volume = Math.max(0, a.volume - step);
this.old = this.old.filter(a => a.volume > 0 || (a.pause(), false));
}
start(src) {
const audio = new Audio(src);
audio.loop = true;
audio.volume = 0;
audio.play().catch(() => { this.blocked = audio; }); // senza permesso si riprova al primo gesto
return audio;
}
unlock() {
const a = this.blocked;
this.blocked = null;
if (a && a === this.current?.audio) a.play().catch(() => {});
}
toggle() {
this.muted = !this.muted;
return this.muted;
}
setVolume(v) {
this.volume = Math.max(0, Math.min(1, v));
if (this.volume > 0) this.muted = false;
return this.volume;
}
settings() { return { volume: this.volume, muted: this.muted }; }
}