Al Trebbio parla lo spirito del castello: compare accanto alla torre, semitrasparente, con la lanterna
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ApqNBvNbNCwXqR2RLo8QJh
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
@@ -160,6 +160,7 @@ export function render(ctx, game, { hover, spots, debug, time = performance.now(
|
||||
if (sheet) queue.push({ y: p.at[1], draw: () => drawSprite(ctx, sheet, game.personPose(p), ...p.at, game.scaleAt(p.at[1]), p.facing ?? 1, p.size) });
|
||||
}
|
||||
for (const q of queue.sort((a, b) => a.y - b.y)) q.draw();
|
||||
drawApparitions(ctx, game, time);
|
||||
drawLights(ctx, scene, time);
|
||||
for (const shape of art) if (shape.top) drawShape(ctx, shape);
|
||||
if (spots) drawSpots(ctx, game);
|
||||
@@ -172,6 +173,29 @@ export function render(ctx, game, { hover, spots, debug, time = performance.now(
|
||||
}
|
||||
}
|
||||
|
||||
// Le apparizioni (`apparitions`): uno spirito che compare solo mentre parla (un dialogo suo, o una sua battuta),
|
||||
// semitrasparente, che ondeggia piano, con un alone freddo intorno e il caldo della lanterna.
|
||||
function drawApparitions(ctx, game, time) {
|
||||
for (const a of game.scene.apparitions ?? []) {
|
||||
if (game.dialogue?.id !== a.who && game.speech?.who !== a.who) continue;
|
||||
const img = backgrounds.get(a.src);
|
||||
if (!img) { loadBackground(a.src); continue; }
|
||||
const h = a.height, w = Math.round(img.width * h / img.height), bob = Math.round(Math.sin(time / 600) * 2);
|
||||
const x = Math.round(a.at[0] - w / 2), y = a.at[1] - h + bob;
|
||||
ctx.save();
|
||||
const halo = ctx.createRadialGradient(a.at[0], y + h * 0.45, 0, a.at[0], y + h * 0.45, h * 0.7);
|
||||
halo.addColorStop(0, 'rgba(180, 230, 255, 0.28)');
|
||||
halo.addColorStop(1, 'rgba(180, 230, 255, 0)');
|
||||
ctx.globalCompositeOperation = 'lighter';
|
||||
ctx.fillStyle = halo;
|
||||
ctx.fillRect(a.at[0] - h, y - h * 0.3, h * 2, h * 1.6);
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
ctx.globalAlpha = 0.82 + 0.08 * Math.sin(time / 380);
|
||||
ctx.drawImage(img, x, y, w, h);
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
// Le scene buie (`shade`: il velo scuro sopra tutto, Andrea compreso) e le fiammelle (`lights`: candele accese).
|
||||
// Ogni fiamma ha due disegni che si alternano a caso, e intorno un alone caldo che pulsa piano.
|
||||
const FLAME_STEP = 140;
|
||||
|
||||
+4
-2
@@ -99,7 +99,7 @@ export const story = {
|
||||
andrea: { name: 'Andrea', color: '#ffffff', sprite: 'assets/characters/andrea.json' },
|
||||
vecchietta: { name: 'Vecchietta', color: '#ffd27a', sprite: 'assets/characters/vecchietta.json' },
|
||||
vecchietto: { name: 'Vecchietto', color: '#9fe3ff', sprite: 'assets/characters/vecchietto.json' },
|
||||
castello: { name: 'Il Castello', color: '#c8f0a0' },
|
||||
castello: { name: 'Lo spirito del Trebbio', color: '#c8f0ff' },
|
||||
leone: { name: 'Il leone', color: '#f0a070' },
|
||||
san_pietro: { name: 'San Pietro', color: '#e8e0c8' },
|
||||
tosca: { name: 'La Tosca', color: '#ffb0d0' },
|
||||
@@ -1736,7 +1736,9 @@ export const story = {
|
||||
walk: [[86, 308], [300, 306], [356, 308], [470, 312], [500, 324], [486, 360], [88, 360], [80, 330]],
|
||||
depth: [[306, 0.75], [360, 1.05]],
|
||||
entry: [110, 345],
|
||||
speakers: { castello: [389, 22] },
|
||||
// Lo spirito del castello (disegnato da Codex, idea di Andrea del 24 ottobre): compare quando parla.
|
||||
speakers: { castello: [446, 178] },
|
||||
apparitions: [{ who: 'castello', src: 'assets/characters/spirito-trebbio.png', at: [446, 304], height: 120 }],
|
||||
enter: [{ if: '!trebbio_visto', then: [{ set: 'trebbio_visto' }, { say: 'Il Trebbio. Quassù anche i pensieri vanno più piano.' }] }],
|
||||
hotspots: [
|
||||
{
|
||||
|
||||
@@ -65,3 +65,13 @@ test('le candele tremolano: due disegni che si alternano a caso, non tutte insie
|
||||
for (const [id, sc] of Object.entries(story.scenes)) for (const { at: [x, y] } of sc.lights ?? [])
|
||||
assert.ok(x >= 0 && x < 640 && y >= 4 && y < 360, `${id}: candela fuori scena`);
|
||||
});
|
||||
|
||||
test('le apparizioni (lo spirito del Trebbio) hanno la loro immagine e parlano con una voce del gioco', async () => {
|
||||
const { story } = await import('../src/story/story.js');
|
||||
const { existsSync } = await import('node:fs');
|
||||
for (const [id, sc] of Object.entries(story.scenes)) for (const a of sc.apparitions ?? []) {
|
||||
assert.ok(existsSync(new URL('../' + a.src, import.meta.url)), `${id}: manca ${a.src}`);
|
||||
assert.ok(story.characters[a.who], `${id}: ${a.who} non è un personaggio`);
|
||||
assert.ok(sc.speakers?.[a.who], `${id}: il fumetto di ${a.who} non ha un posto`);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user