Files
shalom/docs/02-meccaniche-suspense-reveal.md
2026-07-02 12:55:16 +02:00

24 KiB
Raw Permalink Blame History

Meccaniche di Suspense e Reveal: slot machine, ruote della fortuna e loot box

Ricerca applicata alla nostra app: assegniamo le persone alle stanze tramite un algoritmo (risultato predeterminato), ma lo riveliamo con un'animazione che costruisce attesa ed eccitazione, esattamente come lo "spin" di una slot machine. Questo documento raccoglie la psicologia, le meccaniche di animazione e le tecniche JS/CSS concrete per farlo bene.


1. La psicologia dello "spin & reveal"

Il punto centrale, ripetuto in quasi tutta la letteratura di game design, è controintuitivo ma fondamentale per noi:

Il momento della rivelazione (lo spin, il rallentamento, il drumroll) genera più coinvolgimento del premio stesso. Un'animazione di 3-5 secondi fa "più lavoro motivazionale" del contenuto effettivo che viene rivelato.

Questo significa che, anche se la stanza è già decisa dall'algoritmo, è la coreografia della rivelazione a creare l'emozione. Ecco i meccanismi psicologici da sfruttare.

1.1 Anticipazione e dopamina

Il cervello rilascia più dopamina nell'anticipazione di una ricompensa incerta che di fronte a una garantita ("dopamine release is significantly higher when a reward is uncertain than when it's guaranteed"). L'incertezza percepita — non il risultato — è il motore emotivo. Da qui la regola: allungare e drammatizzare la fase di attesa, non accorciarla.

1.2 Variable reward (rinforzo a rapporto variabile)

Le slot machine funzionano su uno schedule di rinforzo a rapporto variabile (variable ratio reinforcement), lo stesso principio comportamentale (Skinner) che rende difficile smettere: la ricompensa arriva in modo imprevedibile, e il cervello resta in stato di anticipazione costante. Nel nostro caso l'assegnazione avviene una volta sola, ma possiamo comunque sfruttare l'imprevedibilità percepita dell'esito (quale stanza?) durante lo spin.

1.3 Effetto near-miss (quasi-vincita)

Il near-miss è la situazione in cui i simboli si fermano quasi allineati (due jackpot allineati, il terzo a una posizione di distanza). Pur essendo tecnicamente una perdita, stimola il cervello quasi come una vittoria reale, rilasciando dopamina e alimentando la sensazione che il colpo grosso sia "a un passo".

Applicazione al nostro reveal: durante lo scorrimento, far passare vicino ad altre stanze/opzioni prima di fermarsi su quella finale amplifica la tensione ("stava per uscire l'altra stanza..."). È la stessa logica della roulette CS:GO che rallenta sfiorando altri item.

1.4 Escalation ed escalation sensoriale

I designer aumentano deliberatamente l'arousal prima di mostrare il contenuto:

  • In Overwatch, la loot box trema e vibra prima di aprirsi.
  • Fasci di luce dorati e particellari segnalano un drop raro PRIMA che l'item appaia.
  • Suoni, animazioni temporizzate e persino lo screen shake enfatizzano l'impatto emotivo.

L'escalation crea una curva di tensione crescente: più segnali "sta per succedere qualcosa di importante" accumuli, più forte è il picco al reveal.

1.5 Il rallentamento finale (slow-down / easing)

Elemento chiave e più imitabile: verso la fine, lo spin decelera progressivamente. Questo rallentamento è ciò che il cervello legge come "sta per fermarsi, dove cadrà?" — è il momento di massima tensione. In termini tecnici è una curva di easing-out (veloce all'inizio, lento alla fine), spesso con un piccolo overshoot/rimbalzo finale (back-out) per un "settle" soddisfacente (vedi §2.2).

1.6 Principi di design etico da mystery box (da Yu-kai Chou)

Riassunto delle leve applicabili in modo sano:

  1. Basso costo di partecipazione — l'utente accetta l'incertezza se lo sforzo è minimo.
  2. Investire nel reveal — il momento teatrale conta più del premio.
  3. Pacing dei reveal — reveal troppo frequenti diluiscono l'impatto e la sensibilità dopaminergica; spaziarli mantiene la novità.
  4. Floor garantito / "pity" — nessuno esce a mani vuote; anche l'esito "comune" deve sembrare significativo (importante per noi: qualunque stanza deve essere presentata come una "bella" assegnazione).

2. Meccaniche di animazione dei rulli (slot reel)

2.1 Come gira un rullo

Il rullo è tipicamente una striscia verticale di simboli (un contenitore lungo dentro una finestra con overflow: hidden). Lo spin si ottiene traslando la striscia in verticale (transform: translateY(...)), non spostando i singoli simboli. Approcci comuni:

  • CSS transition/animation su translateY con easing (semplice, performante).
  • Web Animations API (element.animate) per controllo dinamico del valore finale (consigliato per risultato predeterminato — vedi §7).
  • requestAnimationFrame per controllo frame-by-frame (fisica custom, blur di movimento).

Per la velocità percepita, durante la fase veloce si può aggiungere un leggero motion blur (es. filter: blur() verticale) o semplicemente ripetere i simboli in modo che scorrano rapidamente.

2.2 Curve di easing: le più importanti

Il carattere dell'animazione dipende quasi tutto dalla timing function. Riferimento pratico (easings.net):

Effetto Keyword / cubic-bezier Uso
ease-out cubic-bezier(0, 0, 0.58, 1) Decelerazione base a fine spin
easeOutBack (overshoot) cubic-bezier(0.34, 1.56, 0.64, 1) Il rullo supera il target e torna indietro → "settle" con rimbalzo
Wheel of fortune (con molla) cubic-bezier(0.440, -0.205, 0.000, 1.130) Decelerazione + micro-bounce elastico
ease-in cubic-bezier(0.42, 0, 1, 1) Accelerazione iniziale (partenza dello spin)

Nota tecnica cruciale sui cubic-bezier: gli assi x (tempo) devono stare tra 0 e 1, ma y (progresso) può uscire da 01. Un y > 1 produce overshoot (l'elemento supera la destinazione e rientra → effetto "back"/molla); un y < 0 produce undershoot. È così che si ottiene la sensazione elastica del rimbalzo finale con due sole maniglie.

Per un vero elastic/bounce multi-oscillazione (più rimbalzi smorzati), un singolo cubic-bezier non basta: serve un @keyframes complesso o una libreria (GSAP, Motion, Svelte easing). easeOutElastic dà "l'illusione di un rullo che ruota e poi si assesta".

2.3 Struttura tipica dello spin di un rullo

  1. Accelerazione breve (ease-in) o partenza istantanea.
  2. Fase veloce a velocità costante (loop di simboli, eventuale blur).
  3. Decelerazione lunga (ease-out) — è qui che nasce la suspense.
  4. Settle / bounce finale (easeOutBack) — il rullo si allinea al simbolo con un micro-scatto.

2.4 Stagger tra i rulli (fermata sequenziale)

Elemento firma delle slot: i rulli non si fermano tutti insieme, ma uno dopo l'altro da sinistra a destra. Questo:

  • prolunga la tensione (l'esito totale si scopre solo all'ultimo rullo);
  • abilita l'effetto near-miss (i primi rulli mostrano il simbolo giusto, l'ultimo tiene sul filo).

Implementazione: si applica un delay incrementale per indice di rullo (es. delay = index * 400ms, oppure lo step di aggiornamento è 50ms + index*stagger). Con Web Animations API basta impostare delay diverso per ogni rullo; con CSS, transition-delay o animation-delay scalati.

2.5 Il "bounce/settle" finale

I videogiochi di slot inseriscono "un piccolo rimbalzo nei rulli verso la fine dello spin, subito prima che l'esito finale venga mostrato". Tecnicamente: si fa fermare il rullo poco oltre la posizione target e lo si riporta indietro con un easeOutBack o un keyframe che overshoota di ~1 riga e rientra. È il dettaglio che rende la fermata "soddisfacente" invece che secca.

Riferimenti implementativi slot:

  • Victor Toschi, How to create a Slot Machine animation with CSS and JavaScript (metodi init / spin / shuffle) + CodePen toschivictor/pen/JjNZjEj.
  • ezslots (jQuery toolkit) e HTML-slot-machine-example (vanilla) su GitHub.
  • CodePen MrFirthy/pen/oGVWqK (CSS/JS slot machine).

3. Ruota della fortuna / spinning wheel (fisica e landing)

3.1 Struttura

La ruota è un cerchio suddiviso in spicchi; ogni spicchio è ruotato di 360° / n * indice con transform-origin sul bordo/centro. Si anima la rotazione dell'intera ruota (transform: rotate(...)) e un puntatore fisso indica il vincitore in cima.

3.2 Decelerazione (l'anima dell'effetto)

Come per i rulli, la magia è nella curva: la ruota parte veloce e decelera fino a fermarsi. Esempio reale dall'articolo DEV Community Wheel of Fortune with CSS (Web Animations API):

animation = wheel.animate(
  [
    { transform: `rotate(${previousEndDegree}deg)` },
    { transform: `rotate(${newEndDegree}deg)` }
  ],
  {
    duration: 4000,
    easing: 'cubic-bezier(0.440, -0.205, 0.000, 1.130)', // decelerazione + micro-bounce
    fill: 'forwards',
    iterations: 1
  }
);

3.3 Atterrare su un target (landing predeterminato)

Formula generale:

angoloSpicchio = 360 / numeroSpicchi
offsetTarget   = angoloSpicchio * indiceVincitore
rotazioneTotale = (giriInteri * 360) + offsetTarget   // ± correzione per il puntatore
  • Diversi giri interi (es. Math.random() * 360 + 1800, cioè ≥5 giri) servono a rendere l'effetto credibile: "spinnare direttamente sul premio non sembra giusto, servono dei giri completi".
  • Si aggiunge una randomizzazione entro lo spicchio così che il puntatore non cada sempre esattamente al centro (più realistico).
  • Importante: cancellare l'animazione precedente (animation.cancel()) prima di rilanciarne una nuova, per evitare conflitti.

3.4 Riferimenti wheel

  • dev.to/madsstoumann/wheel-of-fortune-with-css (Web Animations API, cubic-bezier a molla).
  • jamesrwilliams.ca/posts/css-wheel-of-fortune (approccio CSS puro).
  • Winwheel.js (dougtesting.net/winwheel) — libreria dedicata con animation.stopAngle, spin verso segmento predeterminato.
  • Konva (konvajs.org/docs/sandbox/Wheel_of_Fortune.html) — canvas 2D.
  • Emanuele Feronato — JavaScript + CSS 3D Wheel of Fortune.

4. Card flip, scratch card, box/envelope e loot box

Alternative allo spin che possiamo usare come variazione del reveal (o come "atto finale" dopo lo spin).

4.1 Card flip (rivelazione a carta girata)

Tecnica CSS 3D standard (David DeSandro, Intro to CSS 3D transforms — Card Flip):

  • perspective sul contenitore genitore → contesto 3D.
  • transform-style: preserve-3d sulla carta.
  • Due facce sovrapposte; il retro con backface-visibility: hidden e pre-ruotato rotateY(180deg).
  • Al click/hover si applica transform: rotateY(180deg) per rivelare.

Ottimo come reveal secco e teatrale: la carta si gira e mostra la stanza. Si combina bene con un piccolo delay/drumroll prima del flip.

4.2 Scratch card (gratta e vinci)

Con <canvas> e composite operation:

  • Si disegna la copertura opaca sul canvas.
  • ctx.globalCompositeOperation = "destination-out" fa sì che il movimento del cursore cancelli la copertura, rivelando il contenuto sotto.
  • Si tracciano archi (ctx.arc) alla posizione del puntatore e si può calcolare la percentuale di area grattata per auto-rivelare al superamento di una soglia.

Vantaggio: introduce interazione fisica (l'utente "scopre" la propria stanza), aumentando ownership ed engagement. Riferimenti: Envato Tuts+ scratch card effect in vanilla JavaScript; Medium (Mr Zouraiz) Digital Scratch Card.

4.3 Box / envelope / loot box open

Pattern loot box (Overwatch-style):

  1. Oggetto (box/busta) che vibra e trema (keyframe di shake) con intensità crescente.
  2. Build-up di particellari e luce che anticipa la rarità/importanza.
  3. Apertura (scale + fade + burst) e uscita del contenuto (la stanza).

Lo shake si fa con un @keyframes di piccole traslazioni/rotazioni alternate; l'escalation con animation-duration decrescente o ampiezza crescente. Il picco luminoso PRIMA del contenuto è il trucco che "vende" il reveal.


5. Countdown, drumroll ed escalation della tensione

Prima dello spin/reveal, un build-up temporizzato aumenta l'attesa.

  • Countdown ring animato: si anima stroke-dasharray di un cerchio SVG per "svuotare" un anello nel tempo, o si sovrappone un anello colorato che si consuma come progress bar (CSS-Tricks, How to Create an Animated Countdown Timer).
  • Drumroll audio + visivo: rullo di tamburi con vibrazione/pulsazione crescente dell'elemento centrale sincronizzata; il picco coincide con l'inizio dello spin.
  • Escalation: aumentare progressivamente frequenza dei flash, ampiezza dello shake, pitch/volume del suono. La curva di tensione deve salire fino al reveal e poi scaricarsi al settle.
  • Attenzione UX: i countdown troppo aggressivi generano stress/panico (Medium, The Stress of Countdown Clocks). Per noi il countdown deve essere eccitante, non ansiogeno → durate brevi (2-3s), tono celebrativo, nessuna penalità reale.

6. Come far sembrare RANDOM un risultato PREDETERMINATO

Questo è il cuore del nostro caso d'uso: l'algoritmo ha già scelto la stanza, ma l'animazione deve costruire suspense come se fosse un sorteggio dal vivo.

Principio guida: in tutti questi sistemi (slot, CS:GO case, loot box) l'esito è già deciso prima dell'animazione — "the weapon contained in a case is already predetermined... there is no roulette". L'animazione è pura scenografia. Quindi possiamo replicarlo in modo legittimo.

Ricetta pratica

  1. Decidi il risultato server-side (la stanza) e passalo al client.
  2. Costruisci una lista visiva che contiene il risultato in una posizione nota, circondato da distrattori (altre stanze). Es. per una roulette orizzontale stile CS:GO: array lungo di item con il vincitore posizionato vicino alla fine, poi altri item dopo per poter "sfiorare" e superare.
  3. Anima verso quella posizione con easing-out lungo, così l'utente vede scorrere molte opzioni e non può prevedere dove si fermerà.
  4. Sfrutta il near-miss: fai passare/rallentare vicino ad altre stanze prima di fermarti su quella giusta (l'effetto "stava per uscire l'altra").
  5. Settle con bounce (easeOutBack) sull'elemento vincitore + highlight/particellari.
  6. Non rivelare mai la posizione target in anticipo nel DOM visibile: l'ordine può essere reale ma l'occhio non deve poter contare gli spicchi.

Roulette orizzontale (CS:GO-style) — la più adatta a "assegnazione stanza"

È il pattern che mappa meglio sul nostro problema (una lista di stanze che scorre e si ferma su una).

  • Striscia orizzontale di card (le stanze, ripetute/mischiate), dentro una finestra con overflow: hidden e un marker centrale fisso.
  • Si anima translateX negativo fino a portare la card vincitrice sotto il marker, con ease-out lungo (3-6s).
  • La distanza finale include un piccolo offset random dentro la card così che non si fermi sempre perfettamente al centro → sembra fisico.
  • Riferimenti CodePen: killerek/pen/gLBgWd (case opening animation), marctannous/pen/xGRGBz (layout CS:GO + winner selection), memir0/pen/QPEPxm (random name picker side-scrolling). Discussione GSAP: Creating CS:GO Roulette Horizontal Spinner.

7. Tecniche JS/CSS pratiche

7.1 Web Animations API (element.animate) — consigliata per risultato dinamico

Ideale quando il valore finale (posizione/rotazione) è calcolato a runtime dall'esito dell'algoritmo.

const spin = strip.animate(
  [
    { transform: 'translateX(0)' },
    { transform: `translateX(${-targetX}px)` }
  ],
  {
    duration: 5000,
    easing: 'cubic-bezier(0.12, 0.8, 0.2, 1)', // decelerazione forte
    fill: 'forwards'
  }
);

// Sapere quando è finita → trigger del settle/celebrazione
spin.finished.then(() => {
  highlightWinner();
});
// oppure: spin.onfinish = () => { ... }

Opzioni chiave: duration (ms), easing, iterations, delay (per lo stagger dei rulli), fill: 'forwards' (mantiene lo stato finale). Controllo: play/pause/reverse/finish/cancel; cancel() prima di un nuovo spin per evitare conflitti.

7.2 CSS transition/animation

Semplice e performante per spin a target fisso o loop:

.reel {
  transition: transform 4s cubic-bezier(0.12, 0.8, 0.2, 1);
}
.reel.spinning { transform: translateY(-3000px); }

/* settle con overshoot */
.reel.settle {
  transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}

Per lo stagger: transition-delay / animation-delay scalati per indice. Nota: un cubic-bezier a 2 maniglie non fa bounce multipli → usare @keyframes o libreria.

7.3 requestAnimationFrame (fisica custom)

Quando serve controllo totale: decelerazione basata su velocità/attrito, snap al simbolo più vicino, motion blur dipendente dalla velocità.

function frame() {
  velocity *= friction;           // attrito → decelerazione
  offset += velocity;
  strip.style.transform = `translateY(${-offset}px)`;
  if (velocity > threshold) requestAnimationFrame(frame);
  else snapToNearest();           // allineamento finale + bounce
}

requestAnimationFrame sincronizza il callback con il ciclo di paint del browser → animazioni fluide ed efficienti.

7.4 Performance

  • Animare solo transform e opacity (compositor-only, no reflow/repaint).
  • Evitare di animare top/left/width/height.
  • will-change: transform sugli elementi che spinnano; rimuoverlo a fine animazione.
  • Preferire un singolo loop rAF condiviso a molti loop indipendenti.

7.5 Librerie utili

  • GSAP — timeline, easing avanzati (back, elastic, bounce), usato spesso per roulette CS:GO.
  • Motion / Web Animations API improvements (motion.dev) — spring nativi, DX migliorata.
  • Winwheel.js — ruote della fortuna con landing su segmento.
  • Konva — animazioni canvas 2D (ruota, particellari).

8. Applicazione consigliata al nostro reveal "assegnazione stanza"

Sequenza proposta, orchestrando i pezzi sopra:

  1. Build-up (2-3s): countdown ring + drumroll, elemento centrale che pulsa/vibra con escalation. Tono celebrativo, non ansiogeno (§5).
  2. Spin: roulette orizzontale CS:GO-style (§6) o rulli slot (§2) che scorrono le stanze, con blur/velocità alta.
  3. Decelerazione lunga con ease-out (§2.2, §3.2): momento di massima tensione.
  4. Near-miss: rallenta sfiorando 1-2 stanze alternative prima del target (§1.3).
  5. Settle con easeOutBack sul target + burst di luce/particellari (§1.4, §2.5).
  6. Reveal finale: opzionale card flip / apertura box sulla stanza assegnata (§4) con celebrazione.
  7. Floor positivo: presentare qualsiasi stanza come esito desiderabile (§1.6, punto pity).

Il risultato è deciso dall'algoritmo al passo 0, ma l'utente vive un sorteggio dal vivo credibile ed eccitante.


Fonti

Psicologia (near-miss, variable reward, anticipazione, loot box)

Animazione rulli slot

Ruota della fortuna

Roulette orizzontale / case opening (risultato predeterminato)

Card flip, scratch card

Countdown / drumroll / tensione

Easing e tecniche JS/CSS