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:
+43
-2
@@ -3,7 +3,8 @@ import { story } from './story/story.js';
|
||||
import { Game, VERBS, VERB_LABELS } from './engine/game.js';
|
||||
import { Command } from './engine/command.js';
|
||||
import { render, loadAssets, WIDTH, HEIGHT } from './engine/render.js';
|
||||
import { readSave, writeSave, clearSave } from './engine/storage.js';
|
||||
import { readSave, writeSave, clearSave, readAudio, writeAudio } from './engine/storage.js';
|
||||
import { Music, trackFor } from './engine/music.js';
|
||||
|
||||
const $ = id => document.getElementById(id);
|
||||
const canvas = $('scene'), ctx = canvas.getContext('2d');
|
||||
@@ -13,6 +14,20 @@ const debug = new URLSearchParams(location.search).has('debug');
|
||||
|
||||
let game = null, command = null, hoverTarget = null, mouse = null, running = false;
|
||||
|
||||
// --- Musica: parte subito se il browser lo permette, altrimenti al primo gesto; volume e muto si ricordano.
|
||||
const music = new Music((storage && readAudio(storage)) ?? {});
|
||||
const saveAudio = () => storage && writeAudio(storage, music.settings());
|
||||
function showSound() {
|
||||
$('mute').setAttribute('aria-pressed', String(music.muted));
|
||||
$('mute').setAttribute('aria-label', music.muted ? "Musica: rimetti l'audio" : "Musica: togli l'audio");
|
||||
$('volume').value = Math.round(music.volume * 100);
|
||||
}
|
||||
for (const ev of ['pointerdown', 'keydown']) addEventListener(ev, () => music.unlock(), { capture: true });
|
||||
$('mute').addEventListener('click', () => { music.toggle(); saveAudio(); showSound(); });
|
||||
$('volume').addEventListener('input', e => { music.setVolume(e.target.value / 100); saveAudio(); showSound(); });
|
||||
showSound();
|
||||
window.music = music; // per i test nel browser
|
||||
|
||||
function start(state) {
|
||||
game = new Game(story, { state, onSave: data => storage && writeSave(storage, data) });
|
||||
command = new Command(game);
|
||||
@@ -23,6 +38,27 @@ function start(state) {
|
||||
window.game = game; // per i test nel browser e per curiosare dalla console
|
||||
}
|
||||
|
||||
// --- Introduzione: le righe di story.intro una alla volta sopra il panorama; poi comincia la partita.
|
||||
let introLine = -1;
|
||||
function showIntro() {
|
||||
if (!story.intro?.length) return start(null);
|
||||
$('title').hidden = true;
|
||||
$('intro').hidden = false;
|
||||
introLine = -1;
|
||||
nextIntro();
|
||||
}
|
||||
function nextIntro() {
|
||||
introLine++;
|
||||
if (introLine >= story.intro.length) { $('intro').hidden = true; return start(null); }
|
||||
const p = $('intro-text');
|
||||
p.textContent = story.intro[introLine];
|
||||
p.style.animation = 'none';
|
||||
void p.offsetWidth; // riavvia la comparsa
|
||||
p.style.animation = '';
|
||||
}
|
||||
$('intro').addEventListener('click', e => { if (e.target !== $('skip-intro')) nextIntro(); });
|
||||
$('skip-intro').addEventListener('click', () => { introLine = story.intro.length; nextIntro(); });
|
||||
|
||||
function showTitle() {
|
||||
running = false;
|
||||
const saved = storage && readSave(storage);
|
||||
@@ -113,6 +149,9 @@ function updateChoices() {
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', e => {
|
||||
if (e.target === $('volume')) return;
|
||||
if (!$('intro').hidden && [' ', 'Enter'].includes(e.key)) { e.preventDefault(); nextIntro(); return; }
|
||||
if (e.key === 'm' || e.key === 'M') { music.toggle(); saveAudio(); showSound(); return; }
|
||||
if (!running) return;
|
||||
if ([' ', '.', 'Enter'].includes(e.key) && game.speech) { e.preventDefault(); game.skip(); return; }
|
||||
if (e.key === 'Escape') command.reset();
|
||||
@@ -150,6 +189,8 @@ let last = performance.now();
|
||||
function frame(now) {
|
||||
const dt = Math.min(0.1, (now - last) / 1000);
|
||||
last = now;
|
||||
const screen = !$('title').hidden ? 'title' : !$('intro').hidden ? 'intro' : !$('ending').hidden ? 'ending' : 'game';
|
||||
music.update(trackFor(story, { screen, scene: game?.state.scene }), dt);
|
||||
if (game) {
|
||||
if (running) game.update(dt);
|
||||
render(ctx, game, { hover: debug && hoverTarget ? game.hotspot(hoverTarget) : null });
|
||||
@@ -158,7 +199,7 @@ function frame(now) {
|
||||
requestAnimationFrame(frame);
|
||||
}
|
||||
|
||||
$('new-game').addEventListener('click', () => { if (storage) clearSave(storage); start(null); });
|
||||
$('new-game').addEventListener('click', () => { if (storage) clearSave(storage); showIntro(); });
|
||||
$('continue').addEventListener('click', () => start(readSave(storage)));
|
||||
$('restart').addEventListener('click', () => { if (storage) clearSave(storage); start(null); });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user