Alieni, piccioni e palline da golf (parte 1)

Autore: Raffox - Pubblicato il 15-08-2017.
(© https://ready64.org - Versione Stampabile. Riproduzione Vietata.)

Planet Golf

Abbiamo già avuto il grande piacere di conoscere Antonio Savona, l'ideatore del videogioco P0 Snake, pubblicato da Psytronik e RGCD nel 2016. Ebbene... Antonio non è rimasto con le mani in mano e ha reso quest'estate 2017 ancora più calda proponendoci Planet Golf, un nuovo videogame molto appassionante. Abbiamo pensato di discostarci un po' dal nostro abituale format delle interviste, lasciando penna e calamaio direttamente al programmatore, libero di snocciolare quanti più dettagli tecnici possibili, come ad esempio quelli relativi alla simulazione della fisica implementata nel game.


Perchè il golf

Come spesso accade in questi casi, l'idea di fare Planet Golf mi è venuta nel tentativo di realizzare tutt'altro.

Inizialmente, incuriosito dal gioco Peggle della PopCap, volevo capire più che altro se fosse possibile implementare una simulazione abbastanza realistica della meccanica newtoniana. Un semplice esperimento/sfida, quindi, senza nessun concept specifico di gioco in mente (in particolare, non ero interessato a ricreare Peggle stesso, la cui interfaccia verrebbe eccessivamente penalizzata da un dispositivo non touch).

Questo obiettivo di per sé non era particolarmente ambizioso, visto che già nei primi anni 80 sul Commodore 64 erano apparsi alcuni Pinball che implementavano approssimazioni meccaniche piuttosto credibili. Il primo di questi, Night Mission, risale addirittura al 1982. Tuttavia in questi esempi la creazione di un ambiente fisico era semplificata dal fatto che tutti questi giochi disponevano di un set di ostacoli predefinito e immutabile: il tavolo di gioco, appunto. Questa limitazione facilita notevolmente il calcolo dei rimbalzi della pallina perché in questo modo le possibili situazioni di gioco sono finite e pre-calcolabili. Da un punto di vista prettamente computazionale, questo si traduce nella possibilità di implementare dinamiche precalcolate in forma tabellare, calcolando durante il gioco semplicemente le deviazioni rispetto a queste situazioni predefinite. Chiaramente questo non era il caso di Peggle, che contiene ostacoli mobili e livelli modificabili a seguito dell'azione stessa del giocatore. Peggle e molti dei così detti physics game usano infatti un vero e proprio engine di simulazione fisica che si occupa di gestire rimbalzi, attrito e quant'altro, potendo contare sulla potenza di calcolo garantita dai processori moderni. La mia domanda era: "Posso ricreare questi movimenti così 'affascinanti' anche sul meno prestante Commodore 64?". Non è stato propriamente semplice (vedremo alcuni dettagli in seguito), ma alla fine mi sono ritrovato con quello che sarebbe poi diventato l'engine di Planet Golf: la soluzione al problema di cui sopra, cioè una pallina che poteva rimbalzare realisticamente su ostacoli buttati a caso sullo schermo.

Nei giorni seguenti, riguardando il codice e giocherellando senza scopo con questa pallina, la mia attenzione si è focalizzata su un aspetto fondamentale: trattandosi di una vera e propria simulazione della fisica, e non di una riproduzione artificiosa, avevo a mia disposizione alcune costanti, come l'accelerazione gravitazionale, l'attrito delle superfici e via di seguito. Cambiando queste grandezze la pallina rispondeva realisticamente, rotolando più o meno velocemente e facendo rimbalzi più o meno lunghi. L'associazione con diversi pianeti (dove queste costanti sono, appunto, diverse) è venuta abbastanza spontaneamente, e da lì è nato il concept di Planet Golf. Il golf è il gioco più semplice che si possa realizzare avendo a disposizione un engine del genere. Se fossi stato un po' meno pigro, magari ora potremmo giocare a Planet Tennis, oppure a Planet Soccer. 

Sarà per un'altra volta…

Planet Golf

Difficoltà nello sviluppo di Planet Golf

Il motto del grande Jeff Minter, il prezioso insegnamento che tutti noi aspiranti game developer dovremmo tenere sempre a mente, è il seguente: “get the controls right”.

Jeff sostiene che un gioco esteticamente stupendo non sarà mai un buon gioco se il giocatore non riesce a padroneggiarlo con naturalezza. Analogamente anche il gioco più semplice, perfino banale, può diventare appagante a patto che l’interazione sia semplice e immediata. Sembra un'ovvietà, ma la storia del videogioco è piena di titoli potenzialmente grandiosi che si sono rivelati delle esperienze frustranti a causa di controlli completamente sballati. 

In Planet Golf, la rifinitura degli aspetti relativi alla fisica e il perfezionamento dei controlli sono restati legati a doppio filo per tutta la durata dello sviluppo. Perfino la scelta dei pianeti è conseguenza diretta di quella che amo chiamare “la legge di Minter”: una volta raggiunta una giocabilità e una controllabilità ottimali su ciò che avevo deciso sarebbe stato il pianeta Terra, e fissata una “forza” massima per il giocatore (non mi piaceva l’idea di rendere il golfista più o meno forzuto a seconda del pianeta), ho provato ad applicare le costanti fisiche principali che ci sarebbero state su altri corpi celesti.

Molti mi chiedono come mai Planet Golf non includa la Luna, sebbene il gioco si ispiri proprio al famoso colpo che Alan Shephard eseguì sul nostro satellite naturale nel lontano 1971. Ebbene, con la sua attrazione gravitazionale 6 volte inferiore a quella terrestre, e in mancanza di attrito, la pallina poteva essere scagliata a decine di schermi di distanza con un tiro a piena potenza e i tiri di precisione diventavano praticamente impossibili da effettuare. Dopo aver colpito la pallina, Alan Shephard esclamò: "Miles and miles and miles". Il giocatore si sarebbe ritrovato a ripetere questa frase un po' troppo spesso.

Tecniche implementate

Ci sono almeno tre aspetti di Planet Golf che hanno richiesto molto tempo e tantissime "limature" perché non sono molto comuni nei giochi per Commodore 64: la fisica, come detto, il codec video negli extra e la decompressione real-time del parlato digitalizzato. Ci sono poi altre fasi di gioco che, pur non essendo particolarmente innovative, hanno richiesto uno sforzo implementativo paragonabile, se non superiore, come ad esempio la schermata di selezione del pianeta che è di gran lunga la parte più complessa di tutto il gioco: implementa, tra le altre cose, un multiplexer dinamico per gestire 40 sprite, decompressione dinamica di bitmap e parlato digitalizzato (quest'ultimo, come noto agli addetti ai lavori, mal si coniuga con i multiplexer).

Planet Golf

Questi aspetti verranno trattati in articoli futuri. Per ora possiamo concentrarci su quello che ha suscitato più curiosità: la fisica.

La fisica, come detto, è nata prima del gioco. Nell'implementarla non ho fatto ricorso a scorciatoie di nessun tipo, ma ho realmente implementato le equazioni che regolano il moto. Questo, sul Commodore 64, significa doversi costruire tutti i tasselli intermedi: di base, il processore del Commodore 64 può solo fare addizioni e sottrazioni di numeri interi a 8 bit. In realtà il Basic offre una implementazione dei numeri in virgola mobile e delle principali operazioni matematiche, ma si tratta di codice scritto per essere il più generico possibile, quindi risulta inutilizzabile per tutti casi in cui serva una certa velocità di calcolo (non a caso il Basic Commodore è lentissimo). Se servono quindi, ad esempio, le divisioni, tipicamente si finisce col doverle implementare in maniera ottimizzata. Come potrete immaginare, il percorso che porta dalle addizioni e sottrazioni di piccoli numeri interi a una corretta implementazione delle leggi della meccanica newtoniana è molto lungo! Descriverlo nella sua interezza renderebbe questo articolo troppo noioso. In particolare per la parte prettamente fisica, un qualunque testo di fisica del liceo scientifico svolge un lavoro migliore di quello che svolgerei io, ma ritengo possa essere interessante analizzare uno di questi tasselli: l'implementazione della moltiplicazione, spiegando un trucco impiegato spesso dai coder moderni.

La fisica di Planet Golf si basa su numeri in virgola mobile codificati in 4 byte, più un ulteriore byte che contiene il segno. La rappresentazione di questi numeri non è ottimale in termini di memoria, ma è funzionale al tipo di operazioni che il gioco si trova a svolgere. 

Un numero è rappresentato da due byte per la parte intera e due byte per la parte decimale. I primi due byte rappresentano un range di numeri interi tra 0 e 65535, mentre i tre byte per la parte decimale possono esprimere una precisione decimale di 1/65536.

Questa strana conformazione, normalmente poco flessibile, ha il vantaggio di essere particolarmente adatta per moltiplicazioni tra numeri interi e numeri in virgola mobile - una delle situazioni più frequenti nel gioco - e tra due numeri in virgola mobile. Vediamo un esempio del primo caso.

Il numero 112.5 in questa modalità sarà rappresentato dai seguenti byte:

0 (segno) 0 112 , 128 0

Questa tabella va letta così:

il byte 112 è autoesplicativo. Il byte 128 subito dopo la virgola, rappresenta 128*256 unità di precisione, cioè 128*256/65536 = 0.5 

Tutti sappiamo come fare la moltiplicazione in colonna, con il concetto di riporto. Nel sistema decimale il riporto è un numero da 0 a 9. Lavorando con byte il riporto sarà un numero tra 0 e 255. Il nostro "dieci" sarà il numero 256.

Vediamo come possiamo moltiplicare il numero dell'esempio per 4.

0*4 = 0

128*4 = 512 = 256*2 significa che abbiamo uno zero (il numero 256, lo ricordiamo, è il "dieci" della numerazione in base 256) e un riporto di 2;

112*4 = 448 significa che abbiamo 192 con il riporto di 1.

Sviluppando la moltiplicazione, includendo i riporti, abbiamo:

OP1 0 (segno) 0 112 , 128 0
OP2   4
Risultato 0 (segno) 1 194 , 0 0

Leggiamo questo numero nel sistema a virgola mobile:

1*256 + 194 = 450 che è esattamente il risultato che cercavamo: 112.5 * 4 = 450.

Il procedimento per moltiplicare due numeri in virgola mobile è molto simile, e contempla solo la necessità di fare degli shift di un numero di bit multiplo di 8 in aggiunta a quanto visto ora, cioè, di fatto, a "trascurare" alcuni byte del risultato finale.

Questo sistema ci consente di moltiplicare numeri in virgola mobile con precisione molto elevata utilizzando solo moltiplicazioni di numeri che vanno tra 0 e 255. Non male, vero?

Ora ci serve solo un sistema per fare le moltiplicazioni tra byte che sia molto veloce visto che, come ricordavo prima, il 6510 non lo prevede.

Un trucco utilizzato parte dall'analisi del noto sviluppo:

( a - b )^2 = a^2 + b^2 - 2ab

Significa che:

ab = [ a^2 + b^2 - ( a - b )^2 ] / 2

Questo significa che se avessimo un modo di calcolare l'operazione di elevamento a potenza di 2 in maniera facile e se sapessimo fare la divisione per 2, saremmo anche in grado di moltiplicare due numeri.

Per far ciò basterà tenere in memoria una tabella precalcolata contenente tutti i possibili valori di x^2, per x = [0..255]. Perché 255? Perché l'equazione ( a - b )^2, potendo a e b variare in un range tra 0 e 255, può assumere valori tra ( - 255 )^2 e 255^2. Tuttavia l'elevamento a potenza di due rende il segno sempre positivo, quindi ci basta che la tabella contenga il risultato dell'operazione per i valori tra 0 e 255. Si tratta di una struttura con 256 valori di 2 byte, quindi certamente semplice da tenere in memoria.

Ci restano quindi da fare solo le addizioni e la divisione finale.

In assembly la divisione intera per 2 è una operazione banale, che si può implementare tramite uno shift binario. Almeno in questo il 6510 ci può semplificare la vita, offrendoci di serie questa opportunità.

In conclusione la moltiplicazione tra due valori interi di un byte può essere svolta tramite 3 accessi a tabella, due addizioni, una sottrazione e uno shift.

Si tratta di tipologie di operazioni in cui il 6510 eccelle: gli accessi a tabella, grazie ai registri indice, sono velocissimi. Le addizioni, le sottrazioni e lo shift sono addirittura disponibili direttamente nel set di istruzioni.

Un ulteriore vantaggio di questo approccio consiste nel fatto di poter variare dinamicamente la precisione della moltiplicazione: "scartando" un byte a destra nel primo operando possiamo lavorare con precisione 1/256. Scartandone due, possiamo forzare operazioni sugli interi. Una delle mille ottimizzazioni che mi sono trovato a dover implementare per far girare il gioco alla velocità che desideravo, consiste proprio nel variare dinamicamente la precisione a seconda della fase del calcolo: quando la pallina si sposta senza urtare ostacoli, le operazioni che il gioco svolge sono parecchie, ma poco onerose: principalmente si tratta di applicare il vettore di moto alla pallina e, successivamente, la gravità, anche se bisogna farlo per svariate centinaia di iterazioni in un secondo (fino a venti volte per frame). In questo caso serve però una precisione molto alta, perché l'errore introdotto da un calcolo troppo approssimato si cumulerebbe per tutto il percorso della pallina. In questo caso il gioco usa il massimo della precisione consentita dall'approccio che abbiamo appena visto. Nel caso degli urti, invece, i calcoli che il gioco deve svolgere sono molto complessi, perché, tra le altre cose, si deve calcolare il rimbalzo. Tuttavia questo calcolo è puntuale, cioè viene fatto una sola volta, e quindi il problema del cumulo dell'errore non esiste. Il peggio che possiamo fare lavorando con una bassa precisione è sbagliare l'angolo di rimbalzo di qualche decimo di grado, ma questa approssimazione è comunque impossibile da rilevare visivamente. 

La variazione dinamica della precisione di calcolo a seconda delle situazioni di gioco è solo una delle molteplici ottimizzazioni che fanno sì che la fisica possa "girare" fluidamente anche nelle situazioni di gioco più complesse. Ci sono moltissimi altri "trucchi" che vanno dai più classici, come l'uso massiccio dei cosiddetti "unintended opcodes", alle soluzioni più inusuali come l'uso di grandi matrici compresse, che non potrebbero coesistere in memoria nella loro forma originaria, ma che Planet Golf "scompatta" in tempo reale, usa e poi distrugge. Un esempio di quest'ultima tecnica è dato dalla gestione dei rimbalzi. Alla pallina è associato un vettore che ne descrive il moto. Quando avviene un impatto o un "rotolamento", il calcolo del rimbalzo passa attraverso operazioni trigonometriche su questo vettore e sul vettore normale al punto di impatto. Ebbene, tale vettore è precalcolato per ogni pixel della superficie e necessariamente memorizzato in forma compressa. All'atto dell'impatto il vettore specifico viene identificato, scompattato, usato per calcolare l'angolo e l'entità del rimbalzo, e infine eliminato per risparmiare memoria.

Planet Golf

Tutte le altre operazioni che Planet Golf si trova a dover svolgere per simulare la fisica in modo realistico seguono un canovaccio simile: operazioni di base implementate in modo ottimizzato e tabelle precalcolate, ove possibile, per descrivere le caratteristiche fisiche e trigonometriche degli oggetti che fanno parte del gioco. 

Utilizzando questi blocchi come tasselli elementari per costruire le equazioni del moto si arriva ad avere un engine molto simile a quello di cui parlavamo all'inizio dell'articolo. Il percorso è stato semplicemente un po' più lungo.

- Qui la seconda parte dell'articolo -