Prototipo del motore: scena, camminata, verbi, inventario, dialoghi, salvataggio
Motore guidato dai dati con due scene di prova (Piazza e salita alla Fortezza), percorsi su area concava, clic intelligente e pressione lunga, test della soluzione completa in node e nel browser con Playwright. Nel documento la leggenda del nonno. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
// Disegno della scena su un canvas 640×360, ingrandito dal CSS senza sfocatura.
|
||||
// Le forme provvisorie (`art`) verranno sostituite da sfondi e sprite in pixel art.
|
||||
export const WIDTH = 640, HEIGHT = 360;
|
||||
|
||||
function drawShape(ctx, s) {
|
||||
if (s.rect) {
|
||||
ctx.fillStyle = s.fill;
|
||||
ctx.fillRect(...s.rect);
|
||||
} else if (s.poly) {
|
||||
ctx.fillStyle = s.fill;
|
||||
ctx.beginPath();
|
||||
s.poly.forEach(([x, y], i) => i ? ctx.lineTo(x, y) : ctx.moveTo(x, y));
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
} else if (s.lines) {
|
||||
// Righe di riempimento: fughe del selciato o sbarre di un cancello.
|
||||
const { from: [x0, y0], to: [x1, y1], step, vertical } = s.lines;
|
||||
ctx.fillStyle = s.stroke;
|
||||
if (vertical) for (let x = x0; x <= x1; x += step) ctx.fillRect(x, y0, 2, y1 - y0);
|
||||
else for (let y = y0; y <= y1; y += step) ctx.fillRect(x0, y, x1 - x0, 1);
|
||||
} else if (s.text) {
|
||||
ctx.fillStyle = s.fill;
|
||||
ctx.font = 'bold 11px monospace';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(s.text, ...s.at);
|
||||
}
|
||||
}
|
||||
|
||||
// Andrea provvisorio, disegnato a blocchi: maglia blu (il rione di Campomigliaio), gambe che si alternano.
|
||||
const BODY = [
|
||||
// [x, y, w, h, colore] con l'origine ai piedi, rivolto a destra.
|
||||
[-6, -60, 12, 4, '#4a2e1c'], [-6, -56, 12, 12, '#e8b48c'], [3, -53, 2, 2, '#2a1a10'], [-7, -60, 4, 10, '#4a2e1c'],
|
||||
[-8, -44, 16, 20, '#2f5fb3'], [-4, -42, 8, 3, '#f4efe4'],
|
||||
];
|
||||
|
||||
export function drawAndrea(ctx, x, y, scale, facing, walkPhase) {
|
||||
ctx.save();
|
||||
ctx.translate(Math.round(x), Math.round(y));
|
||||
ctx.scale(scale * facing, scale);
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.22)';
|
||||
ctx.fillRect(-10, -2, 20, 4);
|
||||
const swing = walkPhase == null ? 0 : Math.round(Math.sin(walkPhase) * 5);
|
||||
ctx.fillStyle = '#3a3f52';
|
||||
ctx.fillRect(-6 + swing, -24, 5, 22);
|
||||
ctx.fillRect(1 - swing, -24, 5, 22);
|
||||
ctx.fillStyle = '#e8e3d6';
|
||||
ctx.fillRect(-7 + swing, -3, 7, 3);
|
||||
ctx.fillRect(0 - swing, -3, 7, 3);
|
||||
for (const [bx, by, w, h, c] of BODY) {
|
||||
ctx.fillStyle = c;
|
||||
ctx.fillRect(bx, by, w, h);
|
||||
}
|
||||
ctx.fillStyle = '#e8b48c';
|
||||
ctx.fillRect(-2 - swing, -40, 4, 16);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
export function render(ctx, game, { hover } = {}) {
|
||||
const scene = game.scene, s = game.state;
|
||||
ctx.clearRect(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);
|
||||
if (hover) {
|
||||
ctx.strokeStyle = 'rgba(255,255,255,0.35)';
|
||||
ctx.setLineDash([3, 3]);
|
||||
ctx.strokeRect(...hover.box);
|
||||
ctx.setLineDash([]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user