Andrea vero: camminata, parlato, prendere e di spalle dal foglio sprite

tools/slice_sprites.py da Manzolo Rush e tools/prepare_character.py, che allinea
i fotogrammi sul busto e sui piedi; il motore sceglie il fotogramma da quello
che Andrea sta facendo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 01:00:36 +02:00
co-authored by Claude Opus 5.5
parent 7133c46891
commit f7f65a74fd
12 changed files with 803 additions and 19 deletions
+47 -11
View File
@@ -56,15 +56,50 @@ export function drawAndrea(ctx, x, y, scale, facing, walkPhase) {
ctx.restore();
}
// Sfondi delle scene, caricati una volta sola. Finché un'immagine non c'è si vedono le forme `art`.
const backgrounds = new Map();
export function loadBackgrounds(story) {
return Promise.all(Object.values(story.scenes).filter(s => s.background).map(s => new Promise(resolve => {
const img = new Image();
img.onload = () => { backgrounds.set(s.background, img); resolve(true); };
img.onerror = () => resolve(false);
img.src = s.background;
})));
// Sfondi e fogli dei personaggi, caricati una volta sola. Finché un'immagine non c'è si vedono i segnaposto.
const backgrounds = new Map(), sprites = new Map();
const loadImage = src => new Promise(resolve => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => resolve(null);
img.src = src;
});
export function loadAssets(story) {
const scenes = Object.values(story.scenes).filter(s => s.background).map(async s => {
const img = await loadImage(s.background);
if (img) backgrounds.set(s.background, img);
return !!img;
});
const people = Object.entries(story.characters).filter(([, c]) => c.sprite).map(async ([id, c]) => {
try {
const atlas = await (await fetch(c.sprite)).json();
const img = await loadImage(new URL(atlas.image, new URL(c.sprite, location.href)).href);
if (img) sprites.set(id, { atlas, img });
return !!img;
} catch {
return false;
}
});
return Promise.all([...scenes, ...people]);
}
// Il personaggio alto PERSON pixel a grandezza 1, con i piedi in (x, y); rivolto a sinistra si specchia.
const PERSON = 62;
function drawSprite(ctx, { atlas, img }, frame, x, y, scale, facing) {
const f = atlas.frames[frame] ?? atlas.frames.stand, k = PERSON * scale / atlas.height;
ctx.save();
ctx.translate(Math.round(x), Math.round(y));
ctx.fillStyle = 'rgba(0,0,0,0.22)';
ctx.beginPath();
ctx.ellipse(0, 0, 13 * scale, 3 * scale, 0, 0, Math.PI * 2);
ctx.fill();
// La pagina è ingrandita a pixel netti: qui invece si rimpicciolisce, e la media dei pixel evita lo sfarfallio.
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
if (frame !== 'back') ctx.scale(facing, 1);
ctx.drawImage(img, f.x, f.y, f.w, f.h, -f.ax * k, -f.ay * k, f.w * k, f.h * k);
ctx.restore();
}
export function render(ctx, game, { hover } = {}) {
@@ -74,8 +109,9 @@ export function render(ctx, game, { hover } = {}) {
if (bg) ctx.drawImage(bg, 0, 0, WIDTH, HEIGHT);
for (const shape of scene.art ?? []) drawShape(ctx, shape);
for (const h of game.hotspots()) for (const shape of h.art ?? []) drawShape(ctx, shape);
const phase = game.walking ? (s.x + s.y) / 6 : null;
drawAndrea(ctx, s.x, s.y, game.scaleAt(s.y), s.facing, phase);
const andrea = sprites.get('andrea');
if (andrea) drawSprite(ctx, andrea, game.pose(), s.x, s.y, game.scaleAt(s.y), s.facing);
else drawAndrea(ctx, s.x, s.y, game.scaleAt(s.y), s.facing, game.walking ? game.stride / 6 : null);
if (hover) {
ctx.strokeStyle = 'rgba(255,255,255,0.35)';
ctx.setLineDash([3, 3]);