Game design

Coyote time, ovvero come mentire un po’ ai giocatori

8 novembre 20267 min

Nei giochi esiste una categoria di bug piuttosto curiosa: formalmente sono bug a tutti gli effetti, ma ai giocatori piacciono, e anzi sono proprio loro a chiamare bug il «comportamento normale» e a sfornare segnalazioni sui programmatori con due mani sinistre. Dove il programmatore ha fatto il suo lavoro bene e in modo fisicamente corretto, il giocatore vede un errore nella propria percezione del gioco: non della fisica, ma proprio della percezione. Immaginate il più classico dei platform, in cui il personaggio corre su una piattaforma, si avvicina al bordo e deve saltare sulla successiva. Se in quel punto gestite il salto in modo «corretto», tipo «il personaggio è a terra - può saltare, il personaggio non è a terra - non può saltare»: if (IsGrounded() && JumpPressed()) then Jump(), dal punto di vista della fisica «corretta» non c'è nulla da obiettare, perché se sotto i piedi non c'è più la piattaforma sarebbe piuttosto strano dire al personaggio che si trova ancora a terra.

E se il tasto di salto è stato premuto dopo che il sistema di collisioni ci ha comunicato la perdita di contatto con la superficie, il salto va negato. Di questa logica si può persino dire che si comporta in modo «fisicamente corretto», ma poi arriva il tester e dice: «Ragazzi, il salto è strano. Sento di essere a terra, ma il salto non parte».

Quel «sento» nasce dall'esperienza umana: gli occhi stanno un po' più avanti, i talloni un po' più indietro, e tutta la vostra esperienza precedente vi dice che si può ancora saltare. In realtà no, non si può: con i talloni proprio sul bordo della piattaforma non riuscirete a saltare, a meno che, ovviamente, non siate Drake o Jackie Chan. Il vostro cervello si limita a compensare la vostra posizione rispetto al punto di stacco, raccontandovi qualche piccola bugia.

Va bene, lasciamo in pace i tester: sono brave persone e spesso hanno più pietà dei giocatori che degli sviluppatori. Andiamo dal nostro programmatore della fisica a presentargli le lamentele dei giocatori, e riceviamo una risposta fisicamente corretta, ma sbagliata, badate bene: «manca una finestra di tolleranza tra lo stato grounded e il momento in cui arriva il jump input». Tradotto in lingua umana: «il bordo della piattaforma è un edge case che non obbedisce alla fisica».

Dove finisce la piattaforma?

Supponiamo che il personaggio corra verso destra a 5 metri al secondo, che il gioco giri a 60 FPS e che a un certo punto succeda questo:

frame 100 -> personaggio ancora sulla piattaforma
frame 101 -> personaggio ancora sulla piattaforma
frame 102 -> personaggio ha lasciato la piattaforma
frame 103 -> il giocatore ha premuto Jump

Per la fisica, insomma, la situazione è del tutto univoca: al frame 103 il personaggio è in aria, quindi IsGrounded() restituisce false, la condizione non è soddisfatta, niente salto. Per la persona davanti al monitor le cose stanno in modo completamente diverso: lei non vede i singoli frame, vede il movimento, e non come una sequenza di stati discreti, ma come un evento continuo. «Sono arrivato al bordo, mi trovo letteralmente a pochi pixel da lì, ho premuto il tasto, ed è successo tutto praticamente nello stesso istante».

Ma il gioco ha già fatto in tempo a spezzare questa scena in diversi stati completamente distinti, a registrare tra loro qualche millisecondo di differenza e a dire, in sostanza, «troppo tardi, ti è mancata la terra sotto i piedi».

Qui arriviamo a uno dei problemi fondamentali dello sviluppo di videogiochi, che con i salti non c'entra affatto: è solo che lì si vede più spesso. Il gioco misura il mondo in eventi, mentre l'uomo lo percepisce in stati di movimento, e ciò che per il gioco è avvenuto al frame 101, 102 o 103, per l'uomo è «vedo il bordo, decido di saltare e premo il tasto». Non sono tre azioni diverse, è un unico stato di movimento. Queste due visioni divergono, e la logica rigorosa comincia a punire la persona per qualcosa che non è colpa sua, ma dei limiti del suo sistema nervoso.

Ed ecco che spunta il coyote dei cartoni

Il termine stesso «coyote time» rimanda alla vecchia gag dei cartoni animati con Wile E. Coyote, che corre oltre il bordo del burrone e per un po' continua a correre sicuro nell'aria, finché non guarda in basso e non si accorge che sotto i piedi, a dirla tutta, non c'è più niente. È una metafora piuttosto precisa della meccanica: il personaggio ha già lasciato la piattaforma e fisicamente si trova in aria, ma il gioco per qualche frame ancora finge che sotto di lui ci sia la terra, e se il giocatore preme salto subito dopo il bordo della piattaforma, il gioco gli permette comunque di saltare.

if (IsGrounded() || timeSinceLeftGround < coyoteTime)
    Jump();

Alla fine tutta la meccanica si riduce a un paio di righe e a un valore di configurazione, ma abbiamo appena aggiunto al gioco un bug di proposito: il gioco sa che il personaggio è in aria, il gioco sa che la superficie è finita, il gioco sa che il giocatore è in ritardo. Però abbiamo restituito il comportamento «giusto», quello che si aspetta il giocatore, che semplicemente preme il tasto e vede il salto. Un buon coyote time non dovrebbe essere percepito come una meccanica a sé, ma come un normale controllo del personaggio.

Ma chi se l'è inventato?

Il nome moderno è chiaramente legato al coyote dei cartoni, ma individuare la prima persona che ha inventato la meccanica, o che per prima l'ha chiamata proprio coyote time, è praticamente impossibile. Varianti simili di «perdono del bordo» esistevano nei vecchi platform molto prima che la terminologia attuale si diffondesse, e alcuni giochi d'epoca contenevano meccaniche decisamente simili. Tra gli esempi citati ci sono A Boy and His Blob, Donkey Kong Country, Super Mario Bros. 2 e altri giochi della fine degli anni '80 e degli anni '90.

Il coyote time somiglia molto a una di quelle idee che spuntano contemporaneamente in tutta l'industria: decine di sviluppatori si scontrano con lo stesso problema, ognuno lo risolve un po' a modo suo, e poi col tempo la community trova un nome azzeccato per una tecnica che già esiste. Prima nasce una piccola pezza, poi la pezza diventa un sistema, poi il sistema riceve un nome, e vent'anni dopo gli sviluppatori chiedono quale float bisogna impostare per ottenere il comportamento «giusto». Ma il float giusto, ovviamente, non esiste, perché i giocatori premono i tasti con ritardi diversi, da 80 ms fino a 420 ms nel caso peggiore.

Ma a cosa serve il coyote time?

Qui bisogna dimenticarsi per un attimo del salto perché, come ho già detto, questa meccanica non riguarda affatto i salti, ma il ritardo tra ciò che il giocatore vede e la sua reazione. La persona vede la piattaforma, valuta la distanza, capisce che adesso deve saltare. Poi il sistema nervoso trasmette il segnale ai muscoli, il dito inizia a muoversi, il tasto viene premuto, il controller trasmette lo stato al gioco, e solo a quel punto il nostro codice viene a sapere cos'è successo.

Eppure al giocatore sembra di aver fatto tutto all'istante: per lui non c'è differenza tra «ho premuto 30 millisecondi prima del bordo» e «ho premuto 30 millisecondi dopo il bordo», perché questi eventi si trovano praticamente nello stesso punto del suo tempo soggettivo. Per il gioco la differenza c'è, perché tra il momento in cui un certo sistema ha comunicato che l'azione è impossibile e la pressione del tasto possono passare fino a 30 frame (nel caso peggiore 420 ms, cioè quasi 30 frame). Ed è proprio per questo che i giochi sono costretti di continuo a concedere simili agevolazioni: il problema non è che il giocatore sia scarso o lento, è che la percezione umana è continua, mentre il gioco, in fin dei conti, è fatto di singoli stati discreti.

E più il gioco è veloce, più questa differenza si fa sentire: a 60 FPS un frame dura circa 16,7 millisecondi, sei frame sono già circa 100 millisecondi, che per una persona è praticamente il massimo dei riflessi rapidi.

Il problema inverso del coyote time

Ok, abbiamo imparato a perdonare il ritardo del giocatore, e ora, superato il bordo della piattaforma e premuto il salto qualche frame dopo, lui salta comunque. Ma salta fuori un'altra situazione. Il personaggio sta cadendo su una piattaforma e il giocatore vuole fare un «bunny hop», cioè proseguire il movimento con una serie di salti: vede il personaggio toccare terra e preme il tasto. Ma la fisica non ha ancora fatto in tempo a registrare l'atterraggio, e succede questo:

frame 200 -> personaggio in aria
frame 201 -> personaggio ha quasi toccato terra
frame 202 -> il giocatore ha premuto Jump
frame 203 -> personaggio è atterrato

E di nuovo il gioco ha ragione: al frame 202 non si può ancora saltare, solo che il giocatore, ancora una volta, è convinto di aver fatto tutto bene. Lui non vuole saltare «adesso», vuole saltare «appena atterra», e se ci limitiamo a buttare via l'input perdiamo l'intenzione del giocatore, strappandolo dal suo stato di movimento. Così tocca commettere un secondo errore, il cosiddetto jump buffer, in cui il gioco conserva la pressione del tasto per un certo tempo:

if (JumpPressed())
    jumpBuffer = JumpBufferTime;
else
    jumpBuffer -= dt;

E dopo l'atterraggio controlliamo se il giocatore non volesse saltare un po' prima:

if (IsGrounded() && jumpBuffer > 0.0f)
    Jump();

È logico: se il coyote time perdona il ritardo, un altro sistema deve perdonare la fretta. Il che ci porta a una descrizione del «gameplay giusto» più precisa della parola «reattività». Però qui andrei molto cauto con il numero in sé: come capite, i tempi di reazione delle persone sono diversi, e se proviamo a creare un parametro magico, o dei parametri, per entrambe le agevolazioni, sarà sempre sbagliato:

Coyote Time:
Mario       = 100 ms
Celeste     = 100 ms
Hollow Knight = 120 ms
Your Game   = ????

Per un sistema del genere non esistono numeri giusti: 100 millisecondi possono essere un valore perfetto per una persona e insufficiente per un'altra. Ecco perché spuntano altri due sistemi, che regolano questi numeri in base alla velocità del personaggio, alle dimensioni della piattaforma, all'accelerazione attuale, alla durata del salto, alla durata dell'animazione in corso e ai tempi con cui il giocatore ha premuto il tasto in passato, portandoci a un sistema di movimento del personaggio a sé stante.

Come influisce sulla difficoltà del gioco

Il coyote time semplifica il gioco: per il giocatore saltare diventa un po' più facile. E per due giocatori che leggono il livello in modo diverso, prendono decisioni in modo diverso e premono il tasto in frame diversi, il gioco elimina proprio questa differenza. Questo permette al designer di creare livelli più difficili e spettacolari, aggiungendo una difficoltà interessante.

Ma c'è anche il rovescio: i giochi moderni senza questo sistema ora sembrano rotti. Quando un giocatore gioca per anni ai titoli moderni, si abitua pian piano a un certo livello di «perdono», e non solo nei salti: questo coyote time trapela anche in altri sistemi. Solo che ormai il giocatore non sa nemmeno che questo livello esiste, e lo considera la norma. Se ha premuto il tasto di salto un attimo prima dell'atterraggio, il personaggio deve saltare. Se è appena sceso dal bordo, il salto deve ancora funzionare. Se ha quasi preso l'angolo della piattaforma, il gioco probabilmente dovrebbe dargli una mano. Se ha avviato un'azione qualche millisecondo prima che finisse la precedente, il gioco forse dovrebbe mettere l'input in coda. Il giocatore non lo percepisce come un aiuto: per lui ormai è un «normale controllo».

E quando si imbatte in un gioco dove niente di tutto questo c'è, si crea una situazione in cui il gioco può essere tecnicamente, «fisicamente» corretto, ma il giocatore dice che «i controlli sono un po' storti». Non significa per forza che i vecchi giochi fossero brutti: è semplicemente cambiato lo standard culturale di come un gioco deve interpretare un'azione umana. Più o meno lo stesso è successo con i salvataggi: un tempo era del tutto normale perdere un'ora di progressi dopo una morte, oggi una persona può perdere dieci minuti e cominciare già a sospettare che gli sviluppatori abbiano deciso di condurre su di lei un esperimento sociale. Ci abituiamo gradualmente a un certo livello di comodità, e da quel momento la mancanza della comodità abituale comincia a essere percepita non come l'assenza di una funzione, ma come un «errore di comportamento del programma».

Un buon gioco a volte deve mentire

Se il gioco riesce a capire cosa voleva fare il giocatore, deve cercare di farlo, e allora diventa chiaro perché la stessa logica compare ben oltre i salti. Nei giochi moderni questo sistema funziona già nel buffer degli attacchi, nei ritardi extra per il wall jump, nel sistema che permette a un'azione di iniziare un po' prima che la precedente sia finita, o nel mantenere lo slancio del giocatore per qualche frame dopo che si è fermato.

Ammettiamo che tra l'intenzione della persona e lo stato della simulazione esista un piccolo errore temporale e spaziale, e poi progettiamo apposta il sistema in modo che questo errore non si trasformi in una punizione. Siamo abituati a pensare che una buona fisica rispetti il più fedelmente possibile le regole stabilite, ma al giocatore della vostra fisica non importa nulla. Gli serve un personaggio che faccia ciò che lui si aspetta.

«Sì, tecnicamente sei già caduto dalla piattaforma, ma capisco che volevi saltare, quindi facciamo finta che tu non sia ancora caduto»: questa è una parte del tutto normale dello sviluppo di videogiochi. Si può costringere la persona ad adattarsi al computer, oppure si può adattare un po' il gioco alla persona.

E se il gioco è riuscito ad adattarsi, il giocatore non si accorgerà nemmeno di quante piccole regole avete infranto per lui. Premerà semplicemente il tasto, e il personaggio salterà.

P.S. Tutti gli esempi sono qui

Tutti gli articoli